From d1885a61482b77ed5c5d133f65e4dc3d97d43bf2 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 18 Jul 2025 17:39:44 +0200 Subject: [PATCH 001/220] added initial base contracts, UniV3StyleFacet and tests --- new-lda-2.0.md | 1291 +++++++ src/Libraries/LibCallbackManager.sol | 58 + src/Libraries/LibInputStream.sol | 146 + src/Libraries/LibUniV3Logic.sol | 99 + src/Periphery/Lda/Facets/CoreRouteFacet.sol | 232 ++ src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 76 + src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 37 + src/Periphery/Lda/LdaDiamond.sol | 72 + .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 3357 ++++++++++++++++ .../Lda/Facets/UniV3StyleFacet.t.sol | 28 + .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 3383 +++++++++++++++++ .../Periphery/Lda/utils/LdaDiamondTest.sol | 34 + test/solidity/utils/BaseDiamondTest.sol | 88 + test/solidity/utils/DiamondTest.sol | 108 +- test/solidity/utils/TestBase.sol | 5 +- 15 files changed, 8916 insertions(+), 98 deletions(-) create mode 100644 new-lda-2.0.md create mode 100644 src/Libraries/LibCallbackManager.sol create mode 100644 src/Libraries/LibInputStream.sol create mode 100644 src/Libraries/LibUniV3Logic.sol create mode 100644 src/Periphery/Lda/Facets/CoreRouteFacet.sol create mode 100644 src/Periphery/Lda/Facets/UniV2StyleFacet.sol create mode 100644 src/Periphery/Lda/Facets/UniV3StyleFacet.sol create mode 100644 src/Periphery/Lda/LdaDiamond.sol create mode 100644 test/solidity/Periphery/Lda/BaseDexFacet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol create mode 100644 test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol create mode 100644 test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol create mode 100644 test/solidity/utils/BaseDiamondTest.sol diff --git a/new-lda-2.0.md b/new-lda-2.0.md new file mode 100644 index 000000000..51441f757 --- /dev/null +++ b/new-lda-2.0.md @@ -0,0 +1,1291 @@ +## **Part I: Architectural Decision** + +## **1. Core Architecture Comparison** + +### **1.1 High-Level Architecture** + +| **Pattern** | **OLD Monolithic** | **NEW Diamond + Registry** | +| --- | --- | --- | +| **Proxy Style** | Transparent proxy. One huge contract | EIP-2535 Diamond. Minimal proxy + many facets | +| **Code-Size** | All logic in one file → size limits | Each facet separate → no init-code size hits | +| **Upgrade Scope** | Redeploy entire contract | Upgrade individual facet | +| **Modularity** | One `swap(...)` with 10+ branches | One facet per dex + dynamic registry dispatch | +| **Tests** | One massive suite | Per-facet suites (`CoreRouteFacet`, `FooFacet`) | +| **Callback Guards** | `lastCalledPool = ...` | `CallbackManager.arm()/verify()/clear()` (see 4.1) | +| **Transfers** | `IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));` | // In facet implementations: `import { LibAsset } from "../Libraries/LibAsset.sol"; LibAsset.transferAsset(tokenIn, payable(recipient), amount); LibAsset.transferFromERC20(tokenIn, from, recipient, amount); LibAsset.depositAsset(tokenIn, amount); | + +--- + +### **1.2. Facet Breakdown & Naming** + +The entry point remains exactly the same: users still call `processRoute(...)`, but under the new architecture that logic now lives in **CoreRouteFacet.sol**, which looks up the target in the registry and forwards execution to the appropriate dex facet. All DEX-related facets will follow the naming convention: **`{DexName}Facet.sol`**. + +| **OLD (standalone functions)** | **NEW Facet** | **Notes** | +| --- | --- | --- | +| `processRoute(...)`, `transferValueAnd…(...)` | **CoreRouteFacet.sol** | Entrypoints + registry + helpers (`applyPermit`, `distributeAndSwap`, `dispatchSwap`) | +| `swapUniV2(...)` | **UniswapV2StyleFacet.sol** | Handles UniV2, SushiSwap, PancakeV2, TraderJoe V1, and other router-based DEXs | +| `swapUniV3(...)` + `uniswapV3SwapCallback(...)` | **UniV3Facet.sol** | Uniswap V3 logic and callbacks | +| `pancakeV3SwapCallback(...)` | **PancakeV3Facet.sol** | May or may not include swap function depending on approach (see sections 3.1 and 3.2) | + +## **3. Two New Architectural Approaches** + +This document presents **two approaches** to replace the monolithic if/else dispatch system from the original `LiFiDEXAggregator.sol`. Both approaches leverage the Diamond pattern to overcome contract size limitations and provide extensibility. + +**Context**: The original monolithic design required `poolTypes` because in this way we could reduce contract size limit and gas costs. With the Diamond pattern, we can explore better architectures that prioritize user gas costs and deployment simplicity. + +### **3.1 Approach 1: Registry driven dispatch** + +This approach uses a dynamic registry where DEX types (previously named as `poolTypes`) are registered with their corresponding function selectors, enabling runtime dispatch without hardcoded if/else chains. + +### **OLD (LiFiDEXAggregator.sol monolithic if/else):** + +```solidity +uint8 t = stream.readUint8(); +if (t == POOL_TYPE_UNIV2) swapUniV2(...); +else if (t == POOL_TYPE_UNIV3) swapUniV3(...); +else if (t == POOL_TYPE_VELODROME_V2) swapVelodromeV2(...); +else if (t == POOL_TYPE_ALGEBRA) swapAlgebra(...); +// ... 20+ more else-if statements +// Growing chain = increasing gas costs + +``` + +### **NEW (Registry driven dispatch):** + +```solidity +// DRAFT code of CoreRouteFacet contract +// DRAFT code of CoreRouteFacet contract +// DRAFT code of CoreRouteFacet contract + +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibDiamond } from "../Libraries/LibDiamond.sol"; +import { LibAccess } from "../Libraries/LibAccess.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +/// --- Custom Errors --- /// +error UnknownDexType(); +error SwapFailed(); +error DexTypeAlreadyRegistered(); +error CannotRemoveUnknownDexType(); +error MismatchedArrayLength(); + +/// @title CoreRouteFacet +/// @notice Handles DEX type registration and swap dispatching via selector registry +contract CoreRouteFacet { + using EnumerableSet for EnumerableSet.UintSet; + + /// --- Storage Namespace --- /// + bytes32 internal constant NAMESPACE = keccak256("com.lifi.lda.facets.core.route"); + + struct Storage { + mapping(uint8 => bytes4) swapSelectorByDex; + mapping(bytes4 => uint8) dexTypeBySelector; + EnumerableSet.UintSet dexTypes; + } + + function getStorage() internal pure returns (Storage storage s) { + bytes32 namespace = NAMESPACE; + assembly { + s.slot := namespace + } + } + + /// --- Events --- /// + event DexTypeRegistered(uint8 indexed dexType, bytes4 indexed selector); + event DexTypeRemoved(uint8 indexed dexType); + + /// --- Admin Functions --- /// + + function registerDexType(uint8 dexType, bytes4 selector) external { + if (msg.sender != LibDiamond.contractOwner()) { + LibAccess.enforceAccessControl(); + } + + Storage storage s = getStorage(); + if (s.dexTypes.contains(dexType)) revert DexTypeAlreadyRegistered(); + + s.swapSelectorByDex[dexType] = selector; + s.dexTypeBySelector[selector] = dexType; + s.dexTypes.add(dexType); + + emit DexTypeRegistered(dexType, selector); + } + + function batchRegisterDexTypes( + uint8[] calldata dexTypes_, + bytes4[] calldata selectors + ) external { + if (msg.sender != LibDiamond.contractOwner()) { + LibAccess.enforceAccessControl(); + } + + if (dexTypes_.length != selectors.length) { + revert MismatchedArrayLength(); + } + + Storage storage s = getStorage(); + + for (uint256 i = 0; i < dexTypes_.length; ) { + uint8 dexType = dexTypes_[i]; + bytes4 selector = selectors[i]; + s.swapSelectorByDex[dexType] = selector; + s.dexTypeBySelector[selector] = dexType; + s.dexTypes.add(dexType); + emit DexTypeRegistered(dexType, selector); + unchecked { + ++i; + } + } + } + + function removeDexType(uint8 dexType) external { + if (msg.sender != LibDiamond.contractOwner()) { + LibAccess.enforceAccessControl(); + } + + Storage storage s = getStorage(); + bytes4 selector = s.swapSelectorByDex[dexType]; + if (selector == bytes4(0)) revert CannotRemoveUnknownDexType(); + + delete s.swapSelectorByDex[dexType]; + delete s.dexTypeBySelector[selector]; + s.dexTypes.remove(dexType); + + emit DexTypeRemoved(dexType); + } + + function batchRemoveDexTypes(uint8[] calldata dexTypes_) external { + if (msg.sender != LibDiamond.contractOwner()) { + LibAccess.enforceAccessControl(); + } + + Storage storage s = getStorage(); + + for (uint256 i = 0; i < dexTypes_.length; ) { + uint8 dexType = dexTypes_[i]; + bytes4 selector = s.swapSelectorByDex[dexType]; + if (selector != bytes4(0)) { + delete s.swapSelectorByDex[dexType]; + delete s.dexTypeBySelector[selector]; + s.dexTypes.remove(dexType); + emit DexTypeRemoved(dexType); + } + unchecked { + ++i; + } + } + } + + /// --- Internal Logic --- /// + + function dispatchSwap( + uint8 dexType, + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) internal returns (uint256 amountOut) { + Storage storage s = getStorage(); + bytes4 sel = s.swapSelectorByDex[dexType]; + if (sel == 0) revert UnknownDexType(); + + bytes memory data = abi.encodePacked(sel, stream, from, tokenIn, amountIn); + (bool ok, bytes memory ret) = address(this).delegatecall(data); + if (!ok) revert SwapFailed(); + + return abi.decode(ret, (uint256)); + } + + /// --- View Functions --- /// + + function getSwapSelectorByDex(uint8 dexType) external view returns (bytes4) { + return getStorage().swapSelectorByDex[dexType]; + } + + function getDexTypeBySelector(bytes4 selector) external view returns (uint8) { + return getStorage().dexTypeBySelector[selector]; + } + + function getAllDexTypes() external view returns (uint8[] memory result) { + Storage storage s = getStorage(); + uint256 len = s.dexTypes.length(); + result = new uint8[](len); + for (uint256 i = 0; i < len; ++i) { + result[i] = uint8(s.dexTypes.at(i)); + } + } + + function getAllDexTypesWithSelectors() + external + view + returns (uint8[] memory dexTypesOut, bytes4[] memory selectors) + { + Storage storage s = getStorage(); + uint256 len = s.dexTypes.length(); + dexTypesOut = new uint8[](len); + selectors = new bytes4[](len); + + for (uint256 i = 0; i < len; ++i) { + uint8 dexType = uint8(s.dexTypes.at(i)); + dexTypesOut[i] = dexType; + selectors[i] = s.swapSelectorByDex[dexType]; + } + } +} + +``` + +### **Adding New DEX (Approach 1):** + +```solidity +// 1. Deploy FooFacet +// 2. Add diamondCut with FooFacet +// 3. Register new DEX type (optionally) +await coreRouteFacet.registerDexType(DEX_TYPE_FOO, FooFacet.swapFoo.selector); + +``` + +### **NO Backend Changes (Approach 1):** + +poolType (newly named dexTypes) stays the same like for the old version of LDA + +--- + +### **3.2 Approach 2: Selector based dispatch** + +This approach eliminates the registry entirely by having the backend directly specify function selectors in the route data, achieving the lowest possible gas costs and deployment complexity + +### **OLD (LiFiDEXAggregator.sol monolithic if/else):** + +```solidity +uint8 t = stream.readUint8(); +if (t == POOL_TYPE_UNIV2) swapUniV2(...); +else if (t == POOL_TYPE_UNIV3) swapUniV3(...); +else if (t == POOL_TYPE_VELODROME_V2) swapVelodromeV2(...); +else if (t == POOL_TYPE_ALGEBRA) swapAlgebra(...); +// ... 20+ more else-if statements +// Growing chain = increasing gas costs + +``` + +### **NEW (Selector based dispatch):** + +```solidity +function swap( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn +) private { + bytes4 selector = stream.readBytes4(); + + (bool success, bytes memory result) = address(this).call( + abi.encodePacked(selector, stream, from, tokenIn, amountIn) + ); + if (!success) revert SwapFailed(); +} + +``` + +### **Adding New DEX (Approach 2):** + +```solidity +// 1. Deploy FooFacet +// 2. Add diamondCut with FooFacet + +``` + +### **Backend Changes (Approach 2):** + +```tsx +// OLD route encoding +const routeData = encodeRoute({ + command: ProcessUserERC20, + token: tokenIn, + pools: [{ + poolType: POOL_TYPE_UNIV3, + poolAddress: poolAddress, + direction: direction, + recipient: recipient + }] +}); + +// NEW route encoding +const routeData = encodeRoute({ + command: ProcessUserERC20, + token: tokenIn, + pools: [{ + selector: UniV3Facet.swapUniV3.selector, // <== selector instead of poolType + poolAddress: poolAddress, + direction: direction, + recipient: recipient + }] +}); + +``` + +--- + +## **4. Implementation Details** + +### **4.1 Facet Dependencies: Different for Each Approach** + +### **Approach 1 (Registry): Dependencies Exist** + +With the registry approach, compatible DEXs share the same `dexType` (previously `poolType`), creating dependencies: + +```solidity +// UniV3Facet.sol - Main implementation +contract UniV3Facet { + function swapUniV3(...) external returns (uint256) { + // Full UniV3 swap logic implementation + } + + function uniswapV3SwapCallback(...) external { + // UniswapV3-specific callback logic + } +} + +// PancakeV3Facet.sol - Callback-only (DEPENDS on UniV3Facet) +contract PancakeV3Facet { + // NO swapPancakeV3 function - reuses UniV3Facet.swapUniV3() + + function pancakeV3SwapCallback(...) external { + // Forward to UniV3 callback logic + IUniV3Facet(address(this)).uniswapV3SwapCallback( + amount0Delta, + amount1Delta, + data + ); + } +} + +``` + +**Registry Approach Dependencies:** + +- `PancakeV3Facet` **depends on** `UniV3Facet` +- Both use same `DEX_TYPE_UNIV3` dexType +- Deployment order matters: `UniV3Facet` must be deployed first +- PancakeV3 only provides callback wrapper + +**Adding PancakeV3 (Registry Approach):** + +```bash +# 1. Deploy UniV3Facet first (if not already deployed) +# 2. Register: registerDexType(DEX_TYPE_UNIV3, UniV3Facet.swapUniV3.selector) +# 3. Deploy PancakeV3Facet (callback only) +# 4. Add PancakeV3Facet to diamond (for callback) +# Backend uses DEX_TYPE_UNIV3 for both UniV3 and PancakeV3 + +``` + +--- + +### **Approach 2 (Selector): Zero Dependencies** + +With the selector approach, each DEX has its own selector, enabling complete independence: + +```solidity +// UniV3Facet.sol - Complete implementation +contract UniV3Facet { + function swapUniV3(...) external returns (uint256) { + // Full UniV3 swap logic implementation + } + + function uniswapV3SwapCallback(...) external { + // UniswapV3-specific callback logic + } +} + +// PancakeV3Facet.sol - Complete implementation (INDEPENDENT) +contract PancakeV3Facet { + function swapPancakeV3(...) external returns (uint256) { + // Full swap logic (can reuse UniV3 logic via libraries) + return LibUniV3Logic.executeSwap(...); + } + + function pancakeV3SwapCallback(...) external { + // PancakeV3-specific callback logic + } +} + +``` + +**Selector Approach Dependencies:** + +- **Zero dependencies** between facets +- Each facet is completely self-contained +- Deploy in any order +- **Compatible DEXs can share selectors** (e.g., UniV2 forks share `UniswapV2StyleFacet.swapUniV2.selector`) + +**Adding PancakeV3 (Selector Approach):** + +```bash +# 1. Deploy PancakeV3Facet (complete implementation) +# 2. Add PancakeV3Facet to diamond +# Backend immediately uses PancakeV3Facet.swapPancakeV3.selector + +``` + +--- + +## **5. Adding a New DEX** + +### **5.1 Approach 1 (Registry): Onboarding Process** + +### **For Uniswap V3-compatible forks (no new dexType needed):** + +Many Uniswap V3 forks (eg Pancake V3) use exactly the Uniswap V3 swap signature but different callback semantics. You don't need a whole new facet or dexType. Only new smaller facet (without swap function) with callback forward: + +```solidity +/// DRAFT file for PancakeV3Facet.sol +/// DRAFT file for PancakeV3Facet.sol +/// DRAFT file for PancakeV3Facet.sol + +// interface with callback in seperated file +interface IUniV3Facet { + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + +// example for PancakeV3 facet +contract PancakeV3Facet { + function pancakeV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external { + // keep the same logic of handling callback from uniswap v3 + IUniV3Facet(address(this)).uniswapV3SwapCallback( + amount0Delta, + amount1Delta, + data + ); + } +} + +``` + +**Steps:** + +1. DiamondCut in `PancakeV3CallbackFacet.pancakeV3SwapCallback`. +2. No new dexType registration, since it reuses `DEX_TYPE_UNIV3`. + +### **For completely new DEX (new dexType needed):** + +```solidity +/// DRAFT file for FooFacet.sol +/// DRAFT file for FooFacet.sol +/// DRAFT file for FooFacet.sol + +import { InputStream } from "./InputStream.sol"; +import { CallbackManager } from "../Libraries/CallbackManager.sol"; +interface IFooPool { /* … */ } // in seperated file + +contract FooFacet { + using CallbackManager for *; + uint8 public constant DEX_TYPE = 4; // New dexType + + function swapFoo( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256 amountOut) { + CallbackManager.arm(pool); // if pool does callback + /// IFooPool(pool).swap ... + CallbackManager.verify(); // if pool does callback + } + + // callback implemented only if pool does callback + function fooSwapCallback(bytes calldata data) external { + // validate msg.sender==pool… + CallbackManager.clear(); + // transfer funds… + } +} + +``` + +**Steps:** + +1. DiamondCut in `FooFacet.swapFoo` and/or `fooSwapCallback`. +2. Register new type: + + ``` + await coreRouteFacet.registerDexType(DEX_TYPE_FOO, FooFacet.swapFoo.selector); + + ``` + + +--- + +### **5.2 Approach 2 (Selector): Onboarding Process** + +### **For Uniswap V3-compatible forks:** + +Each V3 fork typically gets its own facet due to callback differences: + +```solidity +/// DRAFT file for PancakeV3Facet.sol +/// DRAFT file for PancakeV3Facet.sol +/// DRAFT file for PancakeV3Facet.sol + +contract PancakeV3Facet { + function swapPancakeV3( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256 amountOut) { + // Complete swap implementation (can reuse LibUniV3Logic) + return LibUniV3Logic.executeSwap(...); + } + + function pancakeV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external { + // PancakeV3-specific callback logic + } +} + +``` + +**Steps:** + +1. Deploy `PancakeV3Facet` +2. DiamondCut in `PancakeV3Facet.swapPancakeV3` and `pancakeV3SwapCallback` +3. Backend immediately uses `PancakeV3Facet.swapPancakeV3.selector` + +### **For completely new DEX:** + +```solidity +/// DRAFT file for FooFacet.sol +/// DRAFT file for FooFacet.sol +/// DRAFT file for FooFacet.sol + +contract FooFacet { + function swapFoo( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256 amountOut) { + // Complete swap implementation + } + + function fooSwapCallback(bytes calldata data) external { + // Callback logic if needed + } +} + +``` + +**Steps:** + +1. Deploy `FooFacet` +2. DiamondCut in `FooFacet.swapFoo` and/or `fooSwapCallback` +3. Backend immediately uses `FooFacet.swapFoo.selector` + +### **5.3 Comparison: Adding PancakeV3** + +| **Step** | **Approach 1 (Registry)** | **Approach 2 (Selector)** | +| --- | --- | --- | +| **1. Deploy** | Deploy callback-only facet | Deploy complete facet | +| **2. Registration** | No registration (reuses DEX_TYPE_UNIV3) | No registration needed | +| **3. Backend** | Uses existing DEX_TYPE_UNIV3 | Uses PancakeV3Facet.swapPancakeV3.selector | +| **4. Dependencies** | Requires UniV3Facet deployed first | Zero dependencies | +| **5. Code Reuse** | Interface call to UniV3Facet | Library-based code reuse | + +--- + +## **6. Facet & Callback Dependencies** + +### **6.1 Approach 1 (Registry): Dependencies Exist** + +**Problem:** Some facets require other facets to already be cut & registered due to shared dex types. + +**Example:** `PancakeV3Facet` requires `UniV3Facet` because: + +- Both use `DEX_TYPE_UNIV3` +- PancakeV3 forwards calls to UniV3 swap function +- PancakeV3 only provides callback wrapper + +**How to handle:** + +- Track via `deps:` comment at the top of facet source file: + + ```solidity + // deps: UniV3Facet + contract PancakeV3Facet { + // callback-only implementation + } + + ``` + +- Add facet dependency validation to `DeployFacet.s.sol` +- Ensure dependency order in deployment scripts +- Document dependencies in deployment runbook + +--- + +### **6.2 Approach 2 (Selector): Zero Dependencies** + +**Benefit:** Each facet is completely self-contained with no dependencies. + +**Example:** `PancakeV3Facet` is independent because: + +- Has its own unique selector +- Contains complete swap implementation +- No shared state with other facets +- Can be deployed in any order + +**How to handle:** + +- No dependency tracking needed +- Deploy facets in any order +- Each facet includes complete implementation +- Use libraries for code reuse without dependencies + +--- + +## **7. Testing, Deployment & Migration Workflow** + +### **7.1 Approach 1: Registry based Testing** + +### **Directory Structure** + +``` +test/solidity/Lda/ +├── Facets/ +│ ├── LdaTestBase.t.sol # Registry specific base class +│ ├── CoreRouteFacet.t.sol # Registry and dispatch tests +│ ├── FooFacet.t.sol # Foo dex integration tests + +``` + +### **LdaTestBase.t.sol** + +```solidity +/// DRAFT file for LdaTestBase.t.sol +/// DRAFT file for LdaTestBase.t.sol +/// DRAFT file for LdaTestBase.t.sol + +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { TestBase } from "../utils/TestBase.sol"; +import { LdaDiamond } from "lifi/Lda/LdaDiamond.sol"; +import { CoreRouteFacet } from "lifi/Lda/Facets/CoreRouteFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title LdaTestBase + * @notice Abstract base test contract for LDA facet tests + * @dev Provides Diamond setup + */ +abstract contract LdaTestBase is TestBase { + // Core Diamond components + LdaDiamond internal ldaDiamond; + CoreRouteFacet internal coreRouteFacet; + + // Common events + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + + // Common errors + error UnknownDexType(); + error SwapFailed(); + error InvalidCallData(); + + function setUp() public virtual { + initTestBase(); + vm.label(USER_SENDER, "USER_SENDER"); + setupLdaDiamond(); + } + + function setupLdaDiamond() internal { + ldaDiamond = new LdaDiamond(USER_DIAMOND_OWNER); + coreRouteFacet = new CoreRouteFacet(); + + // Add CoreRouteFacet with registry functions + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + bytes4[] memory selectors = new bytes4[](8); + selectors[0] = CoreRouteFacet.processRoute.selector; + selectors[1] = CoreRouteFacet.registerDexType.selector; + selectors[2] = CoreRouteFacet.removeDexType.selector; + selectors[3] = CoreRouteFacet.batchRegisterDexTypes.selector; + selectors[4] = CoreRouteFacet.batchRemoveDexTypes.selector; + selectors[5] = CoreRouteFacet.getSwapSelectorByDex.selector; + selectors[6] = CoreRouteFacet.getDexTypeBySelector.selector; + selectors[7] = CoreRouteFacet.getAllDexTypes.selector; + + cut[0] = LibDiamond.FacetCut({ + facetAddress: address(coreRouteFacet), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.prank(USER_DIAMOND_OWNER); + LibDiamond.diamondCut(cut, address(0), ""); + + coreRouteFacet = CoreRouteFacet(address(ldaDiamond)); + vm.label(address(ldaDiamond), "LdaDiamond"); + vm.label(address(coreRouteFacet), "CoreRouteFacet"); + } + + function addFacetAndRegister( + address facetAddress, + bytes4 swapSelector, + uint8 dexType + ) internal { + // Add facet to Diamond + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = swapSelector; + + cut[0] = LibDiamond.FacetCut({ + facetAddress: facetAddress, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.prank(USER_DIAMOND_OWNER); + LibDiamond.diamondCut(cut, address(0), ""); + + // Register dexType + vm.prank(USER_DIAMOND_OWNER); + coreRouteFacet.registerDexType(dexType, swapSelector); + } + + function buildRoute( + address tokenIn, + uint256 amountIn, + uint8 dexType, + bytes memory poolData + ) internal pure returns (bytes memory) { + return abi.encodePacked( + uint8(2), // processUserERC20 + tokenIn, + uint8(1), // number of pools + uint16(65535), // full share + dexType, + poolData + ); + } + + // Abstract test functions + function test_CanSwap() public virtual; + function test_CanSwap_FromDiamond() public virtual; + function test_CanSwap_MultiHop() public virtual; + function test_FieldValidation() public virtual; + + // DEX type constants + uint8 internal constant DEX_TYPE_UNIV2 = 0; + uint8 internal constant DEX_TYPE_UNIV3 = 1; + uint8 internal constant DEX_TYPE_VELODROME_V2 = 6; + uint8 internal constant DEX_TYPE_ALGEBRA = 7; + uint8 internal constant DEX_TYPE_IZUMI_V3 = 8; + uint8 internal constant DEX_TYPE_SYNCSWAP = 9; +} + +``` + +### **Example FooFacet.t.sol** + +```solidity +/// DRAFT file for FooFacet.t.sol +/// DRAFT file for FooFacet.t.sol +/// DRAFT file for FooFacet.t.sol + +contract FooFacetTest is LdaTestBase { + FooFacet internal fooFacet; + uint8 internal constant DEX_TYPE_FOO = 10; + + function setUp() public override { + super.setUp(); + fooFacet = new FooFacet(); + addFacetAndRegister(address(fooFacet), fooFacet.swapFoo.selector, DEX_TYPE_FOO); + } + + function test_CanSwap() public override { + bytes memory route = buildRoute( + address(tokenA), + 1000e18, + DEX_TYPE_FOO, + abi.encodePacked(address(pool), uint8(1), address(USER_RECEIVER)) + ); + + vm.prank(USER_SENDER); + uint256 amountOut = coreRouteFacet.processRoute( + address(tokenA), 1000e18, address(tokenB), 950e18, USER_RECEIVER, route + ); + + assertGt(amountOut, 950e18); + } + + function test_CanSwap_FromDiamond() public override { + // TODO: + } + + function test_CanSwap_MultiHop() public override { + // TODO: + } + + function test_FieldValidation() public override { + // TODO: + } +} + +``` + +### **Deployment Scripts** + +- `script/deploy/lda/facets/DeployXFacet.s.sol` +- `scriptMaster.sh` — calls `registerDexType(...)` after facet deployment + +### **Migration Path** + +- `script/lda/migrateDexTypes.ts` — registry migration + +--- + +### **7.2 Approach 2: Selector based Testing** + +### **Directory Structure** + +``` +test/solidity/Lda/ +├── Facets/ +│ ├── LdaTestBase.t.sol # Selector specific base class +│ ├── CoreRouteFacet.t.sol # Basic routing tests +│ ├── FooFacet.t.sol # Foo dex integration tests + +``` + +### **LdaTestBase.t.sol** + +```solidity +/// DRAFT file for LdaTestBase.t.sol +/// DRAFT file for LdaTestBase.t.sol +/// DRAFT file for LdaTestBase.t.sol + +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { TestBase } from "../utils/TestBase.sol"; +import { LdaDiamond } from "lifi/Lda/LdaDiamond.sol"; +import { CoreRouteFacet } from "lifi/Lda/Facets/CoreRouteFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title LdaTestBase + * @notice Abstract base test contract + * @dev Provides Diamond setup + */ +abstract contract LdaTestBase is TestBase { + // Core Diamond components + LdaDiamond internal ldaDiamond; + CoreRouteFacet internal coreRouteFacet; + + // Common events + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + + // Common errors + error SwapFailed(); + error InvalidCallData(); + + function setUp() public virtual { + initTestBase(); + vm.label(USER_SENDER, "USER_SENDER"); + setupLdaDiamond(); + } + + function setupLdaDiamond() internal { + ldaDiamond = new LdaDiamond(USER_DIAMOND_OWNER); + coreRouteFacet = new CoreRouteFacet(); + + // Add CoreRouteFacet with only processRoute (no registry functions) + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = CoreRouteFacet.processRoute.selector; + + cut[0] = LibDiamond.FacetCut({ + facetAddress: address(coreRouteFacet), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.prank(USER_DIAMOND_OWNER); + LibDiamond.diamondCut(cut, address(0), ""); + + coreRouteFacet = CoreRouteFacet(address(ldaDiamond)); + vm.label(address(ldaDiamond), "LdaDiamond"); + vm.label(address(coreRouteFacet), "CoreRouteFacet"); + } + + function addFacet(address facetAddress, bytes4 swapSelector) internal { + // Simple facet addition - no registration needed + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = swapSelector; + + cut[0] = LibDiamond.FacetCut({ + facetAddress: facetAddress, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectors + }); + + vm.prank(USER_DIAMOND_OWNER); + LibDiamond.diamondCut(cut, address(0), ""); + } + + function buildRoute( + address tokenIn, + uint256 amountIn, + bytes4 selector, + bytes memory poolData + ) internal pure returns (bytes memory) { + return abi.encodePacked( + uint8(2), // processUserERC20 + tokenIn, + uint8(1), // number of pools + uint16(65535), // full share + selector, + poolData + ); + } + + // Abstract test functions + function test_CanSwap() public virtual; + function test_CanSwap_FromDiamond() public virtual; + function test_CanSwap_MultiHop() public virtual; + function test_FieldValidation() public virtual; + + // Direction constants + uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + uint8 internal constant DIRECTION_TOKEN1_TO_TOKEN0 = 0; + uint16 internal constant FULL_SHARE = 65535; +} + +``` + +### **Example FooFacet.t.sol** + +```solidity +/// DRAFT file for FooFacet.t.sol +/// DRAFT file for FooFacet.t.sol +/// DRAFT file for FooFacet.t.sol + +contract FooFacetTest is LdaTestBase { + FooFacet internal fooFacet; + + function setUp() public override { + super.setUp(); + fooFacet = new FooFacet(); + addFacet(address(fooFacet), fooFacet.swapFoo.selector); + } + + function test_CanSwap() public override { + bytes memory route = buildSelectorRoute( + address(tokenA), + 1000e18, + fooFacet.swapFoo.selector, + abi.encodePacked(address(pool), uint8(1), address(USER_RECEIVER)) + ); + + vm.prank(USER_SENDER); + uint256 amountOut = coreRouteFacet.processRoute( + address(tokenA), 1000e18, address(tokenB), 950e18, USER_RECEIVER, route + ); + + assertGt(amountOut, 950e18); + } + + function test_CanSwap_FromDiamond() public override { + } + + function test_CanSwap_MultiHop() public override { + } + + function test_FieldValidation() public override { + } +} + +``` + +### **Deployment Scripts** + +- `script/deploy/lda/facets/DeployXFacet.s.sol` +- `scriptMaster.sh` — simple Diamond cut, no registration needed + +### **Migration Path** + +- Backend needs to map every dex to correct facet swap function selector bytes + +--- + +### **7.3 Migration Requirements** + +**Registry Approach (Approach 1):** + +- Smart contract: Deploy facets + register dexTypes +- Backend: No changes needed (keeps existing dexType system) + +**Selector Approach (Approach 2):** + +- Smart contract: Deploy facets (no registration) +- Backend: Update route encoding to use selectors instead of dexTypes + +--- + +## **8. Detailed Approach Analysis** + +### **8.1 Backend Communication Comparison** + +**Current Complexity (OLD/Approach 1):** + +- Backend/Smart contract teams need to understand `poolType/dexType` mappings +- Complex enum/mapping management +- Multiple DEXs share same `poolType/dexType` identifier +- Requires coordination for `poolType/dexType` assignments + +**Simplified Communication (Approach 2):** + +- **Simpler mapping**: Compatible DEXs share selectors, callback-requiring DEXs get unique selectors +- **No enum management**: Direct function selector usage +- **Self-documenting**: `PancakeV3Facet.swapPancakeV3.selector` is clear +- **Reduced coordination**: Deploy facet → get selector → use immediately + +Regarding communication with the backend team, **Approach 2 significantly simplifies coordination**. Currently, we need to communicate which `dexType` each DEX should use, requiring mapping management and potential conflicts. With the selector approach, communication becomes **clearer and more explicit**: compatible DEXs share selectors while callback-requiring DEXs get unique selectors, reducing the need for complex enum management and coordination overhead. + +### **8.2 DEX Grouping by Compatibility** + +**Selector sharing patterns:** + +- **UniV2-compatible DEXs**: All share `UniswapV2StyleFacet.swapUniV2.selector` + - UniswapV2, SushiSwap, PancakeV2, TraderJoe V1, etc. + - Only pool address differs in route data +- **UniV3-compatible DEXs**: Each gets unique selector due to callback differences + - UniV3 → `UniV3Facet.swapUniV3.selector` + - PancakeV3 → `PancakeV3Facet.swapPancakeV3.selector` + - RamsesV2 → `RamsesV2Facet.swapRamsesV2.selector` + - Different callback function names require separate facets +- **Unique protocol DEXs**: Each gets its own selector + - Curve, Balancer, 1inch, etc. + - Different swap interfaces and callback patterns + +## **9. Final Approach Comparison Matrix** + +| **Aspect** | **OLD (Monolithic)** | **Approach 1 (Registry)** | **Approach 2 (Selector)** | **Notes** | +| --- | --- | --- | --- | --- | +| **Backend Changes** | ✅ None (current system) | ✅ None (keeps dexType) | ❌ Requires route encoding update | One-time migration to selector based routing | +| **User Gas Cost** | ~50-200 gas (growing) | ~2,100 gas (constant) | ~20 gas (constant) | | +| **DEX Integration** | ❌ Update CoreRouteFacet | ✅ Register dexType | ✅ Deploy and use | | +| **Backend Changes** | N/A | ✅ None (keep dexType system) | ✅ Minor (selector mapping) | | +| **Scalability** | ❌ Limited by contract size | ✅ 255 DEXs (uint8) | ✅ 4 billion DEXs (bytes4) | | +| **Deployment Complexity** | ❌ High | ❌ Medium (dependencies) | ✅ Minimal (independent) | | +| **Deployment Order** | N/A | ❌ Must follow dependency order | ✅ Any order | | +| **Facet Dependencies** | N/A | ❌ Hard dependencies exist | ✅ Zero dependencies | | +| **Single Point of Failure** | ❌ Monolithic contract | ❌ Registry corruption | ✅ No central registry | | +| **Gas Predictability** | ❌ Increases with DEXs | ✅ Constant | ✅ Constant | | +| **Code Reuse** | N/A | ✅ High (shared swap functions) | ✅ Medium (via libraries) | | +| **Test Setup Complexity** | ❌ Massive test suite | ❌ Higher (registry setup) | ✅ Lower (direct facet addition) | | +| **Test Isolation** | ❌ Monolithic coupling | ❌ Medium (shared registry) | ✅ High (independent facets) | | +| **Maintenance** | ❌ Fragile monolith | ❌ Fragile interdependencies | ✅ Clear separation | | +| **Upgrade Safety** | ❌ Monolithic failure | ❌ Cascade failures possible | ✅ Isolated failures | | +| **Bytecode Size** | ❌ Massive monolith | ✅ Smaller (less duplication) | ❌ Larger (each facet complete) | with selector approach its larger but still great fit for facet | + +> Note: +> +> +> Approach 1 (Registry-based) was my **initial approach**, which is why I've kept it in the documentation — just to let you know it's still a valid and fully working option. It allows us to onboard new DEXs **without requiring any changes on the backend**, since the backend can continue using the existing `dexType` field. That said, we'd need to ask the LDA backend team whether they're capable and willing to switch to using function selectors (required by Approach 2). +> +> Personally, I recommend Approach 2 going forward. It reduces backend coordination, scales better, and is more explicit and maintainable. You simply deploy a new facet, use its selector directly, and avoid all shared-state registry management or dependency tracking. +> + +--- + +# **Part II: Development Standards & Tooling (common for both approaches)** + +## **10. Callback Handling: From `lastCalledPool` → `CallbackManager`** + +OLD: + +```solidity +lastCalledPool = pool; // in swap function +... +require(msg.sender == lastCalledPool); // in callback function + +``` + +**NEW:** + +We now use the **`CallbackManager` library**, which stores the expected sender in diamond-safe storage. + +**Step-by-step usage:** + +1. **Arm** the callback guard in the swap function (before external call) +2. **Verify** or use the `onlyExpectedCallback` modifier at the beginning of the callback +3. **Clear** the state after validation (modifier handles it automatically) + +**`CallbackManager.sol`** (in `/Libraries`): + +```solidity + +// DRAFT file for LibCallbackManager.sol +// DRAFT file for LibCallbackManager.sol +// DRAFT file for LibCallbackManager.sol + +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +error UnexpectedCallbackSender(address actual, address expected); + +library LibCallbackManager { + bytes32 internal constant NAMESPACE = keccak256("com.lifi.lda.callbackmanager"); + + struct Data { + address expected; + } + + function data() internal pure returns (Data storage d) { + bytes32 p = NAMESPACE; + assembly { + d.slot := p + } + } + + /// @notice Arm the guard with expected pool + function arm(address expectedCallbackSender) internal { + data().expected = expectedCallbackSender; + } + + /// @notice Clear the guard (called inside the callback) + function clear() internal { + data().expected = address(0); + } + + /// @notice Check that callback comes from expected address + function verifyCallbackSender() internal view { + address expected = data().expected; + if (msg.sender != expected) { + revert UnexpectedCallbackSender(msg.sender, expected); + } + } + + /// @dev Wraps a callback with verify + clear. To use with `using CallbackManager for *`. + modifier onlyExpectedCallback() { + verifyCallbackSender(); + _; + clear(); + } +} + +``` + +Example usage: + +```solidity +// DRAFT file for FooFacet.sol +// DRAFT file for FooFacet.sol +// DRAFT file for FooFacet.sol + +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; + +contract FooFacet { + using LibCallbackManager for *; + + function swapFoo( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256 amountOut) { + address pool = decodePoolFromStream(stream); + + // arm callback guard + LibCallbackManager.arm(pool); + + // call to external pool + IFooPool(pool).swap(tokenIn, amountIn, ...); + + // actual result will be handled via callback + } + + /// @notice Callback triggered by FooPool + function fooSwapCallback(bytes calldata data) external LibCallbackManager.onlyExpectedCallback { + // sender verified + cleared automatically + + // Perform balance check, token transfer, emit event, etc. + } +} + +``` + +## **11. Release Checklist** + +Release checklist same based on [New Facet Contract Checklist](https://www.notion.so/New-Facet-Contract-Checklist-157f0ff14ac78095a2b8f999d655622e?pvs=21) + +## **12. Conventions & Cleanup** + +- all `DEX_TYPE` constants in a shared `DexTypes.sol` (Registry approach) +- use `LibAsset` for transfers +- update [`conventions.md`](http://conventions.md/) accordingly + +## **13. Code Generation** + +We use `plop` to scaffold new facet modules with all required boilerplate: + +```bash +bun run plop facet +``` + +You will be prompted with: + +``` +? What kind of facet do you want to generate? +> [1] Main Diamond (e.g. LiFiDiamond) +> [2] LDA Diamond (e.g. LDALiFiDiamond) +? Which approach are you using? +> [1] Registry-based (Approach 1) +> [2] Selector-based (Approach 2) + +``` + +### **Plop Output:** + +- **Path:** `src/Lda/Facets/FooFacet.sol` +- **Test:** `test/solidity/Lda/Facets/FooFacet.t.sol` (extends `LdaTestBase`) +- **Deploy script:** `script/deploy/lda/facets/DeployFooFacet.s.sol` +- **Update script:** `script/deploy/lda/facets/UpdateFooFacet.s.sol` +- **Docs:** `docs/lda/FooFacet.md` + +**Questions:**  + +Can we deprecate bento? \ No newline at end of file diff --git a/src/Libraries/LibCallbackManager.sol b/src/Libraries/LibCallbackManager.sol new file mode 100644 index 000000000..90794eed6 --- /dev/null +++ b/src/Libraries/LibCallbackManager.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: LGPL-3.0-only +/// @custom:version 1.0.0 +pragma solidity ^0.8.17; + +/// @title Callback Manager Library +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for managing callback validation in diamond-safe storage +library LibCallbackManager { + /// Types /// + bytes32 internal constant NAMESPACE = keccak256("com.lifi.lda.callbackmanager"); + + /// Storage /// + struct CallbackStorage { + address expected; + } + + /// Errors /// + error UnexpectedCallbackSender(address actual, address expected); + + /// @dev Fetch local storage + function callbackStorage() + internal + pure + returns (CallbackStorage storage cbStor) + { + bytes32 position = NAMESPACE; + // solhint-disable-next-line no-inline-assembly + assembly { + cbStor.slot := position + } + } + + /// @notice Arm the guard with expected pool + /// @param expectedCallbackSender The address expected to call the callback + function arm(address expectedCallbackSender) internal { + callbackStorage().expected = expectedCallbackSender; + } + + /// @notice Clear the guard (called inside the callback) + function clear() internal { + callbackStorage().expected = address(0); + } + + /// @notice Check that callback comes from expected address + function verifyCallbackSender() internal view { + address expected = callbackStorage().expected; + if (msg.sender != expected) { + revert UnexpectedCallbackSender(msg.sender, expected); + } + } + + /// @dev Modifier wrapper for callback verification and cleanup + modifier onlyExpectedCallback() { + verifyCallbackSender(); + _; + clear(); + } +} \ No newline at end of file diff --git a/src/Libraries/LibInputStream.sol b/src/Libraries/LibInputStream.sol new file mode 100644 index 000000000..7235e0743 --- /dev/null +++ b/src/Libraries/LibInputStream.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: LGPL-3.0-only +/// @custom:version 1.0.0 +pragma solidity ^0.8.17; + +/// @title InputStream Library +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for reading data from packed byte streams with support for selectors +library LibInputStream { + /** @notice Creates stream from data + * @param data data + */ + function createStream( + bytes memory data + ) internal pure returns (uint256 stream) { + assembly { + stream := mload(0x40) + mstore(0x40, add(stream, 64)) + mstore(stream, data) + let length := mload(data) + mstore(add(stream, 32), add(data, length)) + } + } + + /** @notice Checks if stream is not empty + * @param stream stream + */ + function isNotEmpty(uint256 stream) internal pure returns (bool) { + uint256 pos; + uint256 finish; + assembly { + pos := mload(stream) + finish := mload(add(stream, 32)) + } + return pos < finish; + } + + /** @notice Reads uint8 from the stream + * @param stream stream + */ + function readUint8(uint256 stream) internal pure returns (uint8 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 1) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads uint16 from the stream + * @param stream stream + */ + function readUint16(uint256 stream) internal pure returns (uint16 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 2) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads uint24 from the stream + * @param stream stream + */ + function readUint24(uint256 stream) internal pure returns (uint24 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 3) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads uint32 from the stream + * @param stream stream + */ + function readUint32(uint256 stream) internal pure returns (uint32 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 4) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads bytes4 from the stream (for function selectors) + * @param stream stream + */ + function readBytes4(uint256 stream) internal pure returns (bytes4 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 4) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads uint256 from the stream + * @param stream stream + */ + function readUint(uint256 stream) internal pure returns (uint256 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 32) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads bytes32 from the stream + * @param stream stream + */ + function readBytes32(uint256 stream) internal pure returns (bytes32 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 32) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads address from the stream + * @param stream stream + */ + function readAddress(uint256 stream) internal pure returns (address res) { + assembly { + let pos := mload(stream) + pos := add(pos, 20) + res := mload(pos) + mstore(stream, pos) + } + } + + /** @notice Reads bytes from the stream + * @param stream stream + */ + function readBytes( + uint256 stream + ) internal pure returns (bytes memory res) { + assembly { + let pos := mload(stream) + res := add(pos, 32) + let length := mload(res) + mstore(stream, add(res, length)) + } + } +} \ No newline at end of file diff --git a/src/Libraries/LibUniV3Logic.sol b/src/Libraries/LibUniV3Logic.sol new file mode 100644 index 000000000..767d12bd0 --- /dev/null +++ b/src/Libraries/LibUniV3Logic.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: LGPL-3.0-only +/// @custom:version 1.0.0 +pragma solidity ^0.8.17; + +import { LibInputStream } from "./LibInputStream.sol"; +import { LibCallbackManager } from "./LibCallbackManager.sol"; +import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { InvalidCallData } from "../Errors/GenericErrors.sol"; + +interface IUniV3StylePool { + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); +} + +/// @title UniV3 Logic Library +/// @author LI.FI (https://li.fi) +/// @notice Shared logic for UniV3-style DEX protocols +library LibUniV3Logic { + using SafeERC20 for IERC20; + using LibInputStream for uint256; + + /// Constants /// + address internal constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// Errors /// + error UniV3SwapUnexpected(); + + /// @notice Executes a generic UniV3-style swap + /// @param stream The input stream containing swap parameters + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens + function executeSwap( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) internal returns (uint256 amountOut) { + address pool = stream.readAddress(); + bool direction = stream.readUint8() > 0; + address recipient = stream.readAddress(); + + if ( + pool == address(0) || + pool == IMPOSSIBLE_POOL_ADDRESS || + recipient == address(0) + ) { + revert InvalidCallData(); + } + + // Transfer tokens if needed + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); + } + + // Arm callback protection + LibCallbackManager.arm(pool); + + // Execute swap + IUniV3StylePool(pool).swap( + recipient, + direction, + int256(amountIn), + direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + abi.encode(tokenIn) + ); + + // Verify callback was called (arm should be cleared by callback) + LibCallbackManager.CallbackStorage storage cbStor = LibCallbackManager.callbackStorage(); + if (cbStor.expected != address(0)) { + revert UniV3SwapUnexpected(); + } + } + + /// @notice Handles a generic UniV3-style callback + /// @param amount0Delta The amount of token0 owed to pool + /// @param amount1Delta The amount of token1 owed to pool + /// @param data The callback data containing tokenIn address + function handleCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) internal { + int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta; + if (amount <= 0) { + return; // Nothing to pay + } + + address tokenIn = abi.decode(data, (address)); + IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount)); + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol new file mode 100644 index 000000000..b392875be --- /dev/null +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +// import { LibDiamond } from "../Libraries/LibDiamond.sol"; +// import { LibAccess } from "../Libraries/LibAccess.sol"; +// import { LibAsset } from "../Libraries/LibAsset.sol"; +// import { LibInputStream } from "../Libraries/LibInputStream.sol"; +// import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +// import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; +// import { InvalidCallData, InvalidAmount } from "../Errors/GenericErrors.sol"; + +// /// @title Core Route Facet +// /// @author LI.FI (https://li.fi) +// /// @notice Handles route processing and selector-based swap dispatching for LDA 2.0 +// /// @custom:version 2.0.0 +// contract CoreRouteFacet is ReentrancyGuard { +// using SafeERC20 for IERC20; +// using SafeERC20 for IERC20Permit; +// using LibInputStream for uint256; + +// /// Constants /// +// address internal constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; +// address internal constant INTERNAL_INPUT_SOURCE = address(0); + +// /// Events /// +// event Route( +// address indexed from, +// address to, +// address indexed tokenIn, +// address indexed tokenOut, +// uint256 amountIn, +// uint256 amountOutMin, +// uint256 amountOut +// ); + +// /// Errors /// +// error MinimalOutputBalanceViolation(uint256 amountOut); +// error MinimalInputBalanceViolation(uint256 available, uint256 required); +// error UnknownCommandCode(); +// error SwapFailed(); + +// /// External Methods /// + +// /// @notice Processes a route for swapping tokens using selector-based dispatch +// /// @param tokenIn Token to swap from +// /// @param amountIn Amount of tokenIn to swap +// /// @param tokenOut Token to swap to +// /// @param amountOutMin Minimum amount of tokenOut expected +// /// @param to Recipient of the final tokens +// /// @param route Encoded route data containing swap instructions +// function processRoute( +// address tokenIn, +// uint256 amountIn, +// address tokenOut, +// uint256 amountOutMin, +// address to, +// bytes calldata route +// ) external payable nonReentrant returns (uint256 amountOut) { +// return _processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route); +// } + +// /// Internal Methods /// + +// /// @notice Internal route processing logic +// function _processRouteInternal( +// address tokenIn, +// uint256 amountIn, +// address tokenOut, +// uint256 amountOutMin, +// address to, +// bytes calldata route +// ) private returns (uint256 amountOut) { +// uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS +// ? 0 +// : IERC20(tokenIn).balanceOf(msg.sender); +// uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS +// ? address(to).balance +// : IERC20(tokenOut).balanceOf(to); + +// uint256 realAmountIn = amountIn; +// { +// uint256 step = 0; +// uint256 stream = LibInputStream.createStream(route); +// while (stream.isNotEmpty()) { +// uint8 commandCode = stream.readUint8(); +// if (commandCode == 1) { +// uint256 usedAmount = _processMyERC20(stream); +// if (step == 0) realAmountIn = usedAmount; +// } else if (commandCode == 2) { +// _processUserERC20(stream, amountIn); +// } else if (commandCode == 3) { +// uint256 usedAmount = _processNative(stream); +// if (step == 0) realAmountIn = usedAmount; +// } else if (commandCode == 4) { +// _processOnePool(stream); +// } else if (commandCode == 6) { +// _applyPermit(tokenIn, stream); +// } else { +// revert UnknownCommandCode(); +// } +// ++step; +// } +// } + +// uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS +// ? 0 +// : IERC20(tokenIn).balanceOf(msg.sender); +// if (balanceInFinal + amountIn < balanceInInitial) { +// revert MinimalInputBalanceViolation( +// balanceInFinal + amountIn, +// balanceInInitial +// ); +// } + +// uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS +// ? address(to).balance +// : IERC20(tokenOut).balanceOf(to); +// if (balanceOutFinal < balanceOutInitial + amountOutMin) { +// revert MinimalOutputBalanceViolation( +// balanceOutFinal - balanceOutInitial +// ); +// } + +// amountOut = balanceOutFinal - balanceOutInitial; + +// emit Route( +// msg.sender, +// to, +// tokenIn, +// tokenOut, +// realAmountIn, +// amountOutMin, +// amountOut +// ); +// } + +// /// @notice Applies ERC-2612 permit +// function _applyPermit(address tokenIn, uint256 stream) private { +// uint256 value = stream.readUint(); +// uint256 deadline = stream.readUint(); +// uint8 v = stream.readUint8(); +// bytes32 r = stream.readBytes32(); +// bytes32 s = stream.readBytes32(); +// IERC20Permit(tokenIn).safePermit( +// msg.sender, +// address(this), +// value, +// deadline, +// v, +// r, +// s +// ); +// } + +// /// @notice Processes native coin +// function _processNative(uint256 stream) private returns (uint256 amountTotal) { +// amountTotal = address(this).balance; +// _distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal); +// } + +// /// @notice Processes ERC20 token from this contract balance +// function _processMyERC20(uint256 stream) private returns (uint256 amountTotal) { +// address token = stream.readAddress(); +// amountTotal = IERC20(token).balanceOf(address(this)); +// unchecked { +// if (amountTotal > 0) amountTotal -= 1; // slot undrain protection +// } +// _distributeAndSwap(stream, address(this), token, amountTotal); +// } + +// /// @notice Processes ERC20 token from msg.sender balance +// function _processUserERC20(uint256 stream, uint256 amountTotal) private { +// address token = stream.readAddress(); +// _distributeAndSwap(stream, msg.sender, token, amountTotal); +// } + +// /// @notice Processes single pool (tokens already at pool) +// function _processOnePool(uint256 stream) private { +// address token = stream.readAddress(); +// _dispatchSwap(stream, INTERNAL_INPUT_SOURCE, token, 0); +// } + +// /// @notice Distributes amount to pools and calls swap for each +// function _distributeAndSwap( +// uint256 stream, +// address from, +// address tokenIn, +// uint256 amountTotal +// ) private { +// uint8 num = stream.readUint8(); +// unchecked { +// for (uint256 i = 0; i < num; ++i) { +// uint16 share = stream.readUint16(); +// uint256 amount = (amountTotal * share) / type(uint16).max; +// amountTotal -= amount; +// _dispatchSwap(stream, from, tokenIn, amount); +// } +// } +// } + +// /// @notice Dispatches swap using selector-based approach +// /// @dev This is the core of the selector-based dispatch system +// function _dispatchSwap( +// uint256 stream, +// address from, +// address tokenIn, +// uint256 amountIn +// ) private { +// // Read the function selector from the stream +// bytes4 selector = stream.readBytes4(); + +// // Get the facet address for this selector from diamond storage +// LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); +// address facet = ds.selectorToFacetAndPosition[selector].facetAddress; + +// if (facet == address(0)) { +// revert SwapFailed(); +// } + +// // Prepare calldata: selector + remaining stream + additional parameters +// bytes memory data = abi.encodePacked(selector, stream, from, tokenIn, amountIn); + +// // Execute the swap via delegatecall to the facet +// (bool success, bytes memory result) = facet.delegatecall(data); +// if (!success) { +// revert SwapFailed(); +// } + +// // Note: Individual facets can return amounts if needed, but for now we rely on balance checks +// } +// } \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol new file mode 100644 index 000000000..bc2039838 --- /dev/null +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +interface IUniswapV2Pair { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; +} + +/// @title UniV2Style Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles UniswapV2-style swaps (UniV2, SushiSwap, PancakeV2, etc.) +/// @custom:version 2.0.0 +contract UniV2StyleFacet { + using SafeERC20 for IERC20; + using LibInputStream for uint256; + + /// Constants /// + address internal constant INTERNAL_INPUT_SOURCE = address(0); + uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + + /// Errors /// + error WrongPoolReserves(); + + /// @notice Executes a UniswapV2-style swap + /// @param stream The input stream containing swap parameters + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens + function swapUniV2( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256 amountOut) { + address pool = stream.readAddress(); + uint8 direction = stream.readUint8(); + address to = stream.readAddress(); + uint24 fee = stream.readUint24(); // pool fee in 1/1_000_000 + + if (pool == address(0) || to == address(0)) { + revert InvalidCallData(); + } + + // Transfer tokens to pool if needed + if (from == address(this)) { + IERC20(tokenIn).safeTransfer(pool, amountIn); + } else if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom(msg.sender, pool, amountIn); + } + + // Get reserves and calculate output + (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves(); + if (r0 == 0 || r1 == 0) revert WrongPoolReserves(); + + (uint256 reserveIn, uint256 reserveOut) = direction == DIRECTION_TOKEN0_TO_TOKEN1 + ? (r0, r1) + : (r1, r0); + + // Calculate actual input amount from pool balance + amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; + + uint256 amountInWithFee = amountIn * (1_000_000 - fee); + amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1_000_000 + amountInWithFee); + + (uint256 amount0Out, uint256 amount1Out) = direction == DIRECTION_TOKEN0_TO_TOKEN1 + ? (uint256(0), amountOut) + : (amountOut, uint256(0)); + + IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0)); + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol new file mode 100644 index 000000000..4566feefa --- /dev/null +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; + +/// @title UniV3 Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles Uniswap V3 swaps with callback management +/// @custom:version 1.0.0 +contract UniV3StyleFacet { + using LibInputStream for uint256; + + /// @notice Executes a UniswapV3 swap + /// @param stream The input stream containing swap parameters + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens + function swapUniV3( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256 amountOut) { + return LibUniV3Logic.executeSwap(stream, from, tokenIn, amountIn); + } + + /// @notice Callback for UniswapV3 swaps + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/LdaDiamond.sol b/src/Periphery/Lda/LdaDiamond.sol new file mode 100644 index 000000000..961a0b3cb --- /dev/null +++ b/src/Periphery/Lda/LdaDiamond.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; +// solhint-disable-next-line no-unused-import +import { LibUtil } from "lifi/Libraries/LibUtil.sol"; + +/// @title LDA Diamond +/// @author LI.FI (https://li.fi) +/// @notice EIP-2535 Diamond Proxy Contract for LiFi DEX Aggregator using selector-based dispatch. +/// @custom:version 2.0.0 +contract LdaDiamond { + constructor(address _contractOwner, address _diamondCutFacet) payable { + LibDiamond.setContractOwner(_contractOwner); + + // Add the diamondCut external function from the diamondCutFacet + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = IDiamondCut.diamondCut.selector; + cut[0] = LibDiamond.FacetCut({ + facetAddress: _diamondCutFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + LibDiamond.diamondCut(cut, address(0), ""); + } + + // Find facet for function that is called and execute the + // function if a facet is found and return any value. + // solhint-disable-next-line no-complex-fallback + fallback() external payable { + LibDiamond.DiamondStorage storage ds; + bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; + + // get diamond storage + // solhint-disable-next-line no-inline-assembly + assembly { + ds.slot := position + } + + // get facet from function selector + address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; + + if (facet == address(0)) { + revert LibDiamond.FunctionDoesNotExist(); + } + + // Execute external function from facet using delegatecall and return any value. + // solhint-disable-next-line no-inline-assembly + assembly { + // copy function selector and any arguments + calldatacopy(0, 0, calldatasize()) + // execute function call using the facet + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + // get any return value + returndatacopy(0, 0, returndatasize()) + // return any return value or error back to the caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + // Able to receive ether + // solhint-disable-next-line no-empty-blocks + receive() external payable {} +} \ No newline at end of file diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol new file mode 100644 index 000000000..e6818dcb2 --- /dev/null +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -0,0 +1,3357 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; +import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; +import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; +import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; +import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; +import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; +import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; +import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; +import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; +import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; +import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { TestBase } from "../../utils/TestBase.sol"; +import { TestToken as ERC20 } from "../../utils/TestToken.sol"; +import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; + +// Command codes for route processing +enum CommandType { + None, // 0 - not used + ProcessMyERC20, // 1 - processMyERC20 + ProcessUserERC20, // 2 - processUserERC20 + ProcessNative, // 3 - processNative + ProcessOnePool, // 4 - processOnePool + ProcessInsideBento, // 5 - processInsideBento + ApplyPermit // 6 - applyPermit +} + +// Pool type identifiers +enum PoolType { + UniV2, // 0 + UniV3, // 1 + WrapNative, // 2 + BentoBridge, // 3 + Trident, // 4 + Curve, // 5 + VelodromeV2, // 6 + Algebra, // 7 + iZiSwap, // 8 + SyncSwapV2 // 9 +} + +// Direction constants +enum SwapDirection { + Token1ToToken0, // 0 + Token0ToToken1 // 1 +} + +// Callback constants +enum CallbackStatus { + Disabled, // 0 + Enabled // 1 +} + +// Other constants +uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps + +contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + function hook( + address sender, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external { + emit HookCalled(sender, amount0, amount1, data); + } +} +/** + * @title BaseDexFacetTest + * @notice Base test contract with common functionality and abstractions for DEX-specific tests + */ +abstract contract BaseDexFacetTest is TestBase { + using SafeERC20 for IERC20; + + // Common variables + LiFiDEXAggregator internal liFiDEXAggregator; + address[] internal privileged; + + // Common events and errors + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + error WrongPoolReserves(); + error PoolDoesNotExist(); + + // helper function to initialize the aggregator + function _initializeDexAggregator(address owner) internal { + privileged = new address[](1); + privileged[0] = owner; + + liFiDEXAggregator = new LiFiDEXAggregator( + address(0xCAFE), + privileged, + owner + ); + vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); + } + + // Setup function for Apechain tests + function setupApechain() internal { + customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; + customBlockNumberForForking = 12912470; + fork(); + + _initializeDexAggregator(address(USER_DIAMOND_OWNER)); + } + + function setupHyperEVM() internal { + customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; + customBlockNumberForForking = 4433562; + fork(); + + _initializeDexAggregator(USER_DIAMOND_OWNER); + } + + + // ============================ Abstract DEX Tests ============================ + /** + * @notice Abstract test for basic token swapping functionality + * Each DEX implementation should override this + */ + function test_CanSwap() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap: Not implemented"); + } + + /** + * @notice Abstract test for swapping tokens from the DEX aggregator + * Each DEX implementation should override this + */ + function test_CanSwap_FromDexAggregator() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_FromDexAggregator: Not implemented"); + } + + /** + * @notice Abstract test for multi-hop swapping + * Each DEX implementation should override this + */ + function test_CanSwap_MultiHop() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_MultiHop: Not implemented"); + } +} + +// /** +// * @title VelodromeV2 tests +// * @notice Tests specific to Velodrome V2 pool type +// */ +// contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorTest { +// // ==================== Velodrome V2 specific variables ==================== +// IVelodromeV2Router internal constant VELODROME_V2_ROUTER = +// IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router +// address internal constant VELODROME_V2_FACTORY_REGISTRY = +// 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; +// IERC20 internal constant STG_TOKEN = +// IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); +// IERC20 internal constant USDC_E_TOKEN = +// IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); + +// MockVelodromeV2FlashLoanCallbackReceiver +// internal mockFlashloanCallbackReceiver; + +// // Velodrome V2 structs +// struct VelodromeV2SwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// bool stable; +// SwapDirection direction; +// bool callback; +// } + +// struct MultiHopTestParams { +// address tokenIn; +// address tokenMid; +// address tokenOut; +// address pool1; +// address pool2; +// uint256[] amounts1; +// uint256[] amounts2; +// uint256 pool1Fee; +// uint256 pool2Fee; +// } + +// struct ReserveState { +// uint256 reserve0Pool1; +// uint256 reserve1Pool1; +// uint256 reserve0Pool2; +// uint256 reserve1Pool2; +// } + +// // Setup function for Optimism tests +// function setupOptimism() internal { +// customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; +// customBlockNumberForForking = 133999121; +// initTestBase(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// function setUp() public override { +// setupOptimism(); +// } + +// // // ============================ Velodrome V2 Tests ============================ + +// // no stable swap +// function test_CanSwap() public override { +// vm.startPrank(USER_SENDER); + +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(USER_SENDER), +// to: address(USER_SENDER), +// tokenIn: ADDRESS_USDC, +// amountIn: 1_000 * 1e6, +// tokenOut: address(STG_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_NoStable_Reverse() public { +// // first perform the forward swap. +// test_CanSwap(); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(STG_TOKEN), +// amountIn: 500 * 1e18, +// tokenOut: ADDRESS_USDC, +// stable: false, +// direction: SwapDirection.Token1ToToken0, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_Stable() public { +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: ADDRESS_USDC, +// amountIn: 1_000 * 1e6, +// tokenOut: address(USDC_E_TOKEN), +// stable: true, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_Stable_Reverse() public { +// // first perform the forward stable swap. +// test_CanSwap_Stable(); + +// vm.startPrank(USER_SENDER); + +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(USDC_E_TOKEN), +// amountIn: 500 * 1e6, +// tokenOut: ADDRESS_USDC, +// stable: false, +// direction: SwapDirection.Token1ToToken0, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // fund dex aggregator contract so that the contract holds USDC +// deal(ADDRESS_USDC, address(liFiDEXAggregator), 100_000 * 1e6); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(liFiDEXAggregator), +// to: address(USER_SENDER), +// tokenIn: ADDRESS_USDC, +// amountIn: IERC20(ADDRESS_USDC).balanceOf( +// address(liFiDEXAggregator) +// ) - 1, // adjust for slot undrain protection: subtract 1 token so that the aggregator's balance isn't completely drained, matching the contract's safeguard +// tokenOut: address(USDC_E_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_FlashloanCallback() public { +// mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(USER_SENDER), +// to: address(mockFlashloanCallbackReceiver), +// tokenIn: ADDRESS_USDC, +// amountIn: 1_000 * 1e6, +// tokenOut: address(USDC_E_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: true +// }) +// ); +// vm.stopPrank(); +// } + +// // Override the abstract test with VelodromeV2 implementation +// function test_CanSwap_MultiHop() public override { +// vm.startPrank(USER_SENDER); + +// // Setup routes and get amounts +// MultiHopTestParams memory params = _setupRoutes( +// ADDRESS_USDC, +// address(STG_TOKEN), +// address(USDC_E_TOKEN), +// false, +// false +// ); + +// // Get initial reserves BEFORE the swap +// ReserveState memory initialReserves; +// ( +// initialReserves.reserve0Pool1, +// initialReserves.reserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// initialReserves.reserve0Pool2, +// initialReserves.reserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( +// USER_SENDER +// ); + +// // Build route and execute swap +// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); + +// // Approve and execute +// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// params.tokenIn, +// params.tokenOut, +// 1000 * 1e6, +// params.amounts2[1], +// params.amounts2[1] +// ); + +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// 1000 * 1e6, +// params.tokenOut, +// params.amounts2[1], +// USER_SENDER, +// route +// ); + +// _verifyUserBalances(params, initialBalance1, initialBalance2); +// _verifyReserves(params, initialReserves); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop_WithStable() public { +// vm.startPrank(USER_SENDER); + +// // Setup routes and get amounts for stable->volatile path +// MultiHopTestParams memory params = _setupRoutes( +// ADDRESS_USDC, +// address(USDC_E_TOKEN), +// address(STG_TOKEN), +// true, // stable pool for first hop +// false // volatile pool for second hop +// ); + +// // Get initial reserves BEFORE the swap +// ReserveState memory initialReserves; +// ( +// initialReserves.reserve0Pool1, +// initialReserves.reserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// initialReserves.reserve0Pool2, +// initialReserves.reserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// // Record initial balances +// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( +// USER_SENDER +// ); + +// // Build route and execute swap +// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + +// // Approve and execute +// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// params.tokenIn, +// params.tokenOut, +// 1000 * 1e6, +// params.amounts2[1], +// params.amounts2[1] +// ); + +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// 1000 * 1e6, +// params.tokenOut, +// params.amounts2[1], +// USER_SENDER, +// route +// ); + +// _verifyUserBalances(params, initialBalance1, initialBalance2); +// _verifyReserves(params, initialReserves); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidPoolOrRecipient() public { +// vm.startPrank(USER_SENDER); + +// // Get a valid pool address first for comparison +// address validPool = VELODROME_V2_ROUTER.poolFor( +// ADDRESS_USDC, +// address(STG_TOKEN), +// false, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// // Test case 1: Zero pool address +// bytes memory routeWithZeroPool = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// ADDRESS_USDC, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// address(0), +// uint8(SwapDirection.Token1ToToken0), +// USER_SENDER, +// uint8(CallbackStatus.Disabled) +// ); + +// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// ADDRESS_USDC, +// 1000 * 1e6, +// address(STG_TOKEN), +// 0, +// USER_SENDER, +// routeWithZeroPool +// ); + +// // Test case 2: Zero recipient address +// bytes memory routeWithZeroRecipient = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// ADDRESS_USDC, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// validPool, +// uint8(SwapDirection.Token1ToToken0), +// address(0), +// uint8(CallbackStatus.Disabled) +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// ADDRESS_USDC, +// 1000 * 1e6, +// address(STG_TOKEN), +// 0, +// USER_SENDER, +// routeWithZeroRecipient +// ); + +// vm.stopPrank(); +// } + +// function testRevert_WrongPoolReserves() public { +// vm.startPrank(USER_SENDER); + +// // Setup multi-hop route: USDC -> STG -> USDC.e +// MultiHopTestParams memory params = _setupRoutes( +// ADDRESS_USDC, +// address(STG_TOKEN), +// address(USDC_E_TOKEN), +// false, +// false +// ); + +// // Build multi-hop route +// bytes memory firstHop = _buildFirstHop( +// params.tokenIn, +// params.pool1, +// params.pool2, +// 1 // direction +// ); + +// bytes memory secondHop = _buildSecondHop( +// params.tokenMid, +// params.pool2, +// USER_SENDER, +// 0 // direction +// ); + +// bytes memory route = bytes.concat(firstHop, secondHop); + +// deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); + +// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves +// vm.mockCall( +// params.pool2, +// abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), +// abi.encode(0, 0, block.timestamp) +// ); + +// vm.expectRevert(WrongPoolReserves.selector); + +// liFiDEXAggregator.processRoute( +// ADDRESS_USDC, +// 1000 * 1e6, +// address(USDC_E_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// // ============================ Velodrome V2 Helper Functions ============================ + +// /** +// * @dev Helper function to test a VelodromeV2 swap. +// * Uses a struct to group parameters and reduce stack depth. +// */ +// function _testSwap(VelodromeV2SwapTestParams memory params) internal { +// // get expected output amounts from the router. +// IVelodromeV2Router.Route[] +// memory routes = new IVelodromeV2Router.Route[](1); +// routes[0] = IVelodromeV2Router.Route({ +// from: params.tokenIn, +// to: params.tokenOut, +// stable: params.stable, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( +// params.amountIn, +// routes +// ); +// emit log_named_uint("Expected amount out", amounts[1]); + +// // Retrieve the pool address. +// address pool = VELODROME_V2_ROUTER.poolFor( +// params.tokenIn, +// params.tokenOut, +// params.stable, +// VELODROME_V2_FACTORY_REGISTRY +// ); +// emit log_named_uint("Pool address:", uint256(uint160(pool))); + +// // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. +// CommandType commandCode = params.from == address(liFiDEXAggregator) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; + +// // build the route. +// bytes memory route = abi.encodePacked( +// uint8(commandCode), +// params.tokenIn, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// pool, +// params.direction, +// params.to, +// params.callback +// ? uint8(CallbackStatus.Enabled) +// : uint8(CallbackStatus.Disabled) +// ); + +// // approve the aggregator to spend tokenIn. +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); + +// // capture initial token balances. +// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); +// emit log_named_uint("Initial tokenIn balance", initialTokenIn); + +// address from = params.from == address(liFiDEXAggregator) +// ? USER_SENDER +// : params.from; +// if (params.callback == true) { +// vm.expectEmit(true, false, false, false); +// emit HookCalled( +// address(liFiDEXAggregator), +// 0, +// 0, +// abi.encode(params.tokenIn) +// ); +// } +// vm.expectEmit(true, true, true, true); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// amounts[1], +// amounts[1] +// ); + +// // execute the swap +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// amounts[1], +// params.to, +// route +// ); + +// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); +// emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); +// emit log_named_uint( +// "TokenOut received", +// finalTokenOut - initialTokenOut +// ); + +// assertApproxEqAbs( +// initialTokenIn - finalTokenIn, +// params.amountIn, +// 1, // 1 wei tolerance +// "TokenIn amount mismatch" +// ); +// assertEq( +// finalTokenOut - initialTokenOut, +// amounts[1], +// "TokenOut amount mismatch" +// ); +// } + +// // Helper function to set up routes and get amounts +// function _setupRoutes( +// address tokenIn, +// address tokenMid, +// address tokenOut, +// bool isStableFirst, +// bool isStableSecond +// ) private view returns (MultiHopTestParams memory params) { +// params.tokenIn = tokenIn; +// params.tokenMid = tokenMid; +// params.tokenOut = tokenOut; + +// // Setup first hop route +// IVelodromeV2Router.Route[] +// memory routes1 = new IVelodromeV2Router.Route[](1); +// routes1[0] = IVelodromeV2Router.Route({ +// from: tokenIn, +// to: tokenMid, +// stable: isStableFirst, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( +// 1000 * 1e6, +// routes1 +// ); + +// // Setup second hop route +// IVelodromeV2Router.Route[] +// memory routes2 = new IVelodromeV2Router.Route[](1); +// routes2[0] = IVelodromeV2Router.Route({ +// from: tokenMid, +// to: tokenOut, +// stable: isStableSecond, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( +// params.amounts1[1], +// routes2 +// ); + +// // Get pool addresses +// params.pool1 = VELODROME_V2_ROUTER.poolFor( +// tokenIn, +// tokenMid, +// isStableFirst, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// params.pool2 = VELODROME_V2_ROUTER.poolFor( +// tokenMid, +// tokenOut, +// isStableSecond, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// // Get pool fees info +// params.pool1Fee = IVelodromeV2PoolFactory( +// VELODROME_V2_FACTORY_REGISTRY +// ).getFee(params.pool1, isStableFirst); +// params.pool2Fee = IVelodromeV2PoolFactory( +// VELODROME_V2_FACTORY_REGISTRY +// ).getFee(params.pool2, isStableSecond); + +// return params; +// } + +// // function to build first hop of the route +// function _buildFirstHop( +// address tokenIn, +// address pool1, +// address pool2, +// uint8 direction +// ) private pure returns (bytes memory) { +// return +// abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// tokenIn, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// pool1, +// direction, +// pool2, +// uint8(CallbackStatus.Disabled) +// ); +// } + +// // function to build second hop of the route +// function _buildSecondHop( +// address tokenMid, +// address pool2, +// address recipient, +// uint8 direction +// ) private pure returns (bytes memory) { +// return +// abi.encodePacked( +// uint8(CommandType.ProcessOnePool), +// tokenMid, +// uint8(PoolType.VelodromeV2), +// pool2, +// direction, +// recipient, +// uint8(CallbackStatus.Disabled) +// ); +// } + +// // route building function +// function _buildMultiHopRoute( +// MultiHopTestParams memory params, +// address recipient, +// uint8 firstHopDirection, +// uint8 secondHopDirection +// ) private pure returns (bytes memory) { +// bytes memory firstHop = _buildFirstHop( +// params.tokenIn, +// params.pool1, +// params.pool2, +// firstHopDirection +// ); + +// bytes memory secondHop = _buildSecondHop( +// params.tokenMid, +// params.pool2, +// recipient, +// secondHopDirection +// ); + +// return bytes.concat(firstHop, secondHop); +// } + +// function _verifyUserBalances( +// MultiHopTestParams memory params, +// uint256 initialBalance1, +// uint256 initialBalance2 +// ) private { +// // Verify token balances +// uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// assertApproxEqAbs( +// initialBalance1 - finalBalance1, +// 1000 * 1e6, +// 1, // 1 wei tolerance +// "Token1 spent amount mismatch" +// ); +// assertEq( +// finalBalance2 - initialBalance2, +// params.amounts2[1], +// "Token2 received amount mismatch" +// ); +// } + +// function _verifyReserves( +// MultiHopTestParams memory params, +// ReserveState memory initialReserves +// ) private { +// // Get reserves after swap +// ( +// uint256 finalReserve0Pool1, +// uint256 finalReserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// uint256 finalReserve0Pool2, +// uint256 finalReserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); +// address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); + +// // Calculate exact expected changes +// uint256 amountInAfterFees = 1000 * +// 1e6 - +// ((1000 * 1e6 * params.pool1Fee) / 10000); + +// // Assert exact reserve changes for Pool1 +// if (token0Pool1 == params.tokenIn) { +// // tokenIn is token0, so reserve0 should increase and reserve1 should decrease +// assertEq( +// finalReserve0Pool1 - initialReserves.reserve0Pool1, +// amountInAfterFees, +// "Pool1 reserve0 (tokenIn) change incorrect" +// ); +// assertEq( +// initialReserves.reserve1Pool1 - finalReserve1Pool1, +// params.amounts1[1], +// "Pool1 reserve1 (tokenMid) change incorrect" +// ); +// } else { +// // tokenIn is token1, so reserve1 should increase and reserve0 should decrease +// assertEq( +// finalReserve1Pool1 - initialReserves.reserve1Pool1, +// amountInAfterFees, +// "Pool1 reserve1 (tokenIn) change incorrect" +// ); +// assertEq( +// initialReserves.reserve0Pool1 - finalReserve0Pool1, +// params.amounts1[1], +// "Pool1 reserve0 (tokenMid) change incorrect" +// ); +// } + +// // Assert exact reserve changes for Pool2 +// if (token0Pool2 == params.tokenMid) { +// // tokenMid is token0, so reserve0 should increase and reserve1 should decrease +// assertEq( +// finalReserve0Pool2 - initialReserves.reserve0Pool2, +// params.amounts1[1] - +// ((params.amounts1[1] * params.pool2Fee) / 10000), +// "Pool2 reserve0 (tokenMid) change incorrect" +// ); +// assertEq( +// initialReserves.reserve1Pool2 - finalReserve1Pool2, +// params.amounts2[1], +// "Pool2 reserve1 (tokenOut) change incorrect" +// ); +// } else { +// // tokenMid is token1, so reserve1 should increase and reserve0 should decrease +// assertEq( +// finalReserve1Pool2 - initialReserves.reserve1Pool2, +// params.amounts1[1] - +// ((params.amounts1[1] * params.pool2Fee) / 10000), +// "Pool2 reserve1 (tokenMid) change incorrect" +// ); +// assertEq( +// initialReserves.reserve0Pool2 - finalReserve0Pool2, +// params.amounts2[1], +// "Pool2 reserve0 (tokenOut) change incorrect" +// ); +// } +// } +// } + +// contract AlgebraLiquidityAdderHelper { +// address public immutable TOKEN_0; +// address public immutable TOKEN_1; + +// constructor(address _token0, address _token1) { +// TOKEN_0 = _token0; +// TOKEN_1 = _token1; +// } + +// function addLiquidity( +// address pool, +// int24 bottomTick, +// int24 topTick, +// uint128 amount +// ) +// external +// returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) +// { +// // Get balances before +// uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Call mint +// (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( +// address(this), +// address(this), +// bottomTick, +// topTick, +// amount, +// abi.encode(TOKEN_0, TOKEN_1) +// ); + +// // Get balances after to account for fees +// uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Calculate actual amounts transferred accounting for fees +// amount0 = balance0Before - balance0After; +// amount1 = balance1Before - balance1After; + +// return (amount0, amount1, liquidityActual); +// } + +// function algebraMintCallback( +// uint256 amount0Owed, +// uint256 amount1Owed, +// bytes calldata +// ) external { +// // Check token balances +// uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Transfer what we can, limited by actual balance +// if (amount0Owed > 0) { +// uint256 amount0ToSend = amount0Owed > balance0 +// ? balance0 +// : amount0Owed; +// uint256 balance0Before = IERC20(TOKEN_0).balanceOf( +// address(msg.sender) +// ); +// IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); +// uint256 balance0After = IERC20(TOKEN_0).balanceOf( +// address(msg.sender) +// ); +// // solhint-disable-next-line gas-custom-errors +// require(balance0After > balance0Before, "Transfer failed"); +// } + +// if (amount1Owed > 0) { +// uint256 amount1ToSend = amount1Owed > balance1 +// ? balance1 +// : amount1Owed; +// uint256 balance1Before = IERC20(TOKEN_1).balanceOf( +// address(msg.sender) +// ); +// IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); +// uint256 balance1After = IERC20(TOKEN_1).balanceOf( +// address(msg.sender) +// ); +// // solhint-disable-next-line gas-custom-errors +// require(balance1After > balance1Before, "Transfer failed"); +// } +// } +// } + +// /** +// * @title Algebra tests +// * @notice Tests specific to Algebra pool type +// */ +// contract LiFiDexAggregatorAlgebraTest is LiFiDexAggregatorTest { +// address private constant APE_ETH_TOKEN = +// 0xcF800F4948D16F23333508191B1B1591daF70438; +// address private constant WETH_TOKEN = +// 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; +// address private constant ALGEBRA_FACTORY_APECHAIN = +// 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; +// address private constant ALGEBRA_QUOTER_V2_APECHAIN = +// 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; +// address private constant ALGEBRA_POOL_APECHAIN = +// 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; +// address private constant APE_ETH_HOLDER_APECHAIN = +// address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); + +// address private constant IMPOSSIBLE_POOL_ADDRESS = +// 0x0000000000000000000000000000000000000001; + +// struct AlgebraSwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// SwapDirection direction; +// bool supportsFeeOnTransfer; +// } + +// error AlgebraSwapUnexpected(); + +// function setUp() public override { +// setupApechain(); +// } + +// // Override the abstract test with Algebra implementation +// function test_CanSwap_FromDexAggregator() public override { +// // Fund LDA from whale address +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(address(liFiDEXAggregator), 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: address(liFiDEXAggregator), +// to: address(USER_SENDER), +// tokenIn: APE_ETH_TOKEN, +// amountIn: IERC20(APE_ETH_TOKEN).balanceOf( +// address(liFiDEXAggregator) +// ) - 1, +// tokenOut: address(WETH_TOKEN), +// direction: SwapDirection.Token0ToToken1, +// supportsFeeOnTransfer: true +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FeeOnTransferToken() public { +// setupApechain(); + +// uint256 amountIn = 534451326669177; +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); + +// vm.startPrank(APE_ETH_HOLDER_APECHAIN); + +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), amountIn); + +// // Build route for algebra swap with command code 2 (user funds) +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: APE_ETH_HOLDER_APECHAIN, +// pool: ALGEBRA_POOL_APECHAIN, +// supportsFeeOnTransfer: true +// }) +// ); + +// // Track initial balance +// uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( +// APE_ETH_HOLDER_APECHAIN +// ); + +// // Execute the swap +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// amountIn, +// WETH_TOKEN, +// 0, // minOut = 0 for this test +// APE_ETH_HOLDER_APECHAIN, +// route +// ); + +// // Verify balances +// uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( +// APE_ETH_HOLDER_APECHAIN +// ); +// assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); + +// vm.stopPrank(); +// } + +// function test_CanSwap() public override { +// vm.startPrank(APE_ETH_HOLDER_APECHAIN); + +// // Transfer tokens from whale to USER_SENDER +// uint256 amountToTransfer = 100 * 1e18; +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); + +// vm.stopPrank(); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: APE_ETH_TOKEN, +// amountIn: 10 * 1e18, +// tokenOut: address(WETH_TOKEN), +// direction: SwapDirection.Token0ToToken1, +// supportsFeeOnTransfer: true +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_Reverse() public { +// test_CanSwap(); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(WETH_TOKEN), +// amountIn: 5 * 1e18, +// tokenOut: APE_ETH_TOKEN, +// direction: SwapDirection.Token1ToToken0, +// supportsFeeOnTransfer: false +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { +// MultiHopTestState memory state; +// state.isFeeOnTransfer = true; + +// // Setup tokens and pools +// state = _setupTokensAndPools(state); + +// // Execute and verify swap +// _executeAndVerifyMultiHopSwap(state); +// } + +// function test_CanSwap_MultiHop() public override { +// MultiHopTestState memory state; +// state.isFeeOnTransfer = false; + +// // Setup tokens and pools +// state = _setupTokensAndPools(state); + +// // Execute and verify swap +// _executeAndVerifyMultiHopSwap(state); +// } + +// // Test that the proper error is thrown when algebra swap fails +// function testRevert_SwapUnexpected() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Create invalid pool address +// address invalidPool = address(0x999); + +// // Mock token0() call on invalid pool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Create a route with an invalid pool +// bytes memory invalidRoute = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: invalidPool, +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Mock the algebra pool to not reset lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSelector( +// IAlgebraPool.swapSupportingFeeOnInputTokens.selector +// ), +// abi.encode(0, 0) +// ); + +// // Expect the AlgebraSwapUnexpected error +// vm.expectRevert(AlgebraSwapUnexpected.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// invalidRoute +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// // Helper function to setup tokens and pools +// function _setupTokensAndPools( +// MultiHopTestState memory state +// ) private returns (MultiHopTestState memory) { +// // Create tokens +// ERC20 tokenA = new ERC20( +// "Token A", +// state.isFeeOnTransfer ? "FTA" : "TA", +// 18 +// ); +// IERC20 tokenB; +// ERC20 tokenC = new ERC20( +// "Token C", +// state.isFeeOnTransfer ? "FTC" : "TC", +// 18 +// ); + +// if (state.isFeeOnTransfer) { +// tokenB = IERC20( +// address( +// new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) +// ) +// ); +// } else { +// tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); +// } + +// state.tokenA = IERC20(address(tokenA)); +// state.tokenB = tokenB; +// state.tokenC = IERC20(address(tokenC)); + +// // Label addresses +// vm.label(address(state.tokenA), "Token A"); +// vm.label(address(state.tokenB), "Token B"); +// vm.label(address(state.tokenC), "Token C"); + +// // Mint initial token supplies +// tokenA.mint(address(this), 1_000_000 * 1e18); +// if (!state.isFeeOnTransfer) { +// ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); +// } else { +// MockFeeOnTransferToken(address(tokenB)).mint( +// address(this), +// 1_000_000 * 1e18 +// ); +// } +// tokenC.mint(address(this), 1_000_000 * 1e18); + +// // Create pools +// state.pool1 = _createAlgebraPool( +// address(state.tokenA), +// address(state.tokenB) +// ); +// state.pool2 = _createAlgebraPool( +// address(state.tokenB), +// address(state.tokenC) +// ); + +// vm.label(state.pool1, "Pool 1"); +// vm.label(state.pool2, "Pool 2"); + +// // Add liquidity +// _addLiquidityToPool( +// state.pool1, +// address(state.tokenA), +// address(state.tokenB) +// ); +// _addLiquidityToPool( +// state.pool2, +// address(state.tokenB), +// address(state.tokenC) +// ); + +// state.amountToTransfer = 100 * 1e18; +// state.amountIn = 50 * 1e18; + +// // Transfer tokens to USER_SENDER +// IERC20(address(state.tokenA)).transfer( +// USER_SENDER, +// state.amountToTransfer +// ); + +// return state; +// } + +// // Helper function to execute and verify the swap +// function _executeAndVerifyMultiHopSwap( +// MultiHopTestState memory state +// ) private { +// vm.startPrank(USER_SENDER); + +// uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( +// USER_SENDER +// ); + +// // Approve spending +// IERC20(address(state.tokenA)).approve( +// address(liFiDEXAggregator), +// state.amountIn +// ); + +// // Build route +// bytes memory route = _buildMultiHopRouteForTest(state); + +// // Execute swap +// liFiDEXAggregator.processRoute( +// address(state.tokenA), +// state.amountIn, +// address(state.tokenC), +// 0, // No minimum amount out for testing +// USER_SENDER, +// route +// ); + +// // Verify results +// _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); + +// vm.stopPrank(); +// } + +// // Helper function to build the multi-hop route for test +// function _buildMultiHopRouteForTest( +// MultiHopTestState memory state +// ) private view returns (bytes memory) { +// bytes memory firstHop = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: address(state.tokenA), +// recipient: address(liFiDEXAggregator), +// pool: state.pool1, +// supportsFeeOnTransfer: false +// }) +// ); + +// bytes memory secondHop = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessMyERC20, +// tokenIn: address(state.tokenB), +// recipient: USER_SENDER, +// pool: state.pool2, +// supportsFeeOnTransfer: state.isFeeOnTransfer +// }) +// ); + +// return bytes.concat(firstHop, secondHop); +// } + +// // Helper function to verify multi-hop results +// function _verifyMultiHopResults( +// MultiHopTestState memory state, +// uint256 initialBalanceA, +// uint256 initialBalanceC +// ) private { +// uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( +// USER_SENDER +// ); +// uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( +// USER_SENDER +// ); + +// assertApproxEqAbs( +// initialBalanceA - finalBalanceA, +// state.amountIn, +// 1, // 1 wei tolerance +// "TokenA spent amount mismatch" +// ); +// assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); + +// emit log_named_uint( +// state.isFeeOnTransfer +// ? "Output amount with fee tokens" +// : "Output amount with regular tokens", +// finalBalanceC - initialBalanceC +// ); +// } + +// // Helper function to create an Algebra pool +// function _createAlgebraPool( +// address tokenA, +// address tokenB +// ) internal returns (address pool) { +// // Call the actual Algebra factory to create a pool +// pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( +// tokenA, +// tokenB +// ); +// return pool; +// } + +// // Helper function to add liquidity to a pool +// function _addLiquidityToPool( +// address pool, +// address token0, +// address token1 +// ) internal { +// // For fee-on-transfer tokens, we need to send more to account for the fee +// // We'll use a small amount and send extra to cover fees +// uint256 initialAmount0 = 1e17; // 0.1 token +// uint256 initialAmount1 = 1e17; // 0.1 token + +// // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) +// uint256 transferAmount0 = (initialAmount0 * 110) / 100; +// uint256 transferAmount1 = (initialAmount1 * 110) / 100; + +// // Initialize with 1:1 price ratio (Q64.96 format) +// uint160 initialPrice = uint160(1 << 96); +// IAlgebraPool(pool).initialize(initialPrice); + +// // Create AlgebraLiquidityAdderHelper with safe transfer logic +// AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( +// token0, +// token1 +// ); + +// // Transfer tokens with extra amounts to account for fees +// IERC20(token0).transfer( +// address(algebraLiquidityAdderHelper), +// transferAmount0 +// ); +// IERC20(token1).transfer( +// address(algebraLiquidityAdderHelper), +// transferAmount1 +// ); + +// // Get actual balances to use for liquidity, accounting for any fees +// uint256 actualBalance0 = IERC20(token0).balanceOf( +// address(algebraLiquidityAdderHelper) +// ); +// uint256 actualBalance1 = IERC20(token1).balanceOf( +// address(algebraLiquidityAdderHelper) +// ); + +// // Use the smaller of the two balances for liquidity amount +// uint128 liquidityAmount = uint128( +// actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 +// ); + +// // Add liquidity using the actual token amounts we have +// algebraLiquidityAdderHelper.addLiquidity( +// pool, +// -887220, +// 887220, +// liquidityAmount / 2 // Use half of available liquidity to ensure success +// ); +// } + +// struct MultiHopTestState { +// IERC20 tokenA; +// IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken +// IERC20 tokenC; +// address pool1; +// address pool2; +// uint256 amountIn; +// uint256 amountToTransfer; +// bool isFeeOnTransfer; +// } + +// struct AlgebraRouteParams { +// CommandType commandCode; // 1 for contract funds, 2 for user funds +// address tokenIn; // Input token address +// address recipient; // Address receiving the output tokens +// address pool; // Algebra pool address +// bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens +// } + +// // Helper function to build route for Apechain Algebra swap +// function _buildAlgebraRoute( +// AlgebraRouteParams memory params +// ) internal view returns (bytes memory route) { +// address token0 = IAlgebraPool(params.pool).token0(); +// bool zeroForOne = (params.tokenIn == token0); +// SwapDirection direction = zeroForOne +// ? SwapDirection.Token0ToToken1 +// : SwapDirection.Token1ToToken0; + +// route = abi.encodePacked( +// params.commandCode, +// params.tokenIn, +// uint8(1), // one pool +// FULL_SHARE, // 100% share +// uint8(PoolType.Algebra), +// params.pool, +// uint8(direction), +// params.recipient, +// params.supportsFeeOnTransfer ? uint8(1) : uint8(0) +// ); + +// return route; +// } + +// // Helper function to test an Algebra swap +// function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { +// // Find or create a pool +// address pool = _getPool(params.tokenIn, params.tokenOut); + +// vm.label(pool, "AlgebraPool"); + +// // Get token0 from pool and label tokens accordingly +// address token0 = IAlgebraPool(pool).token0(); +// if (params.tokenIn == token0) { +// vm.label( +// params.tokenIn, +// string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") +// ); +// vm.label( +// params.tokenOut, +// string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") +// ); +// } else { +// vm.label( +// params.tokenIn, +// string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") +// ); +// vm.label( +// params.tokenOut, +// string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") +// ); +// } + +// // Record initial balances +// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + +// // Get expected output from QuoterV2 +// // NOTE: There may be a small discrepancy between the quoted amount and the actual output +// // because the Quoter uses the regular swap() function for simulation while the actual +// // execution may use swapSupportingFeeOnInputTokens() for fee-on-transfer tokens. +// // The Quoter cannot accurately predict transfer fees taken by the token contract itself, +// // resulting in minor "dust" differences that are normal and expected when dealing with +// // non-standard token implementations. +// uint256 expectedOutput = _getQuoteExactInput( +// params.tokenIn, +// params.tokenOut, +// params.amountIn +// ); + +// // Build the route +// CommandType commandCode = params.from == address(liFiDEXAggregator) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: commandCode, +// tokenIn: params.tokenIn, +// recipient: params.to, +// pool: pool, +// supportsFeeOnTransfer: params.supportsFeeOnTransfer +// }) +// ); + +// // Approve tokens +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); + +// // Execute the swap +// address from = params.from == address(liFiDEXAggregator) +// ? USER_SENDER +// : params.from; + +// vm.expectEmit(true, true, true, false); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// expectedOutput, +// expectedOutput +// ); + +// uint256 minOut = (expectedOutput * 995) / 1000; // 0.5% slippage + +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// minOut, +// params.to, +// route +// ); + +// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + +// assertApproxEqAbs( +// initialTokenIn - finalTokenIn, +// params.amountIn, +// 1, // 1 wei tolerance +// "TokenIn amount mismatch" +// ); +// assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); +// } + +// function _getPool( +// address tokenA, +// address tokenB +// ) private view returns (address pool) { +// pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( +// tokenA, +// tokenB +// ); +// if (pool == address(0)) revert PoolDoesNotExist(); +// return pool; +// } + +// function _getQuoteExactInput( +// address tokenIn, +// address tokenOut, +// uint256 amountIn +// ) private returns (uint256 amountOut) { +// (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) +// .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); +// return amountOut; +// } + +// function testRevert_AlgebraSwap_ZeroAddressPool() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on address(0) +// vm.mockCall( +// address(0), +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with address(0) as pool +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: address(0), // Zero address pool +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS +// vm.mockCall( +// IMPOSSIBLE_POOL_ADDRESS, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with IMPOSSIBLE_POOL_ADDRESS as pool +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// function testRevert_AlgebraSwap_ZeroAddressRecipient() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on the pool +// vm.mockCall( +// ALGEBRA_POOL_APECHAIN, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with address(0) as recipient +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: address(0), // Zero address recipient +// pool: ALGEBRA_POOL_APECHAIN, +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } +// } + +// /** +// * @title LiFiDexAggregatorIzumiV3Test +// * @notice Tests specific to iZiSwap V3 pool type +// */ +// contract LiFiDexAggregatorIzumiV3Test is LiFiDexAggregatorTest { +// // ==================== iZiSwap V3 specific variables ==================== +// // Base constants +// address internal constant USDC = +// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; +// address internal constant WETH = +// 0x4200000000000000000000000000000000000006; +// address internal constant USDB_C = +// 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; + +// // iZiSwap pools +// address internal constant IZUMI_WETH_USDC_POOL = +// 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; +// address internal constant IZUMI_WETH_USDB_C_POOL = +// 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; + +// // Test parameters +// uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals +// uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals + +// // structs +// struct IzumiV3SwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// SwapDirection direction; +// } + +// struct MultiHopTestParams { +// address tokenIn; +// address tokenMid; +// address tokenOut; +// address pool1; +// address pool2; +// uint256 amountIn; +// SwapDirection direction1; +// SwapDirection direction2; +// } + +// error IzumiV3SwapUnexpected(); +// error IzumiV3SwapCallbackUnknownSource(); +// error IzumiV3SwapCallbackNotPositiveAmount(); + +// function setUp() public override { +// super.setUp(); + +// string memory baseRpc = vm.envString("ETH_NODE_URI_BASE"); +// vm.createSelectFork(baseRpc, 29831758); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); + +// // Setup labels +// vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); +// vm.label(USDC, "USDC"); +// vm.label(WETH, "WETH"); +// vm.label(USDB_C, "USDB-C"); +// vm.label(IZUMI_WETH_USDC_POOL, "WETH-USDC Pool"); +// vm.label(IZUMI_WETH_USDB_C_POOL, "WETH-USDB-C Pool"); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // Test USDC -> WETH +// deal(USDC, address(liFiDEXAggregator), AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// IzumiV3SwapTestParams({ +// from: address(liFiDEXAggregator), +// to: USER_SENDER, +// tokenIn: USDC, +// amountIn: AMOUNT_USDC, +// tokenOut: WETH, +// direction: SwapDirection.Token1ToToken0 +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// _testMultiHopSwap( +// MultiHopTestParams({ +// tokenIn: USDC, +// tokenMid: WETH, +// tokenOut: USDB_C, +// pool1: IZUMI_WETH_USDC_POOL, +// pool2: IZUMI_WETH_USDB_C_POOL, +// amountIn: AMOUNT_USDC, +// direction1: SwapDirection.Token1ToToken0, +// direction2: SwapDirection.Token0ToToken1 +// }) +// ); +// } + +// function test_CanSwap() public override { +// deal(address(USDC), USER_SENDER, AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); +// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); + +// // fix the swap data encoding +// bytes memory swapData = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// USDC, +// uint8(SwapDirection.Token1ToToken0), +// IZUMI_WETH_USDC_POOL, +// USER_RECEIVER +// ); + +// vm.expectEmit(true, true, true, false); +// emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); + +// liFiDEXAggregator.processRoute( +// USDC, +// AMOUNT_USDC, +// WETH, +// 0, +// USER_RECEIVER, +// swapData +// ); + +// vm.stopPrank(); +// } + +// function testRevert_IzumiV3SwapUnexpected() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); + +// // create invalid pool address +// address invalidPool = address(0x999); + +// // create a route with an invalid pool +// bytes memory invalidRoute = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// USDC, +// uint8(SwapDirection.Token1ToToken0), +// invalidPool, +// USER_SENDER +// ); + +// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); + +// // mock the iZiSwap pool to return without updating lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), +// abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool +// ); + +// vm.expectRevert(IzumiV3SwapUnexpected.selector); + +// liFiDEXAggregator.processRoute( +// USDC, +// AMOUNT_USDC, +// WETH, +// 0, +// USER_SENDER, +// invalidRoute +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// function testRevert_IzumiV3SwapCallbackUnknownSource() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// // create invalid pool address +// address invalidPool = address(0x999); + +// vm.prank(USER_SENDER); +// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); + +// // mock the pool to call the callback directly without setting lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), +// abi.encode(0, 0) +// ); + +// // try to call the callback directly from the pool without setting lastCalledPool +// vm.prank(invalidPool); +// vm.expectRevert(IzumiV3SwapCallbackUnknownSource.selector); +// liFiDEXAggregator.swapY2XCallback(0, AMOUNT_USDC, abi.encode(USDC)); + +// vm.clearMockedCalls(); +// } + +// function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// // set lastCalledPool to the pool address to pass the unknown source check +// vm.store( +// address(liFiDEXAggregator), +// bytes32(uint256(3)), // slot for lastCalledPool +// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) +// ); + +// // try to call the callback with zero amount +// vm.prank(IZUMI_WETH_USDC_POOL); +// vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); +// liFiDEXAggregator.swapY2XCallback( +// 0, +// 0, // zero amount should trigger the error +// abi.encode(USDC) +// ); +// } + +// function testRevert_FailsIfAmountInIsTooLarge() public { +// deal(address(WETH), USER_SENDER, type(uint256).max); + +// vm.startPrank(USER_SENDER); +// IERC20(WETH).approve(address(liFiDEXAggregator), type(uint256).max); + +// // fix the swap data encoding +// bytes memory swapData = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// WETH, +// uint8(SwapDirection.Token0ToToken1), +// IZUMI_WETH_USDC_POOL, +// USER_RECEIVER +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// WETH, +// type(uint216).max, +// USDC, +// 0, +// USER_RECEIVER, +// swapData +// ); + +// vm.stopPrank(); +// } + +// function _testSwap(IzumiV3SwapTestParams memory params) internal { +// // Fund the sender with tokens if not the contract itself +// if (params.from != address(liFiDEXAggregator)) { +// deal(params.tokenIn, params.from, params.amountIn); +// } + +// // Capture initial token balances +// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( +// params.from +// ); +// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( +// params.to +// ); + +// // Build the route based on the command type +// CommandType commandCode = params.from == address(liFiDEXAggregator) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; + +// // Construct the route +// bytes memory route = _buildIzumiV3Route( +// commandCode, +// params.tokenIn, +// uint8(params.direction == SwapDirection.Token0ToToken1 ? 1 : 0), +// IZUMI_WETH_USDC_POOL, +// params.to +// ); + +// // Approve tokens if necessary +// if (params.from == USER_SENDER) { +// vm.startPrank(USER_SENDER); +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); +// } + +// // Expect the Route event emission +// address from = params.from == address(liFiDEXAggregator) +// ? USER_SENDER +// : params.from; + +// vm.expectEmit(true, true, true, false); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// 0, // No minimum amount enforced in test +// 0 // Actual amount will be checked after the swap +// ); + +// // Execute the swap +// uint256 amountOut = liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// 0, // No minimum amount for testing +// params.to, +// route +// ); + +// if (params.from == USER_SENDER) { +// vm.stopPrank(); +// } + +// // Verify balances have changed correctly +// uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); + +// assertApproxEqAbs( +// initialBalanceIn - finalBalanceIn, +// params.amountIn, +// 1, // 1 wei tolerance because of undrain protection for dex aggregator +// "TokenIn amount mismatch" +// ); +// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); +// assertEq( +// amountOut, +// finalBalanceOut - initialBalanceOut, +// "AmountOut mismatch" +// ); + +// emit log_named_uint("Amount In", params.amountIn); +// emit log_named_uint("Amount Out", amountOut); +// } + +// function _testMultiHopSwap(MultiHopTestParams memory params) internal { +// // Fund the sender with tokens +// deal(params.tokenIn, USER_SENDER, params.amountIn); + +// // Capture initial token balances +// uint256 initialBalanceIn; +// uint256 initialBalanceOut; + +// initialBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// initialBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// // Build multi-hop route +// bytes memory route = _buildIzumiV3MultiHopRoute(params); + +// // Approve tokens +// vm.startPrank(USER_SENDER); +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); + +// // Execute the swap +// uint256 amountOut = liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// 0, // No minimum amount for testing +// USER_SENDER, +// route +// ); +// vm.stopPrank(); + +// // Verify balances have changed correctly +// uint256 finalBalanceIn; +// uint256 finalBalanceOut; + +// finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// assertEq( +// initialBalanceIn - finalBalanceIn, +// params.amountIn, +// "TokenIn amount mismatch" +// ); +// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); +// assertEq( +// amountOut, +// finalBalanceOut - initialBalanceOut, +// "AmountOut mismatch" +// ); +// } + +// function _buildIzumiV3Route( +// CommandType commandCode, +// address tokenIn, +// uint8 direction, +// address pool, +// address recipient +// ) internal pure returns (bytes memory) { +// return +// abi.encodePacked( +// uint8(commandCode), +// tokenIn, +// uint8(1), // number of pools (1) +// FULL_SHARE, // 100% share +// uint8(PoolType.iZiSwap), // pool type +// pool, +// uint8(direction), +// recipient +// ); +// } + +// function _buildIzumiV3MultiHopRoute( +// MultiHopTestParams memory params +// ) internal view returns (bytes memory) { +// // First hop: USER_ERC20 -> LDA +// bytes memory firstHop = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// params.tokenIn, +// uint8(params.direction1), +// params.pool1, +// address(liFiDEXAggregator) +// ); + +// // Second hop: MY_ERC20 (LDA) -> pool2 +// bytes memory secondHop = _buildIzumiV3Route( +// CommandType.ProcessMyERC20, +// params.tokenMid, +// uint8(params.direction2), +// params.pool2, +// USER_SENDER // final recipient +// ); + +// // Combine the two hops +// return bytes.concat(firstHop, secondHop); +// } +// } + +// ----------------------------------------------------------------------------- +// HyperswapV3 on HyperEVM +// ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorHyperswapV3Test is LiFiDexAggregatorUpgradeTest { +// using SafeERC20 for IERC20; + +// /// @dev HyperswapV3 router on HyperEVM chain +// IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = +// IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); +// /// @dev HyperswapV3 quoter on HyperEVM chain +// IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = +// IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); + +// /// @dev a liquid USDT on HyperEVM +// IERC20 internal constant USDT0 = +// IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); +// /// @dev WHYPE on HyperEVM +// IERC20 internal constant WHYPE = +// IERC20(0x5555555555555555555555555555555555555555); + +// struct HyperswapV3Params { +// CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 +// address tokenIn; // Input token address +// address recipient; // Address receiving the output tokens +// address pool; // HyperswapV3 pool address +// bool zeroForOne; // Direction of the swap +// } + +// function setUp() public override { +// setupHyperEVM(); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + +// deal(address(USDT0), USER_SENDER, amountIn); + +// // user approves +// vm.prank(USER_SENDER); +// USDT0.approve(address(liFiDEXAggregator), amountIn); + +// // fetch the real pool and quote +// address pool = HYPERSWAP_FACTORY.getPool( +// address(USDT0), +// address(WHYPE), +// 3000 +// ); + +// // Create the params struct for quoting +// IHyperswapV3QuoterV2.QuoteExactInputSingleParams +// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ +// tokenIn: address(USDT0), +// tokenOut: address(WHYPE), +// amountIn: amountIn, +// fee: 3000, +// sqrtPriceLimitX96: 0 +// }); + +// // Get the quote using the struct +// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( +// params +// ); + +// // build the "off-chain" route +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(USDT0), +// uint8(1), // 1 pool +// uint16(65535), // FULL_SHARE +// UniV3Facet.swapUniV3.selector, // UNIV3 selector +// pool, +// uint8(0), // zeroForOne = true if USDT0 < WHYPE +// address(USER_SENDER) +// ); + +// // expect the Route event +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// address(USDT0), +// address(WHYPE), +// amountIn, +// quoted, +// quoted +// ); + +// // execute +// vm.prank(USER_SENDER); +// liFiDEXAggregator.processRoute( +// address(USDT0), +// amountIn, +// address(WHYPE), +// quoted, +// USER_SENDER, +// route +// ); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + +// // Fund dex aggregator contract +// deal(address(USDT0), address(liFiDEXAggregator), amountIn); + +// // fetch the real pool and quote +// address pool = HYPERSWAP_FACTORY.getPool( +// address(USDT0), +// address(WHYPE), +// 3000 +// ); + +// // Create the params struct for quoting +// IHyperswapV3QuoterV2.QuoteExactInputSingleParams +// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ +// tokenIn: address(USDT0), +// tokenOut: address(WHYPE), +// amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection +// fee: 3000, +// sqrtPriceLimitX96: 0 +// }); + +// // Get the quote using the struct +// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( +// params +// ); + +// // Build route using our helper function +// bytes memory route = _buildHyperswapV3Route( +// HyperswapV3Params({ +// commandCode: CommandType.ProcessMyERC20, +// tokenIn: address(USDT0), +// recipient: USER_SENDER, +// pool: pool, +// zeroForOne: true // USDT0 < WHYPE +// }) +// ); + +// // expect the Route event +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// address(USDT0), +// address(WHYPE), +// amountIn - 1, // Account for slot undrain protection +// quoted, +// quoted +// ); + +// // execute +// vm.prank(USER_SENDER); +// liFiDEXAggregator.processRoute( +// address(USDT0), +// amountIn - 1, // Account for slot undrain protection +// address(WHYPE), +// quoted, +// USER_SENDER, +// route +// ); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. +// // HyperswapV3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. HyperswapV3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// function _buildHyperswapV3Route( +// HyperswapV3Params memory params +// ) internal pure returns (bytes memory route) { +// route = abi.encodePacked( +// uint8(params.commandCode), +// params.tokenIn, +// uint8(1), // 1 pool +// FULL_SHARE, // 65535 - 100% share +// UniV3Facet.swapUniV3.selector, // UNIV3 selector +// params.pool, +// uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false +// params.recipient +// ); + +// return route; +// } +// } + +// // ----------------------------------------------------------------------------- +// // LaminarV3 on HyperEVM +// // ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorLaminarV3Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// IERC20 internal constant WHYPE = +// IERC20(0x5555555555555555555555555555555555555555); +// IERC20 internal constant LHYPE = +// IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); + +// address internal constant WHYPE_LHYPE_POOL = +// 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; + +// function setUp() public override { +// setupHyperEVM(); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // Fund the user with WHYPE +// deal(address(WHYPE), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WHYPE.approve(address(liFiDEXAggregator), amountIn); + +// // Build a single-pool UniV3 route +// bool zeroForOne = address(WHYPE) > address(LHYPE); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(WHYPE), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), +// WHYPE_LHYPE_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// // Record balances +// uint256 inBefore = WHYPE.balanceOf(USER_SENDER); +// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + +// // Execute swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(WHYPE), +// amountIn, +// address(LHYPE), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify +// uint256 inAfter = WHYPE.balanceOf(USER_SENDER); +// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the aggregator directly +// deal(address(WHYPE), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); + +// bool zeroForOne = address(WHYPE) > address(LHYPE); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(WHYPE), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// WHYPE_LHYPE_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + +// // Withdraw 1 wei to avoid slot-undrain protection +// liFiDEXAggregator.processRoute( +// address(WHYPE), +// amountIn - 1, +// address(LHYPE), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. +// // Laminar V3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. Laminar V3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } +// } + +// contract LiFiDexAggregatorXSwapV3Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// address internal constant USDC_E_WXDC_POOL = +// 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; + +// /// @dev our two tokens: USDC.e and wrapped XDC +// IERC20 internal constant USDC_E = +// IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); +// IERC20 internal constant WXDC = +// IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); + +// function setUp() public override { +// customRpcUrlForForking = "ETH_NODE_URI_XDC"; +// customBlockNumberForForking = 89279495; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e6; +// deal(address(USDC_E), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// USDC_E.approve(address(liFiDEXAggregator), amountIn); + +// // Build a one-pool V3 route +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(USDC_E), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), +// USDC_E_WXDC_POOL, +// uint8(1), // zeroForOne (USDC.e > WXDC) +// USER_SENDER +// ); + +// // Record balances before swap +// uint256 inBefore = USDC_E.balanceOf(USER_SENDER); +// uint256 outBefore = WXDC.balanceOf(USER_SENDER); + +// // Execute swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(USDC_E), +// amountIn, +// address(WXDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify balances after swap +// uint256 inAfter = USDC_E.balanceOf(USER_SENDER); +// uint256 outAfter = WXDC.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + +// vm.stopPrank(); +// } + +// /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 5_000 * 1e6; + +// // fund the aggregator +// deal(address(USDC_E), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); + +// // Account for slot-undrain protection +// uint256 swapAmount = amountIn - 1; + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(USDC_E), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// USDC_E_WXDC_POOL, +// uint8(1), // zeroForOne (USDC.e > WXDC) +// USER_SENDER +// ); + +// // Record balances before swap +// uint256 outBefore = WXDC.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(USDC_E), +// swapAmount, +// address(WXDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify balances after swap +// uint256 outAfter = WXDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. +// // XSwap V3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. XSwap V3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } +// } + +// // ----------------------------------------------------------------------------- +// // RabbitSwap on Viction +// // ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorRabbitSwapTest is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// // Constants for RabbitSwap on Viction +// IERC20 internal constant SOROS = +// IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); +// IERC20 internal constant C98 = +// IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); +// address internal constant SOROS_C98_POOL = +// 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; + +// function setUp() public override { +// // setup for Viction network +// customRpcUrlForForking = "ETH_NODE_URI_VICTION"; +// customBlockNumberForForking = 94490946; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the user with SOROS +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(liFiDEXAggregator), amountIn); + +// // build a single-pool UniV3-style route +// bool zeroForOne = address(SOROS) > address(C98); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), // RabbitSwap uses UniV3 pool type +// SOROS_C98_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// // record balances before swap +// uint256 inBefore = SOROS.balanceOf(USER_SENDER); +// uint256 outBefore = C98.balanceOf(USER_SENDER); + +// // execute swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// // verify balances after swap +// uint256 inAfter = SOROS.balanceOf(USER_SENDER); +// uint256 outAfter = C98.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive C98"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the aggregator directly +// deal(address(SOROS), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); + +// bool zeroForOne = address(SOROS) > address(C98); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// SOROS_C98_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// uint256 outBefore = C98.balanceOf(USER_SENDER); + +// // withdraw 1 wei less to avoid slot-undrain protection +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn - 1, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 outAfter = C98.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive C98"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. +// // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, +// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. UniV3-style pools immediately revert on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// function testRevert_RabbitSwapInvalidPool() public { +// uint256 amountIn = 1_000 * 1e18; +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(liFiDEXAggregator), amountIn); + +// // build route with invalid pool address +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// address(0), // invalid pool address +// uint8(0), +// USER_SENDER +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } + +// function testRevert_RabbitSwapInvalidRecipient() public { +// uint256 amountIn = 1_000 * 1e18; +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(liFiDEXAggregator), amountIn); + +// // build route with invalid recipient +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// SOROS_C98_POOL, +// uint8(0), +// address(0) // invalid recipient +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } +// } + +// // ---------------------------------------------- +// // EnosysDexV3 on Flare +// // ---------------------------------------------- +// contract LiFiDexAggregatorEnosysDexV3Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// /// @dev HLN token on Flare +// IERC20 internal constant HLN = +// IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); + +// /// @dev USDT0 token on Flare +// IERC20 internal constant USDT0 = +// IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); + +// /// @dev The single EnosysDexV3 pool for HLN–USDT0 +// address internal constant ENOSYS_V3_POOL = +// 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; + +// /// @notice Set up a fork of Flare at block 42652369 and initialize the aggregator +// function setUp() public override { +// customRpcUrlForForking = "ETH_NODE_URI_FLARE"; +// customBlockNumberForForking = 42652369; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// /// @notice Single‐pool swap: USER sends HLN → receives USDT0 +// function test_CanSwap() public override { +// // Mint 1 000 HLN to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(HLN), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// HLN.approve(address(liFiDEXAggregator), amountIn); + +// bool zeroForOne = address(HLN) > address(USDT0); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(HLN), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), // V3‐style pool +// ENOSYS_V3_POOL, // pool address +// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1, 1 = token1→token0 +// address(USER_SENDER) // recipient +// ); + +// // Record balances before swap +// uint256 inBefore = HLN.balanceOf(USER_SENDER); +// uint256 outBefore = USDT0.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(HLN), +// amountIn, +// address(USDT0), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that HLN was spent and some USDT0 was received +// uint256 inAfter = HLN.balanceOf(USER_SENDER); +// uint256 outAfter = USDT0.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + +// vm.stopPrank(); +// } + +// /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 +// function test_CanSwap_FromDexAggregator() public override { +// // Fund the aggregator with 1 000 HLN +// uint256 amountIn = 1_000 * 1e18; +// deal(address(HLN), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); +// bool zeroForOne = address(HLN) > address(USDT0); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(HLN), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), // V3‐style pool +// ENOSYS_V3_POOL, // pool address +// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1 +// address(USER_SENDER) // recipient +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDT0.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(HLN), +// swapAmount, +// address(USDT0), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDT0 was received +// uint256 outAfter = USDT0.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. +// // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, +// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. UniV3-style pools immediately revert on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } +// } + +// // ---------------------------------------------- +// // SyncSwapV2 on Linea +// // ---------------------------------------------- +// contract LiFiDexAggregatorSyncSwapV2Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// IERC20 internal constant USDC = +// IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); +// IERC20 internal constant WETH = +// IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); +// address internal constant USDC_WETH_POOL_V1 = +// address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); +// address internal constant SYNC_SWAP_VAULT = +// address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); + +// address internal constant USDC_WETH_POOL_V2 = +// address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); + +// IERC20 internal constant USDT = +// IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); +// address internal constant USDC_USDT_POOL_V1 = +// address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); + +// /// @notice Set up a fork of Linea at block 20077881 and initialize the aggregator +// function setUp() public override { +// customRpcUrlForForking = "ETH_NODE_URI_LINEA"; +// customBlockNumberForForking = 20077881; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// /// @notice Single‐pool swap: USER sends WETH → receives USDC +// function test_CanSwap() public override { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Record balances before swap +// uint256 inBefore = WETH.balanceOf(USER_SENDER); +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that WETH was spent and some USDC_C was received +// uint256 inAfter = WETH.balanceOf(USER_SENDER); +// uint256 outAfter = USDC.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_PoolV2() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V2, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(0) // isV1Pool +// ); + +// // Record balances before swap +// uint256 inBefore = WETH.balanceOf(USER_SENDER); +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that WETH was spent and some USDC_C was received +// uint256 inAfter = WETH.balanceOf(USER_SENDER); +// uint256 outAfter = USDC.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // Fund the aggregator with 1 000 WETH +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(WETH), +// swapAmount, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDC was received +// uint256 outAfter = USDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator_PoolV2() public { +// // Fund the aggregator with 1 000 WETH +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V2, // pool address +// address(USER_SENDER), // recipient +// uint8(2) // withdrawMode +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(WETH), +// swapAmount, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDC was received +// uint256 outAfter = USDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// uint256 amountIn = 1_000e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); +// uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); + +// // +// // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) +// // +// bytes memory hop1 = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(WETH), +// uint8(1), // one pool +// FULL_SHARE, // 100% of the WETH +// uint8(PoolType.SyncSwapV2), +// USDC_WETH_POOL_V1, // the V1 pool +// SYNC_SWAP_VAULT, // “to” = the vault address +// uint8(2), // withdrawMode = 2 +// uint8(1), // isV1Pool = true +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // +// // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 +// // +// bytes memory hop2 = abi.encodePacked( +// uint8(CommandType.ProcessOnePool), +// address(USDC), +// uint8(PoolType.SyncSwapV2), +// USDC_USDT_POOL_V1, // V1 USDC⟶USDT pool +// address(USER_SENDER), // send the USDT home +// uint8(2), // withdrawMode = 2 +// uint8(1), // isV1Pool = true +// SYNC_SWAP_VAULT // vault +// ); + +// bytes memory route = bytes.concat(hop1, hop2); + +// uint256 amountOut = liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDT), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); +// uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); + +// assertEq( +// initialBalanceIn - afterBalanceIn, +// amountIn, +// "WETH spent mismatch" +// ); +// assertEq( +// amountOut, +// afterBalanceOut - initialBalanceOut, +// "USDT amountOut mismatch" +// ); +// vm.stopPrank(); +// } + +// function testRevert_V1PoolMissingVaultAddress() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(0) // vault (invalid address) +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidPoolOrRecipient() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory routeWithInvalidPool = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// address(0), // pool address (invalid address) +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidPool +// ); + +// bytes memory routeWithInvalidRecipient = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(0), // recipient (invalid address) +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidRecipient +// ); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidWithdrawMode() public { +// vm.startPrank(USER_SENDER); + +// bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address (invalid address) +// address(USER_SENDER), // recipient +// uint8(3), // withdrawMode (invalid) +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Expect revert with InvalidCallData because withdrawMode is invalid +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// 1, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidWithdrawMode +// ); + +// vm.stopPrank(); +// } +// } diff --git a/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol b/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol new file mode 100644 index 000000000..9f00222f4 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; + +contract UniV3StyleFacetTest is BaseDexFacetTest { + UniV3StyleFacet internal uniV3StyleFacet; + + function setUp() public { + customBlockNumberForForking = 18277082; + initTestBase(); + + uniV3StyleFacet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3StyleFacet + .swapUniV3 + .selector; + functionSelectors[1] = uniV3StyleFacet + .uniswapV3SwapCallback + .selector; + + addFacet(ldaDiamond, address(uniV3StyleFacet), functionSelectors); + uniV3StyleFacet = UniV3StyleFacet(address(ldaDiamond)); + + setFacetAddressInTestBase(address(uniV3StyleFacet), "UniV3StyleFacet"); + } +} diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol new file mode 100644 index 000000000..1f8cac7d1 --- /dev/null +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -0,0 +1,3383 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; +import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; +import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; +import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; +import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; +import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; +import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; +import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; +import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; +import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; +import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { TestBase } from "../../utils/TestBase.sol"; +import { TestToken as ERC20 } from "../../utils/TestToken.sol"; +import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; + +// Command codes for route processing +enum CommandType { + None, // 0 - not used + ProcessMyERC20, // 1 - processMyERC20 + ProcessUserERC20, // 2 - processUserERC20 + ProcessNative, // 3 - processNative + ProcessOnePool, // 4 - processOnePool + ProcessInsideBento, // 5 - processInsideBento + ApplyPermit // 6 - applyPermit +} + +// Pool type identifiers +enum PoolType { + UniV2, // 0 + UniV3, // 1 + WrapNative, // 2 + BentoBridge, // 3 + Trident, // 4 + Curve, // 5 + VelodromeV2, // 6 + Algebra, // 7 + iZiSwap, // 8 + SyncSwapV2 // 9 +} + +// Direction constants +enum SwapDirection { + Token1ToToken0, // 0 + Token0ToToken1 // 1 +} + +// Callback constants +enum CallbackStatus { + Disabled, // 0 + Enabled // 1 +} + +// Other constants +uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps + +contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + function hook( + address sender, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external { + emit HookCalled(sender, amount0, amount1, data); + } +} +/** + * @title LiFiDexAggregatorUpgradeTest + * @notice Base test contract with common functionality and abstractions for DEX-specific tests + */ +abstract contract LiFiDexAggregatorUpgradeTest is TestBase { + using SafeERC20 for IERC20; + + // Common variables + LiFiDEXAggregator internal liFiDEXAggregator; + address[] internal privileged; + + // Common events and errors + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + error WrongPoolReserves(); + error PoolDoesNotExist(); + + // helper function to initialize the aggregator + function _initializeDexAggregator(address owner) internal { + privileged = new address[](1); + privileged[0] = owner; + + liFiDEXAggregator = new LiFiDEXAggregator( + address(0xCAFE), + privileged, + owner + ); + vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); + } + + // Setup function for Apechain tests + function setupApechain() internal { + customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; + customBlockNumberForForking = 12912470; + fork(); + + _initializeDexAggregator(address(USER_DIAMOND_OWNER)); + } + + function setupHyperEVM() internal { + customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; + customBlockNumberForForking = 4433562; + fork(); + + _initializeDexAggregator(USER_DIAMOND_OWNER); + } + + function setUp() public virtual { + initTestBase(); + vm.label(USER_SENDER, "USER_SENDER"); + + _initializeDexAggregator(USER_DIAMOND_OWNER); + } + + function test_ContractIsSetUpCorrectly() public { + assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); + assertEq( + liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), + true + ); + assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); + } + + function testRevert_FailsIfOwnerIsZeroAddress() public { + vm.expectRevert(InvalidConfig.selector); + + liFiDEXAggregator = new LiFiDEXAggregator( + address(0xCAFE), + privileged, + address(0) + ); + } + + // ============================ Abstract DEX Tests ============================ + /** + * @notice Abstract test for basic token swapping functionality + * Each DEX implementation should override this + */ + function test_CanSwap() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap: Not implemented"); + } + + /** + * @notice Abstract test for swapping tokens from the DEX aggregator + * Each DEX implementation should override this + */ + function test_CanSwap_FromDexAggregator() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_FromDexAggregator: Not implemented"); + } + + /** + * @notice Abstract test for multi-hop swapping + * Each DEX implementation should override this + */ + function test_CanSwap_MultiHop() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_MultiHop: Not implemented"); + } +} + +// /** +// * @title VelodromeV2 tests +// * @notice Tests specific to Velodrome V2 pool type +// */ +// contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorTest { +// // ==================== Velodrome V2 specific variables ==================== +// IVelodromeV2Router internal constant VELODROME_V2_ROUTER = +// IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router +// address internal constant VELODROME_V2_FACTORY_REGISTRY = +// 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; +// IERC20 internal constant STG_TOKEN = +// IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); +// IERC20 internal constant USDC_E_TOKEN = +// IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); + +// MockVelodromeV2FlashLoanCallbackReceiver +// internal mockFlashloanCallbackReceiver; + +// // Velodrome V2 structs +// struct VelodromeV2SwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// bool stable; +// SwapDirection direction; +// bool callback; +// } + +// struct MultiHopTestParams { +// address tokenIn; +// address tokenMid; +// address tokenOut; +// address pool1; +// address pool2; +// uint256[] amounts1; +// uint256[] amounts2; +// uint256 pool1Fee; +// uint256 pool2Fee; +// } + +// struct ReserveState { +// uint256 reserve0Pool1; +// uint256 reserve1Pool1; +// uint256 reserve0Pool2; +// uint256 reserve1Pool2; +// } + +// // Setup function for Optimism tests +// function setupOptimism() internal { +// customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; +// customBlockNumberForForking = 133999121; +// initTestBase(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// function setUp() public override { +// setupOptimism(); +// } + +// // // ============================ Velodrome V2 Tests ============================ + +// // no stable swap +// function test_CanSwap() public override { +// vm.startPrank(USER_SENDER); + +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(USER_SENDER), +// to: address(USER_SENDER), +// tokenIn: ADDRESS_USDC, +// amountIn: 1_000 * 1e6, +// tokenOut: address(STG_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_NoStable_Reverse() public { +// // first perform the forward swap. +// test_CanSwap(); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(STG_TOKEN), +// amountIn: 500 * 1e18, +// tokenOut: ADDRESS_USDC, +// stable: false, +// direction: SwapDirection.Token1ToToken0, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_Stable() public { +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: ADDRESS_USDC, +// amountIn: 1_000 * 1e6, +// tokenOut: address(USDC_E_TOKEN), +// stable: true, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_Stable_Reverse() public { +// // first perform the forward stable swap. +// test_CanSwap_Stable(); + +// vm.startPrank(USER_SENDER); + +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(USDC_E_TOKEN), +// amountIn: 500 * 1e6, +// tokenOut: ADDRESS_USDC, +// stable: false, +// direction: SwapDirection.Token1ToToken0, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // fund dex aggregator contract so that the contract holds USDC +// deal(ADDRESS_USDC, address(liFiDEXAggregator), 100_000 * 1e6); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(liFiDEXAggregator), +// to: address(USER_SENDER), +// tokenIn: ADDRESS_USDC, +// amountIn: IERC20(ADDRESS_USDC).balanceOf( +// address(liFiDEXAggregator) +// ) - 1, // adjust for slot undrain protection: subtract 1 token so that the aggregator's balance isn't completely drained, matching the contract's safeguard +// tokenOut: address(USDC_E_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_FlashloanCallback() public { +// mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(USER_SENDER), +// to: address(mockFlashloanCallbackReceiver), +// tokenIn: ADDRESS_USDC, +// amountIn: 1_000 * 1e6, +// tokenOut: address(USDC_E_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: true +// }) +// ); +// vm.stopPrank(); +// } + +// // Override the abstract test with VelodromeV2 implementation +// function test_CanSwap_MultiHop() public override { +// vm.startPrank(USER_SENDER); + +// // Setup routes and get amounts +// MultiHopTestParams memory params = _setupRoutes( +// ADDRESS_USDC, +// address(STG_TOKEN), +// address(USDC_E_TOKEN), +// false, +// false +// ); + +// // Get initial reserves BEFORE the swap +// ReserveState memory initialReserves; +// ( +// initialReserves.reserve0Pool1, +// initialReserves.reserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// initialReserves.reserve0Pool2, +// initialReserves.reserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( +// USER_SENDER +// ); + +// // Build route and execute swap +// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); + +// // Approve and execute +// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// params.tokenIn, +// params.tokenOut, +// 1000 * 1e6, +// params.amounts2[1], +// params.amounts2[1] +// ); + +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// 1000 * 1e6, +// params.tokenOut, +// params.amounts2[1], +// USER_SENDER, +// route +// ); + +// _verifyUserBalances(params, initialBalance1, initialBalance2); +// _verifyReserves(params, initialReserves); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop_WithStable() public { +// vm.startPrank(USER_SENDER); + +// // Setup routes and get amounts for stable->volatile path +// MultiHopTestParams memory params = _setupRoutes( +// ADDRESS_USDC, +// address(USDC_E_TOKEN), +// address(STG_TOKEN), +// true, // stable pool for first hop +// false // volatile pool for second hop +// ); + +// // Get initial reserves BEFORE the swap +// ReserveState memory initialReserves; +// ( +// initialReserves.reserve0Pool1, +// initialReserves.reserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// initialReserves.reserve0Pool2, +// initialReserves.reserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// // Record initial balances +// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( +// USER_SENDER +// ); + +// // Build route and execute swap +// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + +// // Approve and execute +// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// params.tokenIn, +// params.tokenOut, +// 1000 * 1e6, +// params.amounts2[1], +// params.amounts2[1] +// ); + +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// 1000 * 1e6, +// params.tokenOut, +// params.amounts2[1], +// USER_SENDER, +// route +// ); + +// _verifyUserBalances(params, initialBalance1, initialBalance2); +// _verifyReserves(params, initialReserves); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidPoolOrRecipient() public { +// vm.startPrank(USER_SENDER); + +// // Get a valid pool address first for comparison +// address validPool = VELODROME_V2_ROUTER.poolFor( +// ADDRESS_USDC, +// address(STG_TOKEN), +// false, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// // Test case 1: Zero pool address +// bytes memory routeWithZeroPool = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// ADDRESS_USDC, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// address(0), +// uint8(SwapDirection.Token1ToToken0), +// USER_SENDER, +// uint8(CallbackStatus.Disabled) +// ); + +// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// ADDRESS_USDC, +// 1000 * 1e6, +// address(STG_TOKEN), +// 0, +// USER_SENDER, +// routeWithZeroPool +// ); + +// // Test case 2: Zero recipient address +// bytes memory routeWithZeroRecipient = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// ADDRESS_USDC, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// validPool, +// uint8(SwapDirection.Token1ToToken0), +// address(0), +// uint8(CallbackStatus.Disabled) +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// ADDRESS_USDC, +// 1000 * 1e6, +// address(STG_TOKEN), +// 0, +// USER_SENDER, +// routeWithZeroRecipient +// ); + +// vm.stopPrank(); +// } + +// function testRevert_WrongPoolReserves() public { +// vm.startPrank(USER_SENDER); + +// // Setup multi-hop route: USDC -> STG -> USDC.e +// MultiHopTestParams memory params = _setupRoutes( +// ADDRESS_USDC, +// address(STG_TOKEN), +// address(USDC_E_TOKEN), +// false, +// false +// ); + +// // Build multi-hop route +// bytes memory firstHop = _buildFirstHop( +// params.tokenIn, +// params.pool1, +// params.pool2, +// 1 // direction +// ); + +// bytes memory secondHop = _buildSecondHop( +// params.tokenMid, +// params.pool2, +// USER_SENDER, +// 0 // direction +// ); + +// bytes memory route = bytes.concat(firstHop, secondHop); + +// deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); + +// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); + +// // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves +// vm.mockCall( +// params.pool2, +// abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), +// abi.encode(0, 0, block.timestamp) +// ); + +// vm.expectRevert(WrongPoolReserves.selector); + +// liFiDEXAggregator.processRoute( +// ADDRESS_USDC, +// 1000 * 1e6, +// address(USDC_E_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// // ============================ Velodrome V2 Helper Functions ============================ + +// /** +// * @dev Helper function to test a VelodromeV2 swap. +// * Uses a struct to group parameters and reduce stack depth. +// */ +// function _testSwap(VelodromeV2SwapTestParams memory params) internal { +// // get expected output amounts from the router. +// IVelodromeV2Router.Route[] +// memory routes = new IVelodromeV2Router.Route[](1); +// routes[0] = IVelodromeV2Router.Route({ +// from: params.tokenIn, +// to: params.tokenOut, +// stable: params.stable, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( +// params.amountIn, +// routes +// ); +// emit log_named_uint("Expected amount out", amounts[1]); + +// // Retrieve the pool address. +// address pool = VELODROME_V2_ROUTER.poolFor( +// params.tokenIn, +// params.tokenOut, +// params.stable, +// VELODROME_V2_FACTORY_REGISTRY +// ); +// emit log_named_uint("Pool address:", uint256(uint160(pool))); + +// // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. +// CommandType commandCode = params.from == address(liFiDEXAggregator) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; + +// // build the route. +// bytes memory route = abi.encodePacked( +// uint8(commandCode), +// params.tokenIn, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// pool, +// params.direction, +// params.to, +// params.callback +// ? uint8(CallbackStatus.Enabled) +// : uint8(CallbackStatus.Disabled) +// ); + +// // approve the aggregator to spend tokenIn. +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); + +// // capture initial token balances. +// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); +// emit log_named_uint("Initial tokenIn balance", initialTokenIn); + +// address from = params.from == address(liFiDEXAggregator) +// ? USER_SENDER +// : params.from; +// if (params.callback == true) { +// vm.expectEmit(true, false, false, false); +// emit HookCalled( +// address(liFiDEXAggregator), +// 0, +// 0, +// abi.encode(params.tokenIn) +// ); +// } +// vm.expectEmit(true, true, true, true); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// amounts[1], +// amounts[1] +// ); + +// // execute the swap +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// amounts[1], +// params.to, +// route +// ); + +// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); +// emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); +// emit log_named_uint( +// "TokenOut received", +// finalTokenOut - initialTokenOut +// ); + +// assertApproxEqAbs( +// initialTokenIn - finalTokenIn, +// params.amountIn, +// 1, // 1 wei tolerance +// "TokenIn amount mismatch" +// ); +// assertEq( +// finalTokenOut - initialTokenOut, +// amounts[1], +// "TokenOut amount mismatch" +// ); +// } + +// // Helper function to set up routes and get amounts +// function _setupRoutes( +// address tokenIn, +// address tokenMid, +// address tokenOut, +// bool isStableFirst, +// bool isStableSecond +// ) private view returns (MultiHopTestParams memory params) { +// params.tokenIn = tokenIn; +// params.tokenMid = tokenMid; +// params.tokenOut = tokenOut; + +// // Setup first hop route +// IVelodromeV2Router.Route[] +// memory routes1 = new IVelodromeV2Router.Route[](1); +// routes1[0] = IVelodromeV2Router.Route({ +// from: tokenIn, +// to: tokenMid, +// stable: isStableFirst, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( +// 1000 * 1e6, +// routes1 +// ); + +// // Setup second hop route +// IVelodromeV2Router.Route[] +// memory routes2 = new IVelodromeV2Router.Route[](1); +// routes2[0] = IVelodromeV2Router.Route({ +// from: tokenMid, +// to: tokenOut, +// stable: isStableSecond, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( +// params.amounts1[1], +// routes2 +// ); + +// // Get pool addresses +// params.pool1 = VELODROME_V2_ROUTER.poolFor( +// tokenIn, +// tokenMid, +// isStableFirst, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// params.pool2 = VELODROME_V2_ROUTER.poolFor( +// tokenMid, +// tokenOut, +// isStableSecond, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// // Get pool fees info +// params.pool1Fee = IVelodromeV2PoolFactory( +// VELODROME_V2_FACTORY_REGISTRY +// ).getFee(params.pool1, isStableFirst); +// params.pool2Fee = IVelodromeV2PoolFactory( +// VELODROME_V2_FACTORY_REGISTRY +// ).getFee(params.pool2, isStableSecond); + +// return params; +// } + +// // function to build first hop of the route +// function _buildFirstHop( +// address tokenIn, +// address pool1, +// address pool2, +// uint8 direction +// ) private pure returns (bytes memory) { +// return +// abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// tokenIn, +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.VelodromeV2), +// pool1, +// direction, +// pool2, +// uint8(CallbackStatus.Disabled) +// ); +// } + +// // function to build second hop of the route +// function _buildSecondHop( +// address tokenMid, +// address pool2, +// address recipient, +// uint8 direction +// ) private pure returns (bytes memory) { +// return +// abi.encodePacked( +// uint8(CommandType.ProcessOnePool), +// tokenMid, +// uint8(PoolType.VelodromeV2), +// pool2, +// direction, +// recipient, +// uint8(CallbackStatus.Disabled) +// ); +// } + +// // route building function +// function _buildMultiHopRoute( +// MultiHopTestParams memory params, +// address recipient, +// uint8 firstHopDirection, +// uint8 secondHopDirection +// ) private pure returns (bytes memory) { +// bytes memory firstHop = _buildFirstHop( +// params.tokenIn, +// params.pool1, +// params.pool2, +// firstHopDirection +// ); + +// bytes memory secondHop = _buildSecondHop( +// params.tokenMid, +// params.pool2, +// recipient, +// secondHopDirection +// ); + +// return bytes.concat(firstHop, secondHop); +// } + +// function _verifyUserBalances( +// MultiHopTestParams memory params, +// uint256 initialBalance1, +// uint256 initialBalance2 +// ) private { +// // Verify token balances +// uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// assertApproxEqAbs( +// initialBalance1 - finalBalance1, +// 1000 * 1e6, +// 1, // 1 wei tolerance +// "Token1 spent amount mismatch" +// ); +// assertEq( +// finalBalance2 - initialBalance2, +// params.amounts2[1], +// "Token2 received amount mismatch" +// ); +// } + +// function _verifyReserves( +// MultiHopTestParams memory params, +// ReserveState memory initialReserves +// ) private { +// // Get reserves after swap +// ( +// uint256 finalReserve0Pool1, +// uint256 finalReserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// uint256 finalReserve0Pool2, +// uint256 finalReserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); +// address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); + +// // Calculate exact expected changes +// uint256 amountInAfterFees = 1000 * +// 1e6 - +// ((1000 * 1e6 * params.pool1Fee) / 10000); + +// // Assert exact reserve changes for Pool1 +// if (token0Pool1 == params.tokenIn) { +// // tokenIn is token0, so reserve0 should increase and reserve1 should decrease +// assertEq( +// finalReserve0Pool1 - initialReserves.reserve0Pool1, +// amountInAfterFees, +// "Pool1 reserve0 (tokenIn) change incorrect" +// ); +// assertEq( +// initialReserves.reserve1Pool1 - finalReserve1Pool1, +// params.amounts1[1], +// "Pool1 reserve1 (tokenMid) change incorrect" +// ); +// } else { +// // tokenIn is token1, so reserve1 should increase and reserve0 should decrease +// assertEq( +// finalReserve1Pool1 - initialReserves.reserve1Pool1, +// amountInAfterFees, +// "Pool1 reserve1 (tokenIn) change incorrect" +// ); +// assertEq( +// initialReserves.reserve0Pool1 - finalReserve0Pool1, +// params.amounts1[1], +// "Pool1 reserve0 (tokenMid) change incorrect" +// ); +// } + +// // Assert exact reserve changes for Pool2 +// if (token0Pool2 == params.tokenMid) { +// // tokenMid is token0, so reserve0 should increase and reserve1 should decrease +// assertEq( +// finalReserve0Pool2 - initialReserves.reserve0Pool2, +// params.amounts1[1] - +// ((params.amounts1[1] * params.pool2Fee) / 10000), +// "Pool2 reserve0 (tokenMid) change incorrect" +// ); +// assertEq( +// initialReserves.reserve1Pool2 - finalReserve1Pool2, +// params.amounts2[1], +// "Pool2 reserve1 (tokenOut) change incorrect" +// ); +// } else { +// // tokenMid is token1, so reserve1 should increase and reserve0 should decrease +// assertEq( +// finalReserve1Pool2 - initialReserves.reserve1Pool2, +// params.amounts1[1] - +// ((params.amounts1[1] * params.pool2Fee) / 10000), +// "Pool2 reserve1 (tokenMid) change incorrect" +// ); +// assertEq( +// initialReserves.reserve0Pool2 - finalReserve0Pool2, +// params.amounts2[1], +// "Pool2 reserve0 (tokenOut) change incorrect" +// ); +// } +// } +// } + +// contract AlgebraLiquidityAdderHelper { +// address public immutable TOKEN_0; +// address public immutable TOKEN_1; + +// constructor(address _token0, address _token1) { +// TOKEN_0 = _token0; +// TOKEN_1 = _token1; +// } + +// function addLiquidity( +// address pool, +// int24 bottomTick, +// int24 topTick, +// uint128 amount +// ) +// external +// returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) +// { +// // Get balances before +// uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Call mint +// (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( +// address(this), +// address(this), +// bottomTick, +// topTick, +// amount, +// abi.encode(TOKEN_0, TOKEN_1) +// ); + +// // Get balances after to account for fees +// uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Calculate actual amounts transferred accounting for fees +// amount0 = balance0Before - balance0After; +// amount1 = balance1Before - balance1After; + +// return (amount0, amount1, liquidityActual); +// } + +// function algebraMintCallback( +// uint256 amount0Owed, +// uint256 amount1Owed, +// bytes calldata +// ) external { +// // Check token balances +// uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Transfer what we can, limited by actual balance +// if (amount0Owed > 0) { +// uint256 amount0ToSend = amount0Owed > balance0 +// ? balance0 +// : amount0Owed; +// uint256 balance0Before = IERC20(TOKEN_0).balanceOf( +// address(msg.sender) +// ); +// IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); +// uint256 balance0After = IERC20(TOKEN_0).balanceOf( +// address(msg.sender) +// ); +// // solhint-disable-next-line gas-custom-errors +// require(balance0After > balance0Before, "Transfer failed"); +// } + +// if (amount1Owed > 0) { +// uint256 amount1ToSend = amount1Owed > balance1 +// ? balance1 +// : amount1Owed; +// uint256 balance1Before = IERC20(TOKEN_1).balanceOf( +// address(msg.sender) +// ); +// IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); +// uint256 balance1After = IERC20(TOKEN_1).balanceOf( +// address(msg.sender) +// ); +// // solhint-disable-next-line gas-custom-errors +// require(balance1After > balance1Before, "Transfer failed"); +// } +// } +// } + +// /** +// * @title Algebra tests +// * @notice Tests specific to Algebra pool type +// */ +// contract LiFiDexAggregatorAlgebraTest is LiFiDexAggregatorTest { +// address private constant APE_ETH_TOKEN = +// 0xcF800F4948D16F23333508191B1B1591daF70438; +// address private constant WETH_TOKEN = +// 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; +// address private constant ALGEBRA_FACTORY_APECHAIN = +// 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; +// address private constant ALGEBRA_QUOTER_V2_APECHAIN = +// 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; +// address private constant ALGEBRA_POOL_APECHAIN = +// 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; +// address private constant APE_ETH_HOLDER_APECHAIN = +// address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); + +// address private constant IMPOSSIBLE_POOL_ADDRESS = +// 0x0000000000000000000000000000000000000001; + +// struct AlgebraSwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// SwapDirection direction; +// bool supportsFeeOnTransfer; +// } + +// error AlgebraSwapUnexpected(); + +// function setUp() public override { +// setupApechain(); +// } + +// // Override the abstract test with Algebra implementation +// function test_CanSwap_FromDexAggregator() public override { +// // Fund LDA from whale address +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(address(liFiDEXAggregator), 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: address(liFiDEXAggregator), +// to: address(USER_SENDER), +// tokenIn: APE_ETH_TOKEN, +// amountIn: IERC20(APE_ETH_TOKEN).balanceOf( +// address(liFiDEXAggregator) +// ) - 1, +// tokenOut: address(WETH_TOKEN), +// direction: SwapDirection.Token0ToToken1, +// supportsFeeOnTransfer: true +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FeeOnTransferToken() public { +// setupApechain(); + +// uint256 amountIn = 534451326669177; +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); + +// vm.startPrank(APE_ETH_HOLDER_APECHAIN); + +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), amountIn); + +// // Build route for algebra swap with command code 2 (user funds) +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: APE_ETH_HOLDER_APECHAIN, +// pool: ALGEBRA_POOL_APECHAIN, +// supportsFeeOnTransfer: true +// }) +// ); + +// // Track initial balance +// uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( +// APE_ETH_HOLDER_APECHAIN +// ); + +// // Execute the swap +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// amountIn, +// WETH_TOKEN, +// 0, // minOut = 0 for this test +// APE_ETH_HOLDER_APECHAIN, +// route +// ); + +// // Verify balances +// uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( +// APE_ETH_HOLDER_APECHAIN +// ); +// assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); + +// vm.stopPrank(); +// } + +// function test_CanSwap() public override { +// vm.startPrank(APE_ETH_HOLDER_APECHAIN); + +// // Transfer tokens from whale to USER_SENDER +// uint256 amountToTransfer = 100 * 1e18; +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); + +// vm.stopPrank(); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: APE_ETH_TOKEN, +// amountIn: 10 * 1e18, +// tokenOut: address(WETH_TOKEN), +// direction: SwapDirection.Token0ToToken1, +// supportsFeeOnTransfer: true +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_Reverse() public { +// test_CanSwap(); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(WETH_TOKEN), +// amountIn: 5 * 1e18, +// tokenOut: APE_ETH_TOKEN, +// direction: SwapDirection.Token1ToToken0, +// supportsFeeOnTransfer: false +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { +// MultiHopTestState memory state; +// state.isFeeOnTransfer = true; + +// // Setup tokens and pools +// state = _setupTokensAndPools(state); + +// // Execute and verify swap +// _executeAndVerifyMultiHopSwap(state); +// } + +// function test_CanSwap_MultiHop() public override { +// MultiHopTestState memory state; +// state.isFeeOnTransfer = false; + +// // Setup tokens and pools +// state = _setupTokensAndPools(state); + +// // Execute and verify swap +// _executeAndVerifyMultiHopSwap(state); +// } + +// // Test that the proper error is thrown when algebra swap fails +// function testRevert_SwapUnexpected() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Create invalid pool address +// address invalidPool = address(0x999); + +// // Mock token0() call on invalid pool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Create a route with an invalid pool +// bytes memory invalidRoute = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: invalidPool, +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Mock the algebra pool to not reset lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSelector( +// IAlgebraPool.swapSupportingFeeOnInputTokens.selector +// ), +// abi.encode(0, 0) +// ); + +// // Expect the AlgebraSwapUnexpected error +// vm.expectRevert(AlgebraSwapUnexpected.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// invalidRoute +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// // Helper function to setup tokens and pools +// function _setupTokensAndPools( +// MultiHopTestState memory state +// ) private returns (MultiHopTestState memory) { +// // Create tokens +// ERC20 tokenA = new ERC20( +// "Token A", +// state.isFeeOnTransfer ? "FTA" : "TA", +// 18 +// ); +// IERC20 tokenB; +// ERC20 tokenC = new ERC20( +// "Token C", +// state.isFeeOnTransfer ? "FTC" : "TC", +// 18 +// ); + +// if (state.isFeeOnTransfer) { +// tokenB = IERC20( +// address( +// new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) +// ) +// ); +// } else { +// tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); +// } + +// state.tokenA = IERC20(address(tokenA)); +// state.tokenB = tokenB; +// state.tokenC = IERC20(address(tokenC)); + +// // Label addresses +// vm.label(address(state.tokenA), "Token A"); +// vm.label(address(state.tokenB), "Token B"); +// vm.label(address(state.tokenC), "Token C"); + +// // Mint initial token supplies +// tokenA.mint(address(this), 1_000_000 * 1e18); +// if (!state.isFeeOnTransfer) { +// ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); +// } else { +// MockFeeOnTransferToken(address(tokenB)).mint( +// address(this), +// 1_000_000 * 1e18 +// ); +// } +// tokenC.mint(address(this), 1_000_000 * 1e18); + +// // Create pools +// state.pool1 = _createAlgebraPool( +// address(state.tokenA), +// address(state.tokenB) +// ); +// state.pool2 = _createAlgebraPool( +// address(state.tokenB), +// address(state.tokenC) +// ); + +// vm.label(state.pool1, "Pool 1"); +// vm.label(state.pool2, "Pool 2"); + +// // Add liquidity +// _addLiquidityToPool( +// state.pool1, +// address(state.tokenA), +// address(state.tokenB) +// ); +// _addLiquidityToPool( +// state.pool2, +// address(state.tokenB), +// address(state.tokenC) +// ); + +// state.amountToTransfer = 100 * 1e18; +// state.amountIn = 50 * 1e18; + +// // Transfer tokens to USER_SENDER +// IERC20(address(state.tokenA)).transfer( +// USER_SENDER, +// state.amountToTransfer +// ); + +// return state; +// } + +// // Helper function to execute and verify the swap +// function _executeAndVerifyMultiHopSwap( +// MultiHopTestState memory state +// ) private { +// vm.startPrank(USER_SENDER); + +// uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( +// USER_SENDER +// ); + +// // Approve spending +// IERC20(address(state.tokenA)).approve( +// address(liFiDEXAggregator), +// state.amountIn +// ); + +// // Build route +// bytes memory route = _buildMultiHopRouteForTest(state); + +// // Execute swap +// liFiDEXAggregator.processRoute( +// address(state.tokenA), +// state.amountIn, +// address(state.tokenC), +// 0, // No minimum amount out for testing +// USER_SENDER, +// route +// ); + +// // Verify results +// _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); + +// vm.stopPrank(); +// } + +// // Helper function to build the multi-hop route for test +// function _buildMultiHopRouteForTest( +// MultiHopTestState memory state +// ) private view returns (bytes memory) { +// bytes memory firstHop = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: address(state.tokenA), +// recipient: address(liFiDEXAggregator), +// pool: state.pool1, +// supportsFeeOnTransfer: false +// }) +// ); + +// bytes memory secondHop = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessMyERC20, +// tokenIn: address(state.tokenB), +// recipient: USER_SENDER, +// pool: state.pool2, +// supportsFeeOnTransfer: state.isFeeOnTransfer +// }) +// ); + +// return bytes.concat(firstHop, secondHop); +// } + +// // Helper function to verify multi-hop results +// function _verifyMultiHopResults( +// MultiHopTestState memory state, +// uint256 initialBalanceA, +// uint256 initialBalanceC +// ) private { +// uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( +// USER_SENDER +// ); +// uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( +// USER_SENDER +// ); + +// assertApproxEqAbs( +// initialBalanceA - finalBalanceA, +// state.amountIn, +// 1, // 1 wei tolerance +// "TokenA spent amount mismatch" +// ); +// assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); + +// emit log_named_uint( +// state.isFeeOnTransfer +// ? "Output amount with fee tokens" +// : "Output amount with regular tokens", +// finalBalanceC - initialBalanceC +// ); +// } + +// // Helper function to create an Algebra pool +// function _createAlgebraPool( +// address tokenA, +// address tokenB +// ) internal returns (address pool) { +// // Call the actual Algebra factory to create a pool +// pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( +// tokenA, +// tokenB +// ); +// return pool; +// } + +// // Helper function to add liquidity to a pool +// function _addLiquidityToPool( +// address pool, +// address token0, +// address token1 +// ) internal { +// // For fee-on-transfer tokens, we need to send more to account for the fee +// // We'll use a small amount and send extra to cover fees +// uint256 initialAmount0 = 1e17; // 0.1 token +// uint256 initialAmount1 = 1e17; // 0.1 token + +// // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) +// uint256 transferAmount0 = (initialAmount0 * 110) / 100; +// uint256 transferAmount1 = (initialAmount1 * 110) / 100; + +// // Initialize with 1:1 price ratio (Q64.96 format) +// uint160 initialPrice = uint160(1 << 96); +// IAlgebraPool(pool).initialize(initialPrice); + +// // Create AlgebraLiquidityAdderHelper with safe transfer logic +// AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( +// token0, +// token1 +// ); + +// // Transfer tokens with extra amounts to account for fees +// IERC20(token0).transfer( +// address(algebraLiquidityAdderHelper), +// transferAmount0 +// ); +// IERC20(token1).transfer( +// address(algebraLiquidityAdderHelper), +// transferAmount1 +// ); + +// // Get actual balances to use for liquidity, accounting for any fees +// uint256 actualBalance0 = IERC20(token0).balanceOf( +// address(algebraLiquidityAdderHelper) +// ); +// uint256 actualBalance1 = IERC20(token1).balanceOf( +// address(algebraLiquidityAdderHelper) +// ); + +// // Use the smaller of the two balances for liquidity amount +// uint128 liquidityAmount = uint128( +// actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 +// ); + +// // Add liquidity using the actual token amounts we have +// algebraLiquidityAdderHelper.addLiquidity( +// pool, +// -887220, +// 887220, +// liquidityAmount / 2 // Use half of available liquidity to ensure success +// ); +// } + +// struct MultiHopTestState { +// IERC20 tokenA; +// IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken +// IERC20 tokenC; +// address pool1; +// address pool2; +// uint256 amountIn; +// uint256 amountToTransfer; +// bool isFeeOnTransfer; +// } + +// struct AlgebraRouteParams { +// CommandType commandCode; // 1 for contract funds, 2 for user funds +// address tokenIn; // Input token address +// address recipient; // Address receiving the output tokens +// address pool; // Algebra pool address +// bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens +// } + +// // Helper function to build route for Apechain Algebra swap +// function _buildAlgebraRoute( +// AlgebraRouteParams memory params +// ) internal view returns (bytes memory route) { +// address token0 = IAlgebraPool(params.pool).token0(); +// bool zeroForOne = (params.tokenIn == token0); +// SwapDirection direction = zeroForOne +// ? SwapDirection.Token0ToToken1 +// : SwapDirection.Token1ToToken0; + +// route = abi.encodePacked( +// params.commandCode, +// params.tokenIn, +// uint8(1), // one pool +// FULL_SHARE, // 100% share +// uint8(PoolType.Algebra), +// params.pool, +// uint8(direction), +// params.recipient, +// params.supportsFeeOnTransfer ? uint8(1) : uint8(0) +// ); + +// return route; +// } + +// // Helper function to test an Algebra swap +// function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { +// // Find or create a pool +// address pool = _getPool(params.tokenIn, params.tokenOut); + +// vm.label(pool, "AlgebraPool"); + +// // Get token0 from pool and label tokens accordingly +// address token0 = IAlgebraPool(pool).token0(); +// if (params.tokenIn == token0) { +// vm.label( +// params.tokenIn, +// string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") +// ); +// vm.label( +// params.tokenOut, +// string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") +// ); +// } else { +// vm.label( +// params.tokenIn, +// string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") +// ); +// vm.label( +// params.tokenOut, +// string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") +// ); +// } + +// // Record initial balances +// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + +// // Get expected output from QuoterV2 +// // NOTE: There may be a small discrepancy between the quoted amount and the actual output +// // because the Quoter uses the regular swap() function for simulation while the actual +// // execution may use swapSupportingFeeOnInputTokens() for fee-on-transfer tokens. +// // The Quoter cannot accurately predict transfer fees taken by the token contract itself, +// // resulting in minor "dust" differences that are normal and expected when dealing with +// // non-standard token implementations. +// uint256 expectedOutput = _getQuoteExactInput( +// params.tokenIn, +// params.tokenOut, +// params.amountIn +// ); + +// // Build the route +// CommandType commandCode = params.from == address(liFiDEXAggregator) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: commandCode, +// tokenIn: params.tokenIn, +// recipient: params.to, +// pool: pool, +// supportsFeeOnTransfer: params.supportsFeeOnTransfer +// }) +// ); + +// // Approve tokens +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); + +// // Execute the swap +// address from = params.from == address(liFiDEXAggregator) +// ? USER_SENDER +// : params.from; + +// vm.expectEmit(true, true, true, false); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// expectedOutput, +// expectedOutput +// ); + +// uint256 minOut = (expectedOutput * 995) / 1000; // 0.5% slippage + +// liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// minOut, +// params.to, +// route +// ); + +// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + +// assertApproxEqAbs( +// initialTokenIn - finalTokenIn, +// params.amountIn, +// 1, // 1 wei tolerance +// "TokenIn amount mismatch" +// ); +// assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); +// } + +// function _getPool( +// address tokenA, +// address tokenB +// ) private view returns (address pool) { +// pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( +// tokenA, +// tokenB +// ); +// if (pool == address(0)) revert PoolDoesNotExist(); +// return pool; +// } + +// function _getQuoteExactInput( +// address tokenIn, +// address tokenOut, +// uint256 amountIn +// ) private returns (uint256 amountOut) { +// (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) +// .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); +// return amountOut; +// } + +// function testRevert_AlgebraSwap_ZeroAddressPool() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on address(0) +// vm.mockCall( +// address(0), +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with address(0) as pool +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: address(0), // Zero address pool +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS +// vm.mockCall( +// IMPOSSIBLE_POOL_ADDRESS, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with IMPOSSIBLE_POOL_ADDRESS as pool +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// function testRevert_AlgebraSwap_ZeroAddressRecipient() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on the pool +// vm.mockCall( +// ALGEBRA_POOL_APECHAIN, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with address(0) as recipient +// bytes memory route = _buildAlgebraRoute( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: address(0), // Zero address recipient +// pool: ALGEBRA_POOL_APECHAIN, +// supportsFeeOnTransfer: true +// }) +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// liFiDEXAggregator.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } +// } + +// /** +// * @title LiFiDexAggregatorIzumiV3Test +// * @notice Tests specific to iZiSwap V3 pool type +// */ +// contract LiFiDexAggregatorIzumiV3Test is LiFiDexAggregatorTest { +// // ==================== iZiSwap V3 specific variables ==================== +// // Base constants +// address internal constant USDC = +// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; +// address internal constant WETH = +// 0x4200000000000000000000000000000000000006; +// address internal constant USDB_C = +// 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; + +// // iZiSwap pools +// address internal constant IZUMI_WETH_USDC_POOL = +// 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; +// address internal constant IZUMI_WETH_USDB_C_POOL = +// 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; + +// // Test parameters +// uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals +// uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals + +// // structs +// struct IzumiV3SwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// SwapDirection direction; +// } + +// struct MultiHopTestParams { +// address tokenIn; +// address tokenMid; +// address tokenOut; +// address pool1; +// address pool2; +// uint256 amountIn; +// SwapDirection direction1; +// SwapDirection direction2; +// } + +// error IzumiV3SwapUnexpected(); +// error IzumiV3SwapCallbackUnknownSource(); +// error IzumiV3SwapCallbackNotPositiveAmount(); + +// function setUp() public override { +// super.setUp(); + +// string memory baseRpc = vm.envString("ETH_NODE_URI_BASE"); +// vm.createSelectFork(baseRpc, 29831758); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); + +// // Setup labels +// vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); +// vm.label(USDC, "USDC"); +// vm.label(WETH, "WETH"); +// vm.label(USDB_C, "USDB-C"); +// vm.label(IZUMI_WETH_USDC_POOL, "WETH-USDC Pool"); +// vm.label(IZUMI_WETH_USDB_C_POOL, "WETH-USDB-C Pool"); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // Test USDC -> WETH +// deal(USDC, address(liFiDEXAggregator), AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// IzumiV3SwapTestParams({ +// from: address(liFiDEXAggregator), +// to: USER_SENDER, +// tokenIn: USDC, +// amountIn: AMOUNT_USDC, +// tokenOut: WETH, +// direction: SwapDirection.Token1ToToken0 +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// _testMultiHopSwap( +// MultiHopTestParams({ +// tokenIn: USDC, +// tokenMid: WETH, +// tokenOut: USDB_C, +// pool1: IZUMI_WETH_USDC_POOL, +// pool2: IZUMI_WETH_USDB_C_POOL, +// amountIn: AMOUNT_USDC, +// direction1: SwapDirection.Token1ToToken0, +// direction2: SwapDirection.Token0ToToken1 +// }) +// ); +// } + +// function test_CanSwap() public override { +// deal(address(USDC), USER_SENDER, AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); +// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); + +// // fix the swap data encoding +// bytes memory swapData = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// USDC, +// uint8(SwapDirection.Token1ToToken0), +// IZUMI_WETH_USDC_POOL, +// USER_RECEIVER +// ); + +// vm.expectEmit(true, true, true, false); +// emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); + +// liFiDEXAggregator.processRoute( +// USDC, +// AMOUNT_USDC, +// WETH, +// 0, +// USER_RECEIVER, +// swapData +// ); + +// vm.stopPrank(); +// } + +// function testRevert_IzumiV3SwapUnexpected() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); + +// // create invalid pool address +// address invalidPool = address(0x999); + +// // create a route with an invalid pool +// bytes memory invalidRoute = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// USDC, +// uint8(SwapDirection.Token1ToToken0), +// invalidPool, +// USER_SENDER +// ); + +// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); + +// // mock the iZiSwap pool to return without updating lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), +// abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool +// ); + +// vm.expectRevert(IzumiV3SwapUnexpected.selector); + +// liFiDEXAggregator.processRoute( +// USDC, +// AMOUNT_USDC, +// WETH, +// 0, +// USER_SENDER, +// invalidRoute +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// function testRevert_IzumiV3SwapCallbackUnknownSource() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// // create invalid pool address +// address invalidPool = address(0x999); + +// vm.prank(USER_SENDER); +// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); + +// // mock the pool to call the callback directly without setting lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), +// abi.encode(0, 0) +// ); + +// // try to call the callback directly from the pool without setting lastCalledPool +// vm.prank(invalidPool); +// vm.expectRevert(IzumiV3SwapCallbackUnknownSource.selector); +// liFiDEXAggregator.swapY2XCallback(0, AMOUNT_USDC, abi.encode(USDC)); + +// vm.clearMockedCalls(); +// } + +// function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// // set lastCalledPool to the pool address to pass the unknown source check +// vm.store( +// address(liFiDEXAggregator), +// bytes32(uint256(3)), // slot for lastCalledPool +// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) +// ); + +// // try to call the callback with zero amount +// vm.prank(IZUMI_WETH_USDC_POOL); +// vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); +// liFiDEXAggregator.swapY2XCallback( +// 0, +// 0, // zero amount should trigger the error +// abi.encode(USDC) +// ); +// } + +// function testRevert_FailsIfAmountInIsTooLarge() public { +// deal(address(WETH), USER_SENDER, type(uint256).max); + +// vm.startPrank(USER_SENDER); +// IERC20(WETH).approve(address(liFiDEXAggregator), type(uint256).max); + +// // fix the swap data encoding +// bytes memory swapData = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// WETH, +// uint8(SwapDirection.Token0ToToken1), +// IZUMI_WETH_USDC_POOL, +// USER_RECEIVER +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// WETH, +// type(uint216).max, +// USDC, +// 0, +// USER_RECEIVER, +// swapData +// ); + +// vm.stopPrank(); +// } + +// function _testSwap(IzumiV3SwapTestParams memory params) internal { +// // Fund the sender with tokens if not the contract itself +// if (params.from != address(liFiDEXAggregator)) { +// deal(params.tokenIn, params.from, params.amountIn); +// } + +// // Capture initial token balances +// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( +// params.from +// ); +// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( +// params.to +// ); + +// // Build the route based on the command type +// CommandType commandCode = params.from == address(liFiDEXAggregator) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; + +// // Construct the route +// bytes memory route = _buildIzumiV3Route( +// commandCode, +// params.tokenIn, +// uint8(params.direction == SwapDirection.Token0ToToken1 ? 1 : 0), +// IZUMI_WETH_USDC_POOL, +// params.to +// ); + +// // Approve tokens if necessary +// if (params.from == USER_SENDER) { +// vm.startPrank(USER_SENDER); +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); +// } + +// // Expect the Route event emission +// address from = params.from == address(liFiDEXAggregator) +// ? USER_SENDER +// : params.from; + +// vm.expectEmit(true, true, true, false); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// 0, // No minimum amount enforced in test +// 0 // Actual amount will be checked after the swap +// ); + +// // Execute the swap +// uint256 amountOut = liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// 0, // No minimum amount for testing +// params.to, +// route +// ); + +// if (params.from == USER_SENDER) { +// vm.stopPrank(); +// } + +// // Verify balances have changed correctly +// uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); + +// assertApproxEqAbs( +// initialBalanceIn - finalBalanceIn, +// params.amountIn, +// 1, // 1 wei tolerance because of undrain protection for dex aggregator +// "TokenIn amount mismatch" +// ); +// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); +// assertEq( +// amountOut, +// finalBalanceOut - initialBalanceOut, +// "AmountOut mismatch" +// ); + +// emit log_named_uint("Amount In", params.amountIn); +// emit log_named_uint("Amount Out", amountOut); +// } + +// function _testMultiHopSwap(MultiHopTestParams memory params) internal { +// // Fund the sender with tokens +// deal(params.tokenIn, USER_SENDER, params.amountIn); + +// // Capture initial token balances +// uint256 initialBalanceIn; +// uint256 initialBalanceOut; + +// initialBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// initialBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// // Build multi-hop route +// bytes memory route = _buildIzumiV3MultiHopRoute(params); + +// // Approve tokens +// vm.startPrank(USER_SENDER); +// IERC20(params.tokenIn).approve( +// address(liFiDEXAggregator), +// params.amountIn +// ); + +// // Execute the swap +// uint256 amountOut = liFiDEXAggregator.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// 0, // No minimum amount for testing +// USER_SENDER, +// route +// ); +// vm.stopPrank(); + +// // Verify balances have changed correctly +// uint256 finalBalanceIn; +// uint256 finalBalanceOut; + +// finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// assertEq( +// initialBalanceIn - finalBalanceIn, +// params.amountIn, +// "TokenIn amount mismatch" +// ); +// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); +// assertEq( +// amountOut, +// finalBalanceOut - initialBalanceOut, +// "AmountOut mismatch" +// ); +// } + +// function _buildIzumiV3Route( +// CommandType commandCode, +// address tokenIn, +// uint8 direction, +// address pool, +// address recipient +// ) internal pure returns (bytes memory) { +// return +// abi.encodePacked( +// uint8(commandCode), +// tokenIn, +// uint8(1), // number of pools (1) +// FULL_SHARE, // 100% share +// uint8(PoolType.iZiSwap), // pool type +// pool, +// uint8(direction), +// recipient +// ); +// } + +// function _buildIzumiV3MultiHopRoute( +// MultiHopTestParams memory params +// ) internal view returns (bytes memory) { +// // First hop: USER_ERC20 -> LDA +// bytes memory firstHop = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// params.tokenIn, +// uint8(params.direction1), +// params.pool1, +// address(liFiDEXAggregator) +// ); + +// // Second hop: MY_ERC20 (LDA) -> pool2 +// bytes memory secondHop = _buildIzumiV3Route( +// CommandType.ProcessMyERC20, +// params.tokenMid, +// uint8(params.direction2), +// params.pool2, +// USER_SENDER // final recipient +// ); + +// // Combine the two hops +// return bytes.concat(firstHop, secondHop); +// } +// } + +// ----------------------------------------------------------------------------- +// HyperswapV3 on HyperEVM +// ----------------------------------------------------------------------------- +contract LiFiDexAggregatorHyperswapV3Test is LiFiDexAggregatorUpgradeTest { + using SafeERC20 for IERC20; + + /// @dev HyperswapV3 router on HyperEVM chain + IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = + IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); + /// @dev HyperswapV3 quoter on HyperEVM chain + IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = + IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); + + /// @dev a liquid USDT on HyperEVM + IERC20 internal constant USDT0 = + IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); + /// @dev WHYPE on HyperEVM + IERC20 internal constant WHYPE = + IERC20(0x5555555555555555555555555555555555555555); + + struct HyperswapV3Params { + CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 + address tokenIn; // Input token address + address recipient; // Address receiving the output tokens + address pool; // HyperswapV3 pool address + bool zeroForOne; // Direction of the swap + } + + function setUp() public override { + setupHyperEVM(); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + + deal(address(USDT0), USER_SENDER, amountIn); + + // user approves + vm.prank(USER_SENDER); + USDT0.approve(address(liFiDEXAggregator), amountIn); + + // fetch the real pool and quote + address pool = HYPERSWAP_FACTORY.getPool( + address(USDT0), + address(WHYPE), + 3000 + ); + + // Create the params struct for quoting + IHyperswapV3QuoterV2.QuoteExactInputSingleParams + memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: amountIn, + fee: 3000, + sqrtPriceLimitX96: 0 + }); + + // Get the quote using the struct + (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + params + ); + + // build the "off-chain" route + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDT0), + uint8(1), // 1 pool + uint16(65535), // FULL_SHARE + UniV3StyleFacet.swapUniV3.selector, // UNIV3 selector + pool, + uint8(0), // zeroForOne = true if USDT0 < WHYPE + address(USER_SENDER) + ); + + // expect the Route event + vm.expectEmit(true, true, true, true); + emit Route( + USER_SENDER, + USER_SENDER, + address(USDT0), + address(WHYPE), + amountIn, + quoted, + quoted + ); + + // execute + vm.prank(USER_SENDER); + liFiDEXAggregator.processRoute( + address(USDT0), + amountIn, + address(WHYPE), + quoted, + USER_SENDER, + route + ); + } + + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + + // Fund dex aggregator contract + deal(address(USDT0), address(liFiDEXAggregator), amountIn); + + // fetch the real pool and quote + address pool = HYPERSWAP_FACTORY.getPool( + address(USDT0), + address(WHYPE), + 3000 + ); + + // Create the params struct for quoting + IHyperswapV3QuoterV2.QuoteExactInputSingleParams + memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection + fee: 3000, + sqrtPriceLimitX96: 0 + }); + + // Get the quote using the struct + (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + params + ); + + // Build route using our helper function + bytes memory route = _buildHyperswapV3Route( + HyperswapV3Params({ + commandCode: CommandType.ProcessMyERC20, + tokenIn: address(USDT0), + recipient: USER_SENDER, + pool: pool, + zeroForOne: true // USDT0 < WHYPE + }) + ); + + // expect the Route event + vm.expectEmit(true, true, true, true); + emit Route( + USER_SENDER, + USER_SENDER, + address(USDT0), + address(WHYPE), + amountIn - 1, // Account for slot undrain protection + quoted, + quoted + ); + + // execute + vm.prank(USER_SENDER); + liFiDEXAggregator.processRoute( + address(USDT0), + amountIn - 1, // Account for slot undrain protection + address(WHYPE), + quoted, + USER_SENDER, + route + ); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. + // HyperswapV3 does not support a "one-pool" second hop today, because + // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. HyperswapV3's swap() immediately reverts on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } + + function _buildHyperswapV3Route( + HyperswapV3Params memory params + ) internal pure returns (bytes memory route) { + route = abi.encodePacked( + uint8(params.commandCode), + params.tokenIn, + uint8(1), // 1 pool + FULL_SHARE, // 65535 - 100% share + UniV3StyleFacet.swapUniV3.selector, // UNIV3 selector + params.pool, + uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false + params.recipient + ); + + return route; + } +} + +// // ----------------------------------------------------------------------------- +// // LaminarV3 on HyperEVM +// // ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorLaminarV3Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// IERC20 internal constant WHYPE = +// IERC20(0x5555555555555555555555555555555555555555); +// IERC20 internal constant LHYPE = +// IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); + +// address internal constant WHYPE_LHYPE_POOL = +// 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; + +// function setUp() public override { +// setupHyperEVM(); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // Fund the user with WHYPE +// deal(address(WHYPE), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WHYPE.approve(address(liFiDEXAggregator), amountIn); + +// // Build a single-pool UniV3 route +// bool zeroForOne = address(WHYPE) > address(LHYPE); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(WHYPE), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), +// WHYPE_LHYPE_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// // Record balances +// uint256 inBefore = WHYPE.balanceOf(USER_SENDER); +// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + +// // Execute swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(WHYPE), +// amountIn, +// address(LHYPE), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify +// uint256 inAfter = WHYPE.balanceOf(USER_SENDER); +// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the aggregator directly +// deal(address(WHYPE), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); + +// bool zeroForOne = address(WHYPE) > address(LHYPE); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(WHYPE), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// WHYPE_LHYPE_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + +// // Withdraw 1 wei to avoid slot-undrain protection +// liFiDEXAggregator.processRoute( +// address(WHYPE), +// amountIn - 1, +// address(LHYPE), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. +// // Laminar V3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. Laminar V3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } +// } + +// contract LiFiDexAggregatorXSwapV3Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// address internal constant USDC_E_WXDC_POOL = +// 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; + +// /// @dev our two tokens: USDC.e and wrapped XDC +// IERC20 internal constant USDC_E = +// IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); +// IERC20 internal constant WXDC = +// IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); + +// function setUp() public override { +// customRpcUrlForForking = "ETH_NODE_URI_XDC"; +// customBlockNumberForForking = 89279495; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e6; +// deal(address(USDC_E), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// USDC_E.approve(address(liFiDEXAggregator), amountIn); + +// // Build a one-pool V3 route +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(USDC_E), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), +// USDC_E_WXDC_POOL, +// uint8(1), // zeroForOne (USDC.e > WXDC) +// USER_SENDER +// ); + +// // Record balances before swap +// uint256 inBefore = USDC_E.balanceOf(USER_SENDER); +// uint256 outBefore = WXDC.balanceOf(USER_SENDER); + +// // Execute swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(USDC_E), +// amountIn, +// address(WXDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify balances after swap +// uint256 inAfter = USDC_E.balanceOf(USER_SENDER); +// uint256 outAfter = WXDC.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + +// vm.stopPrank(); +// } + +// /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 5_000 * 1e6; + +// // fund the aggregator +// deal(address(USDC_E), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); + +// // Account for slot-undrain protection +// uint256 swapAmount = amountIn - 1; + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(USDC_E), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// USDC_E_WXDC_POOL, +// uint8(1), // zeroForOne (USDC.e > WXDC) +// USER_SENDER +// ); + +// // Record balances before swap +// uint256 outBefore = WXDC.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(USDC_E), +// swapAmount, +// address(WXDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify balances after swap +// uint256 outAfter = WXDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. +// // XSwap V3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. XSwap V3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } +// } + +// // ----------------------------------------------------------------------------- +// // RabbitSwap on Viction +// // ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorRabbitSwapTest is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// // Constants for RabbitSwap on Viction +// IERC20 internal constant SOROS = +// IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); +// IERC20 internal constant C98 = +// IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); +// address internal constant SOROS_C98_POOL = +// 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; + +// function setUp() public override { +// // setup for Viction network +// customRpcUrlForForking = "ETH_NODE_URI_VICTION"; +// customBlockNumberForForking = 94490946; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the user with SOROS +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(liFiDEXAggregator), amountIn); + +// // build a single-pool UniV3-style route +// bool zeroForOne = address(SOROS) > address(C98); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), // RabbitSwap uses UniV3 pool type +// SOROS_C98_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// // record balances before swap +// uint256 inBefore = SOROS.balanceOf(USER_SENDER); +// uint256 outBefore = C98.balanceOf(USER_SENDER); + +// // execute swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// // verify balances after swap +// uint256 inAfter = SOROS.balanceOf(USER_SENDER); +// uint256 outAfter = C98.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive C98"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the aggregator directly +// deal(address(SOROS), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); + +// bool zeroForOne = address(SOROS) > address(C98); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// SOROS_C98_POOL, +// uint8(zeroForOne ? 0 : 1), +// address(USER_SENDER) +// ); + +// uint256 outBefore = C98.balanceOf(USER_SENDER); + +// // withdraw 1 wei less to avoid slot-undrain protection +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn - 1, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 outAfter = C98.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive C98"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. +// // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, +// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. UniV3-style pools immediately revert on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// function testRevert_RabbitSwapInvalidPool() public { +// uint256 amountIn = 1_000 * 1e18; +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(liFiDEXAggregator), amountIn); + +// // build route with invalid pool address +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// address(0), // invalid pool address +// uint8(0), +// USER_SENDER +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } + +// function testRevert_RabbitSwapInvalidRecipient() public { +// uint256 amountIn = 1_000 * 1e18; +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(liFiDEXAggregator), amountIn); + +// // build route with invalid recipient +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint8(PoolType.UniV3), +// SOROS_C98_POOL, +// uint8(0), +// address(0) // invalid recipient +// ); + +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } +// } + +// // ---------------------------------------------- +// // EnosysDexV3 on Flare +// // ---------------------------------------------- +// contract LiFiDexAggregatorEnosysDexV3Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// /// @dev HLN token on Flare +// IERC20 internal constant HLN = +// IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); + +// /// @dev USDT0 token on Flare +// IERC20 internal constant USDT0 = +// IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); + +// /// @dev The single EnosysDexV3 pool for HLN–USDT0 +// address internal constant ENOSYS_V3_POOL = +// 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; + +// /// @notice Set up a fork of Flare at block 42652369 and initialize the aggregator +// function setUp() public override { +// customRpcUrlForForking = "ETH_NODE_URI_FLARE"; +// customBlockNumberForForking = 42652369; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// /// @notice Single‐pool swap: USER sends HLN → receives USDT0 +// function test_CanSwap() public override { +// // Mint 1 000 HLN to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(HLN), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// HLN.approve(address(liFiDEXAggregator), amountIn); + +// bool zeroForOne = address(HLN) > address(USDT0); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(HLN), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), // V3‐style pool +// ENOSYS_V3_POOL, // pool address +// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1, 1 = token1→token0 +// address(USER_SENDER) // recipient +// ); + +// // Record balances before swap +// uint256 inBefore = HLN.balanceOf(USER_SENDER); +// uint256 outBefore = USDT0.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(HLN), +// amountIn, +// address(USDT0), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that HLN was spent and some USDT0 was received +// uint256 inAfter = HLN.balanceOf(USER_SENDER); +// uint256 outAfter = USDT0.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + +// vm.stopPrank(); +// } + +// /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 +// function test_CanSwap_FromDexAggregator() public override { +// // Fund the aggregator with 1 000 HLN +// uint256 amountIn = 1_000 * 1e18; +// deal(address(HLN), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); +// bool zeroForOne = address(HLN) > address(USDT0); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(HLN), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.UniV3), // V3‐style pool +// ENOSYS_V3_POOL, // pool address +// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1 +// address(USER_SENDER) // recipient +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDT0.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(HLN), +// swapAmount, +// address(USDT0), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDT0 was received +// uint256 outAfter = USDT0.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. +// // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, +// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. UniV3-style pools immediately revert on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } +// } + +// // ---------------------------------------------- +// // SyncSwapV2 on Linea +// // ---------------------------------------------- +// contract LiFiDexAggregatorSyncSwapV2Test is LiFiDexAggregatorTest { +// using SafeERC20 for IERC20; + +// IERC20 internal constant USDC = +// IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); +// IERC20 internal constant WETH = +// IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); +// address internal constant USDC_WETH_POOL_V1 = +// address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); +// address internal constant SYNC_SWAP_VAULT = +// address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); + +// address internal constant USDC_WETH_POOL_V2 = +// address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); + +// IERC20 internal constant USDT = +// IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); +// address internal constant USDC_USDT_POOL_V1 = +// address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); + +// /// @notice Set up a fork of Linea at block 20077881 and initialize the aggregator +// function setUp() public override { +// customRpcUrlForForking = "ETH_NODE_URI_LINEA"; +// customBlockNumberForForking = 20077881; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + +// /// @notice Single‐pool swap: USER sends WETH → receives USDC +// function test_CanSwap() public override { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Record balances before swap +// uint256 inBefore = WETH.balanceOf(USER_SENDER); +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that WETH was spent and some USDC_C was received +// uint256 inAfter = WETH.balanceOf(USER_SENDER); +// uint256 outAfter = USDC.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_PoolV2() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V2, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(0) // isV1Pool +// ); + +// // Record balances before swap +// uint256 inBefore = WETH.balanceOf(USER_SENDER); +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that WETH was spent and some USDC_C was received +// uint256 inAfter = WETH.balanceOf(USER_SENDER); +// uint256 outAfter = USDC.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // Fund the aggregator with 1 000 WETH +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(WETH), +// swapAmount, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDC was received +// uint256 outAfter = USDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator_PoolV2() public { +// // Fund the aggregator with 1 000 WETH +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), address(liFiDEXAggregator), amountIn); + +// vm.startPrank(USER_SENDER); +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V2, // pool address +// address(USER_SENDER), // recipient +// uint8(2) // withdrawMode +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// liFiDEXAggregator.processRoute( +// address(WETH), +// swapAmount, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDC was received +// uint256 outAfter = USDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// uint256 amountIn = 1_000e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); +// uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); + +// // +// // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) +// // +// bytes memory hop1 = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(WETH), +// uint8(1), // one pool +// FULL_SHARE, // 100% of the WETH +// uint8(PoolType.SyncSwapV2), +// USDC_WETH_POOL_V1, // the V1 pool +// SYNC_SWAP_VAULT, // “to” = the vault address +// uint8(2), // withdrawMode = 2 +// uint8(1), // isV1Pool = true +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // +// // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 +// // +// bytes memory hop2 = abi.encodePacked( +// uint8(CommandType.ProcessOnePool), +// address(USDC), +// uint8(PoolType.SyncSwapV2), +// USDC_USDT_POOL_V1, // V1 USDC⟶USDT pool +// address(USER_SENDER), // send the USDT home +// uint8(2), // withdrawMode = 2 +// uint8(1), // isV1Pool = true +// SYNC_SWAP_VAULT // vault +// ); + +// bytes memory route = bytes.concat(hop1, hop2); + +// uint256 amountOut = liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDT), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); +// uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); + +// assertEq( +// initialBalanceIn - afterBalanceIn, +// amountIn, +// "WETH spent mismatch" +// ); +// assertEq( +// amountOut, +// afterBalanceOut - initialBalanceOut, +// "USDT amountOut mismatch" +// ); +// vm.stopPrank(); +// } + +// function testRevert_V1PoolMissingVaultAddress() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(0) // vault (invalid address) +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidPoolOrRecipient() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(liFiDEXAggregator), amountIn); + +// bytes memory routeWithInvalidPool = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// address(0), // pool address (invalid address) +// address(USER_SENDER), // recipient +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidPool +// ); + +// bytes memory routeWithInvalidRecipient = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address +// address(0), // recipient (invalid address) +// uint8(2), // withdrawMode +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidRecipient +// ); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidWithdrawMode() public { +// vm.startPrank(USER_SENDER); + +// bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint8(PoolType.SyncSwapV2), // SyncSwapV2 +// USDC_WETH_POOL_V1, // pool address (invalid address) +// address(USER_SENDER), // recipient +// uint8(3), // withdrawMode (invalid) +// uint8(1), // isV1Pool +// address(SYNC_SWAP_VAULT) // vault +// ); + +// // Expect revert with InvalidCallData because withdrawMode is invalid +// vm.expectRevert(InvalidCallData.selector); +// liFiDEXAggregator.processRoute( +// address(WETH), +// 1, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidWithdrawMode +// ); + +// vm.stopPrank(); +// } +// } diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol new file mode 100644 index 000000000..e2e950565 --- /dev/null +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { LdaDiamond } from "lifi/Periphery/Lda/LdaDiamond.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; + +contract LdaDiamondTest is BaseDiamondTest { + function createLdaDiamond( + address _diamondOwner + ) internal returns (LdaDiamond) { + vm.startPrank(_diamondOwner); + DiamondCutFacet diamondCut = new DiamondCutFacet(); + DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); + OwnershipFacet ownership = new OwnershipFacet(); + LdaDiamond diamond = new LdaDiamond( + _diamondOwner, + address(diamondCut) + ); + + // Add Diamond Loupe + _addDiamondLoupeSelectors(address(diamondLoupe)); + + // Add Ownership + _addOwnershipSelectors(address(ownership)); + + DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); + delete cut; + vm.stopPrank(); + return diamond; + } +} diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol new file mode 100644 index 000000000..a16fafa43 --- /dev/null +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { Test } from "forge-std/Test.sol"; + +abstract contract BaseDiamondTest is Test { + LibDiamond.FacetCut[] internal cut; + + // Common function to add Diamond Loupe selectors + function _addDiamondLoupeSelectors(address _diamondLoupe) internal { + bytes4[] memory functionSelectors = new bytes4[](5); + functionSelectors[0] = DiamondLoupeFacet.facetFunctionSelectors.selector; + functionSelectors[1] = DiamondLoupeFacet.facets.selector; + functionSelectors[2] = DiamondLoupeFacet.facetAddress.selector; + functionSelectors[3] = DiamondLoupeFacet.facetAddresses.selector; + functionSelectors[4] = DiamondLoupeFacet.supportsInterface.selector; + + cut.push( + LibDiamond.FacetCut({ + facetAddress: _diamondLoupe, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + } + + // Common function to add Ownership selectors + function _addOwnershipSelectors(address _ownership) internal { + bytes4[] memory functionSelectors = new bytes4[](4); + functionSelectors[0] = OwnershipFacet.transferOwnership.selector; + functionSelectors[1] = OwnershipFacet.cancelOwnershipTransfer.selector; + functionSelectors[2] = OwnershipFacet.confirmOwnershipTransfer.selector; + functionSelectors[3] = OwnershipFacet.owner.selector; + + cut.push( + LibDiamond.FacetCut({ + facetAddress: _ownership, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + } + + // Common function to add a facet + function addFacet( + address _diamond, + address _facet, + bytes4[] memory _selectors + ) internal { + _addFacet(_diamond, _facet, _selectors, address(0), ""); + } + + function addFacet( + address _diamond, + address _facet, + bytes4[] memory _selectors, + address _init, + bytes memory _initCallData + ) internal { + _addFacet(_diamond, _facet, _selectors, _init, _initCallData); + } + + function _addFacet( + address _diamond, + address _facet, + bytes4[] memory _selectors, + address _init, + bytes memory _initCallData + ) internal { + vm.startPrank(OwnershipFacet(_diamond).owner()); + cut.push( + LibDiamond.FacetCut({ + facetAddress: _facet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: _selectors + }) + ); + + DiamondCutFacet(_diamond).diamondCut(cut, _init, _initCallData); + + delete cut; + vm.stopPrank(); + } +} diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/utils/DiamondTest.sol index e985f35bb..6009072ce 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/utils/DiamondTest.sol @@ -8,11 +8,9 @@ import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; -import { Test } from "forge-std/Test.sol"; - -contract DiamondTest is Test { - LibDiamond.FacetCut[] internal cut; +import { BaseDiamondTest } from "./BaseDiamondTest.sol"; +contract DiamondTest is BaseDiamondTest { function createDiamond( address _diamondOwner, address _pauserWallet @@ -30,53 +28,16 @@ contract DiamondTest is Test { address(diamondCut) ); - bytes4[] memory functionSelectors; - - // Diamond Loupe - - functionSelectors = new bytes4[](5); - functionSelectors[0] = DiamondLoupeFacet - .facetFunctionSelectors - .selector; - functionSelectors[1] = DiamondLoupeFacet.facets.selector; - functionSelectors[2] = DiamondLoupeFacet.facetAddress.selector; - functionSelectors[3] = DiamondLoupeFacet.facetAddresses.selector; - functionSelectors[4] = DiamondLoupeFacet.supportsInterface.selector; - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(diamondLoupe), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) - ); - - // Ownership Facet + // Add Diamond Loupe + _addDiamondLoupeSelectors(address(diamondLoupe)); - functionSelectors = new bytes4[](4); - functionSelectors[0] = OwnershipFacet.transferOwnership.selector; - functionSelectors[1] = OwnershipFacet.cancelOwnershipTransfer.selector; - functionSelectors[2] = OwnershipFacet - .confirmOwnershipTransfer - .selector; - functionSelectors[3] = OwnershipFacet.owner.selector; - - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(ownership), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) - ); - - // PeripheryRegistryFacet - functionSelectors = new bytes4[](2); - functionSelectors[0] = PeripheryRegistryFacet - .registerPeripheryContract - .selector; - functionSelectors[1] = PeripheryRegistryFacet - .getPeripheryContract - .selector; + // Add Ownership + _addOwnershipSelectors(address(ownership)); + // Add PeripheryRegistry + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = PeripheryRegistryFacet.registerPeripheryContract.selector; + functionSelectors[1] = PeripheryRegistryFacet.getPeripheryContract.selector; cut.push( LibDiamond.FacetCut({ facetAddress: address(periphery), @@ -85,12 +46,11 @@ contract DiamondTest is Test { }) ); - // EmergencyPauseFacet + // Add EmergencyPause functionSelectors = new bytes4[](3); functionSelectors[0] = emergencyPause.removeFacet.selector; functionSelectors[1] = emergencyPause.pauseDiamond.selector; functionSelectors[2] = emergencyPause.unpauseDiamond.selector; - cut.push( LibDiamond.FacetCut({ facetAddress: address(emergencyPause), @@ -100,54 +60,8 @@ contract DiamondTest is Test { ); DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); - delete cut; - vm.stopPrank(); return diamond; } - - function addFacet( - LiFiDiamond _diamond, - address _facet, - bytes4[] memory _selectors - ) internal { - _addFacet(_diamond, _facet, _selectors, address(0), ""); - } - - function addFacet( - LiFiDiamond _diamond, - address _facet, - bytes4[] memory _selectors, - address _init, - bytes memory _initCallData - ) internal { - _addFacet(_diamond, _facet, _selectors, _init, _initCallData); - } - - function _addFacet( - LiFiDiamond _diamond, - address _facet, - bytes4[] memory _selectors, - address _init, - bytes memory _initCallData - ) internal { - vm.startPrank(OwnershipFacet(address(_diamond)).owner()); - cut.push( - LibDiamond.FacetCut({ - facetAddress: _facet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: _selectors - }) - ); - - DiamondCutFacet(address(_diamond)).diamondCut( - cut, - _init, - _initCallData - ); - - delete cut; - vm.stopPrank(); - } } diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 9acb12f7f..39f9c9c04 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -7,6 +7,7 @@ import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; import { LibSwap } from "lifi/Libraries/LibSwap.sol"; import { UniswapV2Router02 } from "../utils/Interfaces.sol"; import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; +import { LdaDiamondTest, LdaDiamond } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; @@ -89,7 +90,7 @@ contract ReentrancyChecker is DSTest { //common utilities for forge tests // solhint-disable max-states-count -abstract contract TestBase is Test, DiamondTest, ILiFi { +abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { address internal _facetTestContractAddress; uint64 internal currentTxId; bytes32 internal nextUser = keccak256(abi.encodePacked("user address")); @@ -99,6 +100,7 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { ERC20 internal dai; ERC20 internal weth; LiFiDiamond internal diamond; + LdaDiamond internal ldaDiamond; FeeCollector internal feeCollector; ILiFi.BridgeData internal bridgeData; LibSwap.SwapData[] internal swapData; @@ -323,6 +325,7 @@ abstract contract TestBase is Test, DiamondTest, ILiFi { // deploy & configure diamond diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From 2672c1cf165ce7c7763e572b0069b3f816c8feac Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 23 Jul 2025 17:07:56 +0200 Subject: [PATCH 002/220] New changes for LDA --- src/Periphery/Lda/Facets/AlgebraFacet.sol | 88 ++++ src/Periphery/Lda/Facets/CoreRouteFacet.sol | 447 +++++++++--------- src/Periphery/Lda/Facets/CurveFacet.sol | 89 ++++ src/Periphery/Lda/Facets/IzumiV3Facet.sol | 26 + src/Periphery/Lda/Facets/SyncSwapFacet.sol | 16 + src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 115 +++++ src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 13 + templates/facetTest.template.hbs | 2 +- test/solidity/Facets/AccessManagerFacet.t.sol | 4 +- test/solidity/Facets/AcrossFacet.t.sol | 2 +- test/solidity/Facets/AcrossFacetPacked.t.sol | 2 +- .../solidity/Facets/AcrossFacetPackedV3.t.sol | 2 +- test/solidity/Facets/AcrossFacetV3.t.sol | 2 +- test/solidity/Facets/AllBridgeFacet.t.sol | 2 +- .../solidity/Facets/ArbitrumBridgeFacet.t.sol | 2 +- test/solidity/Facets/CBridge/CBridge.t.sol | 2 +- .../CBridge/CBridgeAndFeeCollection.t.sol | 2 +- .../Facets/CBridge/CBridgeFacetPacked.t.sol | 2 +- .../Facets/CBridge/CBridgeRefund.t.sol | 2 +- .../Facets/CelerCircleBridgeFacet.t.sol | 2 +- test/solidity/Facets/ChainflipFacet.t.sol | 2 +- test/solidity/Facets/DeBridgeDlnFacet.t.sol | 2 +- test/solidity/Facets/DexManagerFacet.t.sol | 4 +- test/solidity/Facets/GasZipFacet.t.sol | 2 +- test/solidity/Facets/GenericSwapFacet.t.sol | 2 +- test/solidity/Facets/GenericSwapFacetV3.t.sol | 4 +- test/solidity/Facets/GlacisFacet.t.sol | 2 +- test/solidity/Facets/GnosisBridgeFacet.t.sol | 2 +- test/solidity/Facets/HopFacet.t.sol | 6 +- .../HopFacetOptimizedL1.t.sol | 2 +- .../HopFacetOptimizedL2.t.sol | 2 +- .../HopFacetPacked/HopFacetPackedL1.t.sol | 4 +- .../HopFacetPacked/HopFacetPackedL2.t.sol | 4 +- test/solidity/Facets/MayanFacet.t.sol | 2 +- .../OmniBridgeFacet/OmniBridgeFacet.t.sol | 2 +- .../OmniBridgeFacet/OmniBridgeL2Facet.t.sol | 2 +- .../solidity/Facets/OptimismBridgeFacet.t.sol | 2 +- test/solidity/Facets/PioneerFacet.t.sol | 2 +- test/solidity/Facets/PolygonBridgeFacet.t.sol | 2 +- test/solidity/Facets/RelayFacet.t.sol | 2 +- test/solidity/Facets/SquidFacet.t.sol | 2 +- test/solidity/Facets/StargateFacetV2.t.sol | 2 +- test/solidity/Facets/SymbiosisFacet.t.sol | 2 +- test/solidity/Facets/ThorSwapFacet.t.sol | 2 +- .../Gas/CBridgeFacetPackedARB.gas.t.sol | 2 +- .../Gas/CBridgeFacetPackedETH.gas.t.sol | 6 +- test/solidity/Gas/Hop.t.sol | 2 +- test/solidity/Gas/HopFacetPackedARB.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedETH.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedPOL.gas.t.sol | 2 +- test/solidity/Helpers/SwapperV2.t.sol | 2 +- test/solidity/Periphery/GasZipPeriphery.t.sol | 2 +- .../Lda/Facets/UniV3StyleFacet.t.sol | 2 +- .../Periphery/LidoWrapper/LidoWrapper.t.sol | 4 +- test/solidity/utils/BaseDiamondTest.sol | 7 +- 55 files changed, 629 insertions(+), 286 deletions(-) create mode 100644 src/Periphery/Lda/Facets/AlgebraFacet.sol create mode 100644 src/Periphery/Lda/Facets/CurveFacet.sol create mode 100644 src/Periphery/Lda/Facets/IzumiV3Facet.sol create mode 100644 src/Periphery/Lda/Facets/SyncSwapFacet.sol create mode 100644 src/Periphery/Lda/Facets/VelodromeV2Facet.sol diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol new file mode 100644 index 000000000..28208f9a5 --- /dev/null +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibInputStream } from "../../../Libraries/LibInputStream.sol"; +import { LibCallbackManager } from "../../../Libraries/LibCallbackManager.sol"; +import { LibUniV3Logic } from "../../../Libraries/LibUniV3Logic.sol"; +import { IAlgebraPool } from "../../../Interfaces/IAlgebraPool.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { InvalidCallData } from "../../../Errors/GenericErrors.sol"; + +/// @title Algebra Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles Algebra swaps with callback management +/// @custom:version 1.0.0 +contract AlgebraFacet { + using LibInputStream for uint256; + using SafeERC20 for IERC20; + + /// Constants /// + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + address internal constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; + + /// Errors /// + error AlgebraSwapUnexpected(); + + function swapAlgebra( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + address pool = stream.readAddress(); + bool direction = stream.readUint8() > 0; + address recipient = stream.readAddress(); + bool supportsFeeOnTransfer = stream.readUint8() > 0; + + if ( + pool == address(0) || + pool == IMPOSSIBLE_POOL_ADDRESS || + recipient == address(0) + ) revert InvalidCallData(); + + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom( + msg.sender, + address(this), + uint256(amountIn) + ); + } + + LibCallbackManager.arm(pool); + + if (supportsFeeOnTransfer) { + IAlgebraPool(pool).swapSupportingFeeOnInputTokens( + address(this), + recipient, + direction, + int256(amountIn), + direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + abi.encode(tokenIn) + ); + } else { + IAlgebraPool(pool).swap( + recipient, + direction, + int256(amountIn), + direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + abi.encode(tokenIn) + ); + } + + if (LibCallbackManager.data().expected != IMPOSSIBLE_POOL_ADDRESS) { + revert AlgebraSwapUnexpected(); + } + + return 0; // Actual output amount tracked via balance checks in CoreFacet + } + + function algebraSwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index b392875be..6eb1df463 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -1,232 +1,229 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -// import { LibDiamond } from "../Libraries/LibDiamond.sol"; -// import { LibAccess } from "../Libraries/LibAccess.sol"; -// import { LibAsset } from "../Libraries/LibAsset.sol"; -// import { LibInputStream } from "../Libraries/LibInputStream.sol"; -// import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -// import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; -// import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; -// import { InvalidCallData, InvalidAmount } from "../Errors/GenericErrors.sol"; - -// /// @title Core Route Facet -// /// @author LI.FI (https://li.fi) -// /// @notice Handles route processing and selector-based swap dispatching for LDA 2.0 -// /// @custom:version 2.0.0 -// contract CoreRouteFacet is ReentrancyGuard { -// using SafeERC20 for IERC20; -// using SafeERC20 for IERC20Permit; -// using LibInputStream for uint256; - -// /// Constants /// -// address internal constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; -// address internal constant INTERNAL_INPUT_SOURCE = address(0); - -// /// Events /// -// event Route( -// address indexed from, -// address to, -// address indexed tokenIn, -// address indexed tokenOut, -// uint256 amountIn, -// uint256 amountOutMin, -// uint256 amountOut -// ); - -// /// Errors /// -// error MinimalOutputBalanceViolation(uint256 amountOut); -// error MinimalInputBalanceViolation(uint256 available, uint256 required); -// error UnknownCommandCode(); -// error SwapFailed(); - -// /// External Methods /// - -// /// @notice Processes a route for swapping tokens using selector-based dispatch -// /// @param tokenIn Token to swap from -// /// @param amountIn Amount of tokenIn to swap -// /// @param tokenOut Token to swap to -// /// @param amountOutMin Minimum amount of tokenOut expected -// /// @param to Recipient of the final tokens -// /// @param route Encoded route data containing swap instructions -// function processRoute( -// address tokenIn, -// uint256 amountIn, -// address tokenOut, -// uint256 amountOutMin, -// address to, -// bytes calldata route -// ) external payable nonReentrant returns (uint256 amountOut) { -// return _processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route); -// } - -// /// Internal Methods /// - -// /// @notice Internal route processing logic -// function _processRouteInternal( -// address tokenIn, -// uint256 amountIn, -// address tokenOut, -// uint256 amountOutMin, -// address to, -// bytes calldata route -// ) private returns (uint256 amountOut) { -// uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS -// ? 0 -// : IERC20(tokenIn).balanceOf(msg.sender); -// uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS -// ? address(to).balance -// : IERC20(tokenOut).balanceOf(to); - -// uint256 realAmountIn = amountIn; -// { -// uint256 step = 0; -// uint256 stream = LibInputStream.createStream(route); -// while (stream.isNotEmpty()) { -// uint8 commandCode = stream.readUint8(); -// if (commandCode == 1) { -// uint256 usedAmount = _processMyERC20(stream); -// if (step == 0) realAmountIn = usedAmount; -// } else if (commandCode == 2) { -// _processUserERC20(stream, amountIn); -// } else if (commandCode == 3) { -// uint256 usedAmount = _processNative(stream); -// if (step == 0) realAmountIn = usedAmount; -// } else if (commandCode == 4) { -// _processOnePool(stream); -// } else if (commandCode == 6) { -// _applyPermit(tokenIn, stream); -// } else { -// revert UnknownCommandCode(); -// } -// ++step; -// } -// } - -// uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS -// ? 0 -// : IERC20(tokenIn).balanceOf(msg.sender); -// if (balanceInFinal + amountIn < balanceInInitial) { -// revert MinimalInputBalanceViolation( -// balanceInFinal + amountIn, -// balanceInInitial -// ); -// } - -// uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS -// ? address(to).balance -// : IERC20(tokenOut).balanceOf(to); -// if (balanceOutFinal < balanceOutInitial + amountOutMin) { -// revert MinimalOutputBalanceViolation( -// balanceOutFinal - balanceOutInitial -// ); -// } - -// amountOut = balanceOutFinal - balanceOutInitial; - -// emit Route( -// msg.sender, -// to, -// tokenIn, -// tokenOut, -// realAmountIn, -// amountOutMin, -// amountOut -// ); -// } - -// /// @notice Applies ERC-2612 permit -// function _applyPermit(address tokenIn, uint256 stream) private { -// uint256 value = stream.readUint(); -// uint256 deadline = stream.readUint(); -// uint8 v = stream.readUint8(); -// bytes32 r = stream.readBytes32(); -// bytes32 s = stream.readBytes32(); -// IERC20Permit(tokenIn).safePermit( -// msg.sender, -// address(this), -// value, -// deadline, -// v, -// r, -// s -// ); -// } - -// /// @notice Processes native coin -// function _processNative(uint256 stream) private returns (uint256 amountTotal) { -// amountTotal = address(this).balance; -// _distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal); -// } - -// /// @notice Processes ERC20 token from this contract balance -// function _processMyERC20(uint256 stream) private returns (uint256 amountTotal) { -// address token = stream.readAddress(); -// amountTotal = IERC20(token).balanceOf(address(this)); -// unchecked { -// if (amountTotal > 0) amountTotal -= 1; // slot undrain protection -// } -// _distributeAndSwap(stream, address(this), token, amountTotal); -// } - -// /// @notice Processes ERC20 token from msg.sender balance -// function _processUserERC20(uint256 stream, uint256 amountTotal) private { -// address token = stream.readAddress(); -// _distributeAndSwap(stream, msg.sender, token, amountTotal); -// } - -// /// @notice Processes single pool (tokens already at pool) -// function _processOnePool(uint256 stream) private { -// address token = stream.readAddress(); -// _dispatchSwap(stream, INTERNAL_INPUT_SOURCE, token, 0); -// } - -// /// @notice Distributes amount to pools and calls swap for each -// function _distributeAndSwap( -// uint256 stream, -// address from, -// address tokenIn, -// uint256 amountTotal -// ) private { -// uint8 num = stream.readUint8(); -// unchecked { -// for (uint256 i = 0; i < num; ++i) { -// uint16 share = stream.readUint16(); -// uint256 amount = (amountTotal * share) / type(uint16).max; -// amountTotal -= amount; -// _dispatchSwap(stream, from, tokenIn, amount); -// } -// } -// } - -// /// @notice Dispatches swap using selector-based approach -// /// @dev This is the core of the selector-based dispatch system -// function _dispatchSwap( -// uint256 stream, -// address from, -// address tokenIn, -// uint256 amountIn -// ) private { -// // Read the function selector from the stream -// bytes4 selector = stream.readBytes4(); +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { LibAccess } from "lifi/Libraries/LibAccess.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; +import { InvalidCallData, InvalidAmount } from "lifi/Errors/GenericErrors.sol"; + +/// @title Core Route Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles route processing and selector-based swap dispatching for LDA 2.0 +/// @custom:version 2.0.0 +contract CoreRouteFacet is ReentrancyGuard { + using SafeERC20 for IERC20; + using SafeERC20 for IERC20Permit; + using LibInputStream for uint256; + + /// Constants /// + address internal constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address internal constant INTERNAL_INPUT_SOURCE = address(0); + + /// Events /// + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + + /// Errors /// + error MinimalOutputBalanceViolation(uint256 amountOut); + error MinimalInputBalanceViolation(uint256 available, uint256 required); + error UnknownCommandCode(); + error SwapFailed(); + error UnknownSelector(); + + /// External Methods /// + + /// @notice Processes a route for swapping tokens using selector-based dispatch + /// @param tokenIn Token to swap from + /// @param amountIn Amount of tokenIn to swap + /// @param tokenOut Token to swap to + /// @param amountOutMin Minimum amount of tokenOut expected + /// @param to Recipient of the final tokens + /// @param route Encoded route data containing swap instructions + function processRoute( + address tokenIn, + uint256 amountIn, + address tokenOut, + uint256 amountOutMin, + address to, + bytes calldata route + ) external payable nonReentrant returns (uint256 amountOut) { + return _processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route); + } + + /// Internal Methods /// + + /// @notice Internal route processing logic + function _processRouteInternal( + address tokenIn, + uint256 amountIn, + address tokenOut, + uint256 amountOutMin, + address to, + bytes calldata route + ) private returns (uint256 amountOut) { + uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS + ? 0 + : IERC20(tokenIn).balanceOf(msg.sender); + uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS + ? address(to).balance + : IERC20(tokenOut).balanceOf(to); + + uint256 realAmountIn = amountIn; + { + uint256 step = 0; + uint256 stream = LibInputStream.createStream(route); + while (stream.isNotEmpty()) { + uint8 commandCode = stream.readUint8(); + if (commandCode == 1) { + uint256 usedAmount = _processMyERC20(stream); + if (step == 0) realAmountIn = usedAmount; + } else if (commandCode == 2) { + _processUserERC20(stream, amountIn); + } else if (commandCode == 3) { + uint256 usedAmount = _processNative(stream); + if (step == 0) realAmountIn = usedAmount; + } else if (commandCode == 4) { + _processOnePool(stream); + } else if (commandCode == 6) { + _applyPermit(tokenIn, stream); + } else { + revert UnknownCommandCode(); + } + ++step; + } + } + + uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS + ? 0 + : IERC20(tokenIn).balanceOf(msg.sender); + if (balanceInFinal + amountIn < balanceInInitial) { + revert MinimalInputBalanceViolation( + balanceInFinal + amountIn, + balanceInInitial + ); + } + + uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS + ? address(to).balance + : IERC20(tokenOut).balanceOf(to); + if (balanceOutFinal < balanceOutInitial + amountOutMin) { + revert MinimalOutputBalanceViolation( + balanceOutFinal - balanceOutInitial + ); + } + + amountOut = balanceOutFinal - balanceOutInitial; + + emit Route( + msg.sender, + to, + tokenIn, + tokenOut, + realAmountIn, + amountOutMin, + amountOut + ); + } + + /// @notice Applies ERC-2612 permit + function _applyPermit(address tokenIn, uint256 stream) private { + uint256 value = stream.readUint(); + uint256 deadline = stream.readUint(); + uint8 v = stream.readUint8(); + bytes32 r = stream.readBytes32(); + bytes32 s = stream.readBytes32(); + IERC20Permit(tokenIn).safePermit( + msg.sender, + address(this), + value, + deadline, + v, + r, + s + ); + } + + /// @notice Processes native coin + function _processNative(uint256 stream) private returns (uint256 amountTotal) { + amountTotal = address(this).balance; + _distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal); + } + + /// @notice Processes ERC20 token from this contract balance + function _processMyERC20(uint256 stream) private returns (uint256 amountTotal) { + address token = stream.readAddress(); + amountTotal = IERC20(token).balanceOf(address(this)); + unchecked { + if (amountTotal > 0) amountTotal -= 1; // slot undrain protection + } + _distributeAndSwap(stream, address(this), token, amountTotal); + } + + /// @notice Processes ERC20 token from msg.sender balance + function _processUserERC20(uint256 stream, uint256 amountTotal) private { + address token = stream.readAddress(); + _distributeAndSwap(stream, msg.sender, token, amountTotal); + } + + /// @notice Processes single pool (tokens already at pool) + function _processOnePool(uint256 stream) private { + address token = stream.readAddress(); + _dispatchSwap(stream, INTERNAL_INPUT_SOURCE, token, 0); + } + + /// @notice Distributes amount to pools and calls swap for each + function _distributeAndSwap( + uint256 stream, + address from, + address tokenIn, + uint256 amountTotal + ) private { + uint8 num = stream.readUint8(); + unchecked { + for (uint256 i = 0; i < num; ++i) { + uint16 share = stream.readUint16(); + uint256 amount = (amountTotal * share) / type(uint16).max; + amountTotal -= amount; + _dispatchSwap(stream, from, tokenIn, amount); + } + } + } + + /// @notice Dispatches swap using selector-based approach + /// @dev This is the core of the selector-based dispatch system + function _dispatchSwap( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) private { + // Read the function selector from the stream + bytes4 selector = stream.readBytes4(); -// // Get the facet address for this selector from diamond storage -// LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); -// address facet = ds.selectorToFacetAndPosition[selector].facetAddress; - -// if (facet == address(0)) { -// revert SwapFailed(); -// } + // Look up facet address directly from Diamond storage + address facet = LibDiamond.facetAddress(selector); + if (facet == address(0)) revert UnknownSelector(); -// // Prepare calldata: selector + remaining stream + additional parameters -// bytes memory data = abi.encodePacked(selector, stream, from, tokenIn, amountIn); + // Prepare calldata: selector + remaining stream + additional parameters + bytes memory data = abi.encodePacked(selector, stream, from, tokenIn, amountIn); -// // Execute the swap via delegatecall to the facet -// (bool success, bytes memory result) = facet.delegatecall(data); -// if (!success) { -// revert SwapFailed(); -// } + // Execute the swap via delegatecall to the facet + (bool success, bytes memory result) = facet.delegatecall(data); + if (!success) { + revert SwapFailed(); + } -// // Note: Individual facets can return amounts if needed, but for now we rely on balance checks -// } -// } \ No newline at end of file + // Note: Individual facets can return amounts if needed, but for now we rely on balance checks + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol new file mode 100644 index 000000000..ce2d53af8 --- /dev/null +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibInputStream } from "../../../Libraries/LibInputStream.sol"; +import { LibCallbackManager } from "../../../Libraries/LibCallbackManager.sol"; +import { LibAsset } from "../../../Libraries/LibAsset.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ICurve } from "../../../Interfaces/ICurve.sol"; +import { ICurveLegacy } from "../../../Interfaces/ICurveLegacy.sol"; +import { InvalidCallData } from "../../../Errors/GenericErrors.sol"; + +/// @title Curve Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles Curve swaps with callback management +/// @custom:version 1.0.0 +contract CurveFacet { + using LibInputStream for uint256; + using SafeERC20 for IERC20; + using LibAsset for IERC20; + + /// Constants /// + address internal constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /// @notice Curve pool swap. Legacy pools that don't return amountOut and have native coins are not supported + /// @param stream [pool, poolType, fromIndex, toIndex, recipient, output token] + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token + /// @param amountIn Amount of tokenIn to take for swap + function swapCurve( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + address pool = stream.readAddress(); + uint8 poolType = stream.readUint8(); + int128 fromIndex = int8(stream.readUint8()); + int128 toIndex = int8(stream.readUint8()); + address to = stream.readAddress(); + address tokenOut = stream.readAddress(); + + uint256 amountOut; + if (tokenIn == NATIVE_ADDRESS) { + amountOut = ICurve(pool).exchange{ value: amountIn }( + fromIndex, + toIndex, + amountIn, + 0 + ); + } else { + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom( + msg.sender, + address(this), + amountIn + ); + } + IERC20(tokenIn).approveSafe(pool, amountIn); + if (poolType == 0) { + amountOut = ICurve(pool).exchange( + fromIndex, + toIndex, + amountIn, + 0 + ); + } else { + uint256 balanceBefore = IERC20(tokenOut).balanceOf( + address(this) + ); + ICurveLegacy(pool).exchange(fromIndex, toIndex, amountIn, 0); + uint256 balanceAfter = IERC20(tokenOut).balanceOf( + address(this) + ); + amountOut = balanceAfter - balanceBefore; + } + } + + if (to != address(this)) { + if (tokenOut == NATIVE_ADDRESS) { + LibAsset.transferNative(to, amountOut); + } else { + IERC20(tokenOut).safeTransfer(to, amountOut); + } + } + + return amountOut; + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol new file mode 100644 index 000000000..c026987e8 --- /dev/null +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -0,0 +1,26 @@ +// src/Periphery/Lda/Facets/IzumiV3Facet.sol +contract IzumiV3Facet { + using LibInputStream for uint256; + using LibCallbackManager for *; + + function swapIzumiV3( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + // Move IzumiV3 swap logic here + } + + function swapX2YCallback(uint256 amountX, bytes calldata data) external { + LibCallbackManager.verifyCallbackSender(); + _handleIzumiV3SwapCallback(amountX, data); + LibCallbackManager.clear(); + } + + function swapY2XCallback(uint256 amountY, bytes calldata data) external { + LibCallbackManager.verifyCallbackSender(); + _handleIzumiV3SwapCallback(amountY, data); + LibCallbackManager.clear(); + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/SyncSwapFacet.sol b/src/Periphery/Lda/Facets/SyncSwapFacet.sol new file mode 100644 index 000000000..fc2502a68 --- /dev/null +++ b/src/Periphery/Lda/Facets/SyncSwapFacet.sol @@ -0,0 +1,16 @@ + + + +// src/Periphery/Lda/Facets/SyncSwapFacet.sol +contract SyncSwapFacet { + using LibInputStream for uint256; + + function swapSyncSwap( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + // Move SyncSwap logic here + } +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 4566feefa..318fc1db3 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -10,6 +10,7 @@ import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; /// @notice Handles Uniswap V3 swaps with callback management /// @custom:version 1.0.0 contract UniV3StyleFacet { + using LibCallbackManager for *; using LibInputStream for uint256; /// @notice Executes a UniswapV3 swap @@ -31,6 +32,120 @@ contract UniV3StyleFacet { int256 amount0Delta, int256 amount1Delta, bytes calldata data + ) external { + LibCallbackManager.verifyCallbackSender(); + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); + } + + function pancakeV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function ramsesV2SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function xeiV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function dragonswapV2SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function agniSwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function fusionXV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function vvsV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function supV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function zebraV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function hyperswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function laminarV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function xswapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function rabbitSwapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external LibCallbackManager.onlyExpectedCallback { + LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + } + + function enosysdexV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data ) external LibCallbackManager.onlyExpectedCallback { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol new file mode 100644 index 000000000..eaf327a24 --- /dev/null +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -0,0 +1,13 @@ +// src/Periphery/Lda/Facets/VelodromeV2Facet.sol +contract VelodromeV2Facet { + using LibInputStream for uint256; + + function swapVelodromeV2( + uint256 stream, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + // Move Velodrome V2 swap logic here + } +} \ No newline at end of file diff --git a/templates/facetTest.template.hbs b/templates/facetTest.template.hbs index 8b9771e31..e9d84b022 100644 --- a/templates/facetTest.template.hbs +++ b/templates/facetTest.template.hbs @@ -45,7 +45,7 @@ contract {{titleCase name}}FacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address({{camelCase name}}Facet), functionSelectors); + addFacet(address(diamond), address({{camelCase name}}Facet), functionSelectors); {{camelCase name}}Facet = Test{{titleCase name}}Facet(address(diamond)); {{camelCase name}}Facet.addDex(ADDRESS_UNISWAP); {{camelCase name}}Facet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AccessManagerFacet.t.sol b/test/solidity/Facets/AccessManagerFacet.t.sol index c2308850d..9bef61f12 100644 --- a/test/solidity/Facets/AccessManagerFacet.t.sol +++ b/test/solidity/Facets/AccessManagerFacet.t.sol @@ -29,11 +29,11 @@ contract AccessManagerFacetTest is TestBase { allowedFunctionSelectors[1] = accessMgr .addressCanExecuteMethod .selector; - addFacet(diamond, address(accessMgr), allowedFunctionSelectors); + addFacet(address(diamond), address(accessMgr), allowedFunctionSelectors); bytes4[] memory restrictedFunctionSelectors = new bytes4[](1); restrictedFunctionSelectors[0] = restricted.restrictedMethod.selector; - addFacet(diamond, address(restricted), restrictedFunctionSelectors); + addFacet(address(diamond), address(restricted), restrictedFunctionSelectors); accessMgr = AccessManagerFacet(address(diamond)); restricted = RestrictedContract(address(diamond)); diff --git a/test/solidity/Facets/AcrossFacet.t.sol b/test/solidity/Facets/AcrossFacet.t.sol index d0ad2444d..a44332d55 100644 --- a/test/solidity/Facets/AcrossFacet.t.sol +++ b/test/solidity/Facets/AcrossFacet.t.sol @@ -50,7 +50,7 @@ contract AcrossFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(acrossFacet), functionSelectors); + addFacet(address(diamond), address(acrossFacet), functionSelectors); acrossFacet = TestAcrossFacet(address(diamond)); acrossFacet.addDex(ADDRESS_UNISWAP); acrossFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AcrossFacetPacked.t.sol b/test/solidity/Facets/AcrossFacetPacked.t.sol index bb12e9644..57c190a0e 100644 --- a/test/solidity/Facets/AcrossFacetPacked.t.sol +++ b/test/solidity/Facets/AcrossFacetPacked.t.sol @@ -108,7 +108,7 @@ contract AcrossFacetPackedTest is TestBase { .selector; // add facet to diamond - addFacet(diamond, address(acrossFacetPacked), functionSelectors); + addFacet(address(diamond), address(acrossFacetPacked), functionSelectors); acrossFacetPacked = AcrossFacetPacked(payable(address(diamond))); /// Prepare parameters diff --git a/test/solidity/Facets/AcrossFacetPackedV3.t.sol b/test/solidity/Facets/AcrossFacetPackedV3.t.sol index 0ccec4640..48defc167 100644 --- a/test/solidity/Facets/AcrossFacetPackedV3.t.sol +++ b/test/solidity/Facets/AcrossFacetPackedV3.t.sol @@ -115,7 +115,7 @@ contract AcrossFacetPackedV3Test is TestBase { .selector; // add facet to diamond - addFacet(diamond, address(acrossFacetPackedV3), functionSelectors); + addFacet(address(diamond), address(acrossFacetPackedV3), functionSelectors); acrossFacetPackedV3 = AcrossFacetPackedV3(payable(address(diamond))); /// Prepare parameters diff --git a/test/solidity/Facets/AcrossFacetV3.t.sol b/test/solidity/Facets/AcrossFacetV3.t.sol index 2d3c4911f..0f3910f57 100644 --- a/test/solidity/Facets/AcrossFacetV3.t.sol +++ b/test/solidity/Facets/AcrossFacetV3.t.sol @@ -56,7 +56,7 @@ contract AcrossFacetV3Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(acrossFacetV3), functionSelectors); + addFacet(address(diamond), address(acrossFacetV3), functionSelectors); acrossFacetV3 = TestAcrossFacetV3(address(diamond)); acrossFacetV3.addDex(ADDRESS_UNISWAP); acrossFacetV3.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AllBridgeFacet.t.sol b/test/solidity/Facets/AllBridgeFacet.t.sol index b2c42cf5b..17aa5d692 100644 --- a/test/solidity/Facets/AllBridgeFacet.t.sol +++ b/test/solidity/Facets/AllBridgeFacet.t.sol @@ -78,7 +78,7 @@ contract AllBridgeFacetTest is TestBaseFacet, LiFiData { .selector; functionSelectors[4] = allBridgeFacet.getAllBridgeChainId.selector; - addFacet(diamond, address(allBridgeFacet), functionSelectors); + addFacet(address(diamond), address(allBridgeFacet), functionSelectors); allBridgeFacet = TestAllBridgeFacet(address(diamond)); allBridgeFacet.addDex(ADDRESS_UNISWAP); allBridgeFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol index 4d60827c3..9b6d27e3a 100644 --- a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol +++ b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol @@ -57,7 +57,7 @@ contract ArbitrumBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(arbitrumBridgeFacet), functionSelectors); + addFacet(address(diamond), address(arbitrumBridgeFacet), functionSelectors); arbitrumBridgeFacet = TestArbitrumBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/CBridge/CBridge.t.sol b/test/solidity/Facets/CBridge/CBridge.t.sol index b7e5bedb9..2ace03982 100644 --- a/test/solidity/Facets/CBridge/CBridge.t.sol +++ b/test/solidity/Facets/CBridge/CBridge.t.sol @@ -89,7 +89,7 @@ contract CBridgeFacetTest is TestBaseFacet { functionSelectors[3] = cBridge.setFunctionApprovalBySignature.selector; functionSelectors[4] = cBridge.triggerRefund.selector; - addFacet(diamond, address(cBridge), functionSelectors); + addFacet(address(diamond), address(cBridge), functionSelectors); cBridge = TestCBridgeFacet(address(diamond)); cBridge.addDex(address(uniswap)); diff --git a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol index c892b350c..8fd7d1ff0 100644 --- a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol @@ -43,7 +43,7 @@ contract CBridgeAndFeeCollectionTest is TestBase { functionSelectors[2] = cBridge.addDex.selector; functionSelectors[3] = cBridge.setFunctionApprovalBySignature.selector; - addFacet(diamond, address(cBridge), functionSelectors); + addFacet(address(diamond), address(cBridge), functionSelectors); cBridge = TestCBridgeFacet(address(diamond)); cBridge.addDex(address(uniswap)); diff --git a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol index 5330e87f4..542acd2c8 100644 --- a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol @@ -86,7 +86,7 @@ contract CBridgeFacetPackedTest is TestBase { .selector; functionSelectors[8] = cBridgeFacetPacked.triggerRefund.selector; - addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); + addFacet(address(diamond), address(cBridgeFacetPacked), functionSelectors); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare parameters diff --git a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol index 96b3faecb..227bb1854 100644 --- a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol @@ -93,7 +93,7 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { selector[0] = withdrawFacet.executeCallAndWithdraw.selector; vm.startPrank(OWNER_ADDRESS); - addFacet(diamond, address(withdrawFacet), selector); + addFacet(address(diamond), address(withdrawFacet), selector); vm.stopPrank(); withdrawFacet = WithdrawFacet(address(diamond)); diff --git a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol index 95c8d67bb..273b10e4c 100644 --- a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol +++ b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol @@ -54,7 +54,7 @@ contract CelerCircleBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(celerCircleBridgeFacet), functionSelectors); + addFacet(address(diamond), address(celerCircleBridgeFacet), functionSelectors); celerCircleBridgeFacet = TestCelerCircleBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/ChainflipFacet.t.sol b/test/solidity/Facets/ChainflipFacet.t.sol index 115b73697..972329d52 100644 --- a/test/solidity/Facets/ChainflipFacet.t.sol +++ b/test/solidity/Facets/ChainflipFacet.t.sol @@ -59,7 +59,7 @@ contract ChainflipFacetTest is TestBaseFacet, LiFiData { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(chainflipFacet), functionSelectors); + addFacet(address(diamond), address(chainflipFacet), functionSelectors); chainflipFacet = TestChainflipFacet(address(diamond)); chainflipFacet.addDex(ADDRESS_UNISWAP); chainflipFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/DeBridgeDlnFacet.t.sol b/test/solidity/Facets/DeBridgeDlnFacet.t.sol index d98da4bd8..0c0529c48 100644 --- a/test/solidity/Facets/DeBridgeDlnFacet.t.sol +++ b/test/solidity/Facets/DeBridgeDlnFacet.t.sol @@ -62,7 +62,7 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { functionSelectors[5] = deBridgeDlnFacet.getDeBridgeChainId.selector; functionSelectors[6] = DeBridgeDlnFacet.initDeBridgeDln.selector; - addFacet(diamond, address(deBridgeDlnFacet), functionSelectors); + addFacet(address(diamond), address(deBridgeDlnFacet), functionSelectors); deBridgeDlnFacet = TestDeBridgeDlnFacet(address(diamond)); deBridgeDlnFacet.addDex(ADDRESS_UNISWAP); deBridgeDlnFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/DexManagerFacet.t.sol b/test/solidity/Facets/DexManagerFacet.t.sol index 2cbfc7107..405755beb 100644 --- a/test/solidity/Facets/DexManagerFacet.t.sol +++ b/test/solidity/Facets/DexManagerFacet.t.sol @@ -49,7 +49,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { .selector; functionSelectors[7] = DexManagerFacet.isFunctionApproved.selector; - addFacet(diamond, address(dexMgr), functionSelectors); + addFacet(address(diamond), address(dexMgr), functionSelectors); // add AccessManagerFacet to be able to whitelist addresses for execution of protected functions accessMgr = new AccessManagerFacet(); @@ -57,7 +57,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { functionSelectors = new bytes4[](2); functionSelectors[0] = accessMgr.setCanExecute.selector; functionSelectors[1] = accessMgr.addressCanExecuteMethod.selector; - addFacet(diamond, address(accessMgr), functionSelectors); + addFacet(address(diamond), address(accessMgr), functionSelectors); accessMgr = AccessManagerFacet(address(diamond)); dexMgr = DexManagerFacet(address(diamond)); diff --git a/test/solidity/Facets/GasZipFacet.t.sol b/test/solidity/Facets/GasZipFacet.t.sol index af5eff276..10e0739a8 100644 --- a/test/solidity/Facets/GasZipFacet.t.sol +++ b/test/solidity/Facets/GasZipFacet.t.sol @@ -67,7 +67,7 @@ contract GasZipFacetTest is TestBaseFacet { functionSelectors[5] = gasZipFacet .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(gasZipFacet), functionSelectors); + addFacet(address(diamond), address(gasZipFacet), functionSelectors); gasZipFacet = TestGasZipFacet(payable(address(diamond))); diff --git a/test/solidity/Facets/GenericSwapFacet.t.sol b/test/solidity/Facets/GenericSwapFacet.t.sol index bedd98665..a2e4bf983 100644 --- a/test/solidity/Facets/GenericSwapFacet.t.sol +++ b/test/solidity/Facets/GenericSwapFacet.t.sol @@ -40,7 +40,7 @@ contract GenericSwapFacetTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(genericSwapFacet), functionSelectors); + addFacet(address(diamond), address(genericSwapFacet), functionSelectors); genericSwapFacet = TestGenericSwapFacet(address(diamond)); genericSwapFacet.addDex(address(uniswap)); diff --git a/test/solidity/Facets/GenericSwapFacetV3.t.sol b/test/solidity/Facets/GenericSwapFacetV3.t.sol index 40f4ad0f5..69ab33746 100644 --- a/test/solidity/Facets/GenericSwapFacetV3.t.sol +++ b/test/solidity/Facets/GenericSwapFacetV3.t.sol @@ -73,7 +73,7 @@ contract GenericSwapFacetV3Test is TestBase, TestHelpers { functionSelectors[3] = genericSwapFacet .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(genericSwapFacet), functionSelectors); + addFacet(address(diamond), address(genericSwapFacet), functionSelectors); // add genericSwapFacet (v3) to diamond bytes4[] memory functionSelectorsV3 = new bytes4[](6); @@ -96,7 +96,7 @@ contract GenericSwapFacetV3Test is TestBase, TestHelpers { .swapTokensMultipleV3NativeToERC20 .selector; - addFacet(diamond, address(genericSwapFacetV3), functionSelectorsV3); + addFacet(address(diamond), address(genericSwapFacetV3), functionSelectorsV3); genericSwapFacet = TestGenericSwapFacet(address(diamond)); genericSwapFacetV3 = TestGenericSwapFacetV3(address(diamond)); diff --git a/test/solidity/Facets/GlacisFacet.t.sol b/test/solidity/Facets/GlacisFacet.t.sol index 4426fbd2a..d82aed5b9 100644 --- a/test/solidity/Facets/GlacisFacet.t.sol +++ b/test/solidity/Facets/GlacisFacet.t.sol @@ -59,7 +59,7 @@ abstract contract GlacisFacetTestBase is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(glacisFacet), functionSelectors); + addFacet(address(diamond), address(glacisFacet), functionSelectors); glacisFacet = TestGlacisFacet(address(diamond)); glacisFacet.addDex(ADDRESS_UNISWAP); glacisFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/GnosisBridgeFacet.t.sol b/test/solidity/Facets/GnosisBridgeFacet.t.sol index d72ea4d4c..cd9de45f0 100644 --- a/test/solidity/Facets/GnosisBridgeFacet.t.sol +++ b/test/solidity/Facets/GnosisBridgeFacet.t.sol @@ -62,7 +62,7 @@ contract GnosisBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(gnosisBridgeFacet), functionSelectors); + addFacet(address(diamond), address(gnosisBridgeFacet), functionSelectors); gnosisBridgeFacet = TestGnosisBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacet.t.sol b/test/solidity/Facets/HopFacet.t.sol index 392ec2495..ee7454c0e 100644 --- a/test/solidity/Facets/HopFacet.t.sol +++ b/test/solidity/Facets/HopFacet.t.sol @@ -54,7 +54,7 @@ contract HopFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](3); configs[0] = HopFacet.Config(ADDRESS_USDC, USDC_BRIDGE); @@ -247,7 +247,7 @@ contract HopFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond2, address(hopFacet2), functionSelectors); + addFacet(address(diamond), address(hopFacet2), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](3); configs[0] = HopFacet.Config(ADDRESS_USDC, USDC_BRIDGE); @@ -282,7 +282,7 @@ contract HopFacetTest is TestBaseFacet { functionSelectors[2] = hopFacet2.initHop.selector; functionSelectors[3] = hopFacet2.registerBridge.selector; - addFacet(diamond, address(hopFacet2), functionSelectors); + addFacet(address(diamond), address(hopFacet2), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](1); configs[0] = HopFacet.Config(addressUSDCPolygon, ammWrapperPolygon); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol index 5d1258289..929805cc2 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol @@ -56,7 +56,7 @@ contract HopFacetOptimizedL1Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); hopFacet = TestHopFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol index 2d5d78582..25ff28ef9 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol @@ -60,7 +60,7 @@ contract HopFacetOptimizedL2Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); hopFacet = TestHopFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol index 5c2840fe2..ef7e3baab 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol @@ -113,7 +113,7 @@ contract HopFacetPackedL1Test is TestBase { .encode_startBridgeTokensViaHopL1ERC20Packed .selector; - addFacet(diamond, address(hopFacetPacked), functionSelectors); + addFacet(address(diamond), address(hopFacetPacked), functionSelectors); hopFacetPacked = HopFacetPacked(address(diamond)); /// Approval @@ -131,7 +131,7 @@ contract HopFacetPackedL1Test is TestBase { .setApprovalForBridges .selector; addFacet( - diamond, + address(diamond), address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol index d3e30cae7..4b1ded25a 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol @@ -120,7 +120,7 @@ contract HopFacetPackedL2Test is TestBase { .encode_startBridgeTokensViaHopL1ERC20Packed .selector; - addFacet(diamond, address(hopFacetPacked), functionSelectors); + addFacet(address(diamond), address(hopFacetPacked), functionSelectors); hopFacetPacked = HopFacetPacked(address(diamond)); /// Approval @@ -150,7 +150,7 @@ contract HopFacetPackedL2Test is TestBase { .setApprovalForBridges .selector; addFacet( - diamond, + address(diamond), address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Facets/MayanFacet.t.sol b/test/solidity/Facets/MayanFacet.t.sol index d56f8a5b3..a5e1c6bda 100644 --- a/test/solidity/Facets/MayanFacet.t.sol +++ b/test/solidity/Facets/MayanFacet.t.sol @@ -111,7 +111,7 @@ contract MayanFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(mayanBridgeFacet), functionSelectors); + addFacet(address(diamond), address(mayanBridgeFacet), functionSelectors); mayanBridgeFacet = TestMayanFacet(address(diamond)); mayanBridgeFacet.addDex(ADDRESS_UNISWAP); mayanBridgeFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol index ae3eab85c..b033fbfc6 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol @@ -53,7 +53,7 @@ contract OmniBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(omniBridgeFacet), functionSelectors); + addFacet(address(diamond), address(omniBridgeFacet), functionSelectors); omniBridgeFacet = TestOmniBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol index 1c1e458b5..a7193d270 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol @@ -62,7 +62,7 @@ contract OmniBridgeL2FacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(omniBridgeFacet), functionSelectors); + addFacet(address(diamond), address(omniBridgeFacet), functionSelectors); omniBridgeFacet = TestOmniBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/OptimismBridgeFacet.t.sol b/test/solidity/Facets/OptimismBridgeFacet.t.sol index c9e7c1b71..30ff689e9 100644 --- a/test/solidity/Facets/OptimismBridgeFacet.t.sol +++ b/test/solidity/Facets/OptimismBridgeFacet.t.sol @@ -63,7 +63,7 @@ contract OptimismBridgeFacetTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(optimismBridgeFacet), functionSelectors); + addFacet(address(diamond), address(optimismBridgeFacet), functionSelectors); OptimismBridgeFacet.Config[] memory configs = new OptimismBridgeFacet.Config[](1); diff --git a/test/solidity/Facets/PioneerFacet.t.sol b/test/solidity/Facets/PioneerFacet.t.sol index 17143740b..6675c65d7 100644 --- a/test/solidity/Facets/PioneerFacet.t.sol +++ b/test/solidity/Facets/PioneerFacet.t.sol @@ -50,7 +50,7 @@ contract PioneerFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(basePioneerFacet), functionSelectors); + addFacet(address(diamond), address(basePioneerFacet), functionSelectors); pioneerFacet = TestPioneerFacet(address(diamond)); pioneerFacet.addDex(ADDRESS_UNISWAP); pioneerFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/PolygonBridgeFacet.t.sol b/test/solidity/Facets/PolygonBridgeFacet.t.sol index dbecd9f57..d7131e7e0 100644 --- a/test/solidity/Facets/PolygonBridgeFacet.t.sol +++ b/test/solidity/Facets/PolygonBridgeFacet.t.sol @@ -53,7 +53,7 @@ contract PolygonBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(polygonBridgeFacet), functionSelectors); + addFacet(address(diamond), address(polygonBridgeFacet), functionSelectors); polygonBridgeFacet = TestPolygonBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 133f64c2b..6d94e5db1 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -68,7 +68,7 @@ contract RelayFacetTest is TestBaseFacet, LiFiData { functionSelectors[4] = relayFacet.getMappedChainId.selector; functionSelectors[5] = relayFacet.setConsumedId.selector; - addFacet(diamond, address(relayFacet), functionSelectors); + addFacet(address(diamond), address(relayFacet), functionSelectors); relayFacet = TestRelayFacet(address(diamond)); relayFacet.addDex(ADDRESS_UNISWAP); relayFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/SquidFacet.t.sol b/test/solidity/Facets/SquidFacet.t.sol index 76354282f..9b371c28b 100644 --- a/test/solidity/Facets/SquidFacet.t.sol +++ b/test/solidity/Facets/SquidFacet.t.sol @@ -58,7 +58,7 @@ contract SquidFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(squidFacet), functionSelectors); + addFacet(address(diamond), address(squidFacet), functionSelectors); squidFacet = TestSquidFacet(address(diamond)); squidFacet.addDex(ADDRESS_UNISWAP); squidFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/StargateFacetV2.t.sol b/test/solidity/Facets/StargateFacetV2.t.sol index 299513b7a..6a415a579 100644 --- a/test/solidity/Facets/StargateFacetV2.t.sol +++ b/test/solidity/Facets/StargateFacetV2.t.sol @@ -75,7 +75,7 @@ contract StargateFacetV2Test is TestBaseFacet { .selector; functionSelectors[4] = stargateFacetV2.tokenMessaging.selector; - addFacet(diamond, address(stargateFacetV2), functionSelectors); + addFacet(address(diamond), address(stargateFacetV2), functionSelectors); stargateFacetV2 = TestStargateFacetV2(payable(address(diamond))); // whitelist DEX and feeCollector addresses and function selectors in diamond diff --git a/test/solidity/Facets/SymbiosisFacet.t.sol b/test/solidity/Facets/SymbiosisFacet.t.sol index 7742e213d..2beb9cb19 100644 --- a/test/solidity/Facets/SymbiosisFacet.t.sol +++ b/test/solidity/Facets/SymbiosisFacet.t.sol @@ -49,7 +49,7 @@ contract SymbiosisFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(symbiosisFacet), functionSelectors); + addFacet(address(diamond), address(symbiosisFacet), functionSelectors); symbiosisFacet = TestSymbiosisFacet(address(diamond)); diff --git a/test/solidity/Facets/ThorSwapFacet.t.sol b/test/solidity/Facets/ThorSwapFacet.t.sol index 2d8aff2e3..5174a7d3e 100644 --- a/test/solidity/Facets/ThorSwapFacet.t.sol +++ b/test/solidity/Facets/ThorSwapFacet.t.sol @@ -43,7 +43,7 @@ contract ThorSwapFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(thorSwapFacet), functionSelectors); + addFacet(address(diamond), address(thorSwapFacet), functionSelectors); thorSwapFacet = TestThorSwapFacet(address(diamond)); thorSwapFacet.addDex(ADDRESS_UNISWAP); diff --git a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol index d492404c7..0ad9826ad 100644 --- a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol @@ -57,7 +57,7 @@ contract CBridgeGasARBTest is TestBase { .encode_startBridgeTokensViaCBridgeERC20Packed .selector; - addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); + addFacet(address(diamond), address(cBridgeFacetPacked), functionSelectors); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare parameters diff --git a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol index 43810c59f..0cfc47158 100644 --- a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol @@ -71,7 +71,7 @@ contract CBridgeGasETHTest is TestBase { .encode_startBridgeTokensViaCBridgeERC20Packed .selector; - addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); + addFacet(address(diamond), address(cBridgeFacetPacked), functionSelectors); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare CBridgeFacet @@ -82,7 +82,7 @@ contract CBridgeGasETHTest is TestBase { .startBridgeTokensViaCBridge .selector; - addFacet(diamond, address(cBridgeFacet), functionSelectors2); + addFacet(address(diamond), address(cBridgeFacet), functionSelectors2); cBridgeFacet = CBridgeFacet(address(diamond)); /// Perpare parameters @@ -170,7 +170,7 @@ contract CBridgeGasETHTest is TestBase { .setApprovalForBridges .selector; addFacet( - diamond, + address(diamond), address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Gas/Hop.t.sol b/test/solidity/Gas/Hop.t.sol index 5785539ef..3dcaf0431 100644 --- a/test/solidity/Gas/Hop.t.sol +++ b/test/solidity/Gas/Hop.t.sol @@ -25,7 +25,7 @@ contract HopGasTest is TestBase { functionSelectors[0] = hopFacet.initHop.selector; functionSelectors[1] = hopFacet.startBridgeTokensViaHop.selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); hopFacet = HopFacet(address(diamond)); HopFacet.Config[] memory config = new HopFacet.Config[](1); diff --git a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol index e590bbc04..8a2ed50bd 100644 --- a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol @@ -98,7 +98,7 @@ pragma solidity ^0.8.17; // .encoder_startBridgeTokensViaHopL2ERC20Packed // .selector; -// addFacet(diamond, address(hopFacetPacked), functionSelectors); +// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol index 38c992f81..e707c644b 100644 --- a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol @@ -70,7 +70,7 @@ pragma solidity ^0.8.17; // .startBridgeTokensViaHopL1ERC20Min // .selector; -// addFacet(diamond, address(hopFacetPacked), functionSelectors); +// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol index daefa092a..8f0e894ee 100644 --- a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol @@ -99,7 +99,7 @@ pragma solidity ^0.8.17; // .encoder_startBridgeTokensViaHopL2ERC20Packed // .selector; -// addFacet(diamond, address(hopFacetPacked), functionSelectors); +// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Helpers/SwapperV2.t.sol b/test/solidity/Helpers/SwapperV2.t.sol index 2a6a177cc..218ba838b 100644 --- a/test/solidity/Helpers/SwapperV2.t.sol +++ b/test/solidity/Helpers/SwapperV2.t.sol @@ -51,7 +51,7 @@ contract SwapperV2Test is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(swapper), functionSelectors); + addFacet(address(diamond), address(swapper), functionSelectors); swapper = TestSwapperV2(address(diamond)); swapper.addDex(address(amm)); diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 03fe4323e..b276e8496 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -471,7 +471,7 @@ contract GasZipPeripheryTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(_gnosisBridgeFacet), functionSelectors); + addFacet(address(diamond), address(_gnosisBridgeFacet), functionSelectors); _gnosisBridgeFacet = TestGnosisBridgeFacet(address(diamond)); diff --git a/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol b/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol index 9f00222f4..45e984e22 100644 --- a/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol @@ -20,7 +20,7 @@ contract UniV3StyleFacetTest is BaseDexFacetTest { .uniswapV3SwapCallback .selector; - addFacet(ldaDiamond, address(uniV3StyleFacet), functionSelectors); + addFacet(address(ldaDiamond), address(uniV3StyleFacet), functionSelectors); uniV3StyleFacet = UniV3StyleFacet(address(ldaDiamond)); setFacetAddressInTestBase(address(uniV3StyleFacet), "UniV3StyleFacet"); diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol index 56cb2e768..b87063a88 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol @@ -99,7 +99,7 @@ contract LidoWrapperTest is TestBase, LiFiData { functionSelectors[4] = relayFacet.getMappedChainId.selector; functionSelectors[5] = relayFacet.setConsumedId.selector; - addFacet(diamond, address(relayFacet), functionSelectors); + addFacet(address(diamond), address(relayFacet), functionSelectors); // slither-disable-next-line reentrancy-no-eth relayFacet = TestRelayFacet(address(diamond)); @@ -505,7 +505,7 @@ contract LidoWrapperTest is TestBase, LiFiData { .selector; // add facet to diamond and store diamond with facet interface in variable - addFacet(diamond, address(genericSwapFacetV3), functionSelectors); + addFacet(address(diamond), address(genericSwapFacetV3), functionSelectors); genericSwapFacetV3 = GenericSwapFacetV3(address(diamond)); } } diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol index a16fafa43..42b20bc7c 100644 --- a/test/solidity/utils/BaseDiamondTest.sol +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -45,12 +45,11 @@ abstract contract BaseDiamondTest is Test { ); } - // Common function to add a facet function addFacet( address _diamond, address _facet, bytes4[] memory _selectors - ) internal { + ) public virtual { _addFacet(_diamond, _facet, _selectors, address(0), ""); } @@ -60,7 +59,7 @@ abstract contract BaseDiamondTest is Test { bytes4[] memory _selectors, address _init, bytes memory _initCallData - ) internal { + ) public virtual { _addFacet(_diamond, _facet, _selectors, _init, _initCallData); } @@ -70,7 +69,7 @@ abstract contract BaseDiamondTest is Test { bytes4[] memory _selectors, address _init, bytes memory _initCallData - ) internal { + ) internal virtual { vm.startPrank(OwnershipFacet(_diamond).owner()); cut.push( LibDiamond.FacetCut({ From 64962b98319faa707adf4aef14347046ec8ff717 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 24 Jul 2025 18:12:41 +0200 Subject: [PATCH 003/220] changes --- src/Interfaces/ICurve.sol | 15 +++ src/Interfaces/ICurveLegacy.sol | 15 +++ src/Periphery/Lda/Facets/AlgebraFacet.sol | 21 ++-- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 48 ++++++--- src/Periphery/Lda/Facets/CurveFacet.sol | 55 +++++++++-- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 98 +++++++++++++++++-- src/Periphery/Lda/Facets/SyncSwapFacet.sol | 61 +++++++++++- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 58 ++++++++--- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 93 +++++++++++++++++- 9 files changed, 402 insertions(+), 62 deletions(-) create mode 100644 src/Interfaces/ICurve.sol create mode 100644 src/Interfaces/ICurveLegacy.sol diff --git a/src/Interfaces/ICurve.sol b/src/Interfaces/ICurve.sol new file mode 100644 index 000000000..a972bffc4 --- /dev/null +++ b/src/Interfaces/ICurve.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +/// @title Interface for Curve +/// @author LI.FI (https://li.fi) +/// @custom:version 1.0.0 +interface ICurve { + function exchange( + int128 i, + int128 j, + uint256 dx, + // solhint-disable-next-line var-name-mixedcase + uint256 min_dy + ) external payable returns (uint256); +} diff --git a/src/Interfaces/ICurveLegacy.sol b/src/Interfaces/ICurveLegacy.sol new file mode 100644 index 000000000..7b418b193 --- /dev/null +++ b/src/Interfaces/ICurveLegacy.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +/// @title Interface for Curve +/// @author LI.FI (https://li.fi) +/// @custom:version 1.0.0 +interface ICurveLegacy { + function exchange( + int128 i, + int128 j, + uint256 dx, + // solhint-disable-next-line var-name-mixedcase + uint256 min_dy + ) external payable; +} diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 28208f9a5..4d56069e0 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -19,8 +19,8 @@ contract AlgebraFacet { /// Constants /// uint160 internal constant MIN_SQRT_RATIO = 4295128739; - uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - address internal constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; + uint160 internal constant MAX_SQRT_RATIO = + 1461446703485210103287273052203988822378723970342; /// Errors /// error AlgebraSwapUnexpected(); @@ -36,11 +36,8 @@ contract AlgebraFacet { address recipient = stream.readAddress(); bool supportsFeeOnTransfer = stream.readUint8() > 0; - if ( - pool == address(0) || - pool == IMPOSSIBLE_POOL_ADDRESS || - recipient == address(0) - ) revert InvalidCallData(); + if (pool == address(0) || recipient == address(0)) + revert InvalidCallData(); if (from == msg.sender) { IERC20(tokenIn).safeTransferFrom( @@ -71,10 +68,10 @@ contract AlgebraFacet { ); } - if (LibCallbackManager.data().expected != IMPOSSIBLE_POOL_ADDRESS) { + if (LibCallbackManager.callbackStorage().expected != address(0)) { revert AlgebraSwapUnexpected(); } - + return 0; // Actual output amount tracked via balance checks in CoreFacet } @@ -82,7 +79,9 @@ contract AlgebraFacet { int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } -} \ No newline at end of file +} diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 6eb1df463..61edeba8f 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { LibAccess } from "lifi/Libraries/LibAccess.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; -import { InvalidCallData, InvalidAmount } from "lifi/Errors/GenericErrors.sol"; +import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; /// @title Core Route Facet /// @author LI.FI (https://li.fi) @@ -20,7 +17,8 @@ contract CoreRouteFacet is ReentrancyGuard { using LibInputStream for uint256; /// Constants /// - address internal constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address internal constant NATIVE_ADDRESS = + 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address internal constant INTERNAL_INPUT_SOURCE = address(0); /// Events /// @@ -58,7 +56,15 @@ contract CoreRouteFacet is ReentrancyGuard { address to, bytes calldata route ) external payable nonReentrant returns (uint256 amountOut) { - return _processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route); + return + _processRouteInternal( + tokenIn, + amountIn, + tokenOut, + amountOutMin, + to, + route + ); } /// Internal Methods /// @@ -155,13 +161,17 @@ contract CoreRouteFacet is ReentrancyGuard { } /// @notice Processes native coin - function _processNative(uint256 stream) private returns (uint256 amountTotal) { + function _processNative( + uint256 stream + ) private returns (uint256 amountTotal) { amountTotal = address(this).balance; _distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal); } /// @notice Processes ERC20 token from this contract balance - function _processMyERC20(uint256 stream) private returns (uint256 amountTotal) { + function _processMyERC20( + uint256 stream + ) private returns (uint256 amountTotal) { address token = stream.readAddress(); amountTotal = IERC20(token).balanceOf(address(this)); unchecked { @@ -210,20 +220,26 @@ contract CoreRouteFacet is ReentrancyGuard { ) private { // Read the function selector from the stream bytes4 selector = stream.readBytes4(); - - // Look up facet address directly from Diamond storage - address facet = LibDiamond.facetAddress(selector); + + // Look up facet address using LibDiamondLoupe + address facet = LibDiamondLoupe.facetAddress(selector); if (facet == address(0)) revert UnknownSelector(); // Prepare calldata: selector + remaining stream + additional parameters - bytes memory data = abi.encodePacked(selector, stream, from, tokenIn, amountIn); - + bytes memory data = abi.encodePacked( + selector, + stream, + from, + tokenIn, + amountIn + ); + // Execute the swap via delegatecall to the facet - (bool success, bytes memory result) = facet.delegatecall(data); + (bool success, ) = facet.delegatecall(data); if (!success) { revert SwapFailed(); } - + // Note: Individual facets can return amounts if needed, but for now we rely on balance checks } -} \ No newline at end of file +} diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index ce2d53af8..bc3f27e2c 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibInputStream } from "../../../Libraries/LibInputStream.sol"; -import { LibCallbackManager } from "../../../Libraries/LibCallbackManager.sol"; -import { LibAsset } from "../../../Libraries/LibAsset.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ICurve } from "../../../Interfaces/ICurve.sol"; import { ICurveLegacy } from "../../../Interfaces/ICurveLegacy.sol"; -import { InvalidCallData } from "../../../Errors/GenericErrors.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; /// @title Curve Facet /// @author LI.FI (https://li.fi) @@ -18,9 +17,11 @@ contract CurveFacet { using LibInputStream for uint256; using SafeERC20 for IERC20; using LibAsset for IERC20; + using Approve for IERC20; /// Constants /// - address internal constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address internal constant NATIVE_ADDRESS = + 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice Curve pool swap. Legacy pools that don't return amountOut and have native coins are not supported /// @param stream [pool, poolType, fromIndex, toIndex, recipient, output token] @@ -40,6 +41,8 @@ contract CurveFacet { address to = stream.readAddress(); address tokenOut = stream.readAddress(); + // TODO arm callback protection + uint256 amountOut; if (tokenIn == NATIVE_ADDRESS) { amountOut = ICurve(pool).exchange{ value: amountIn }( @@ -78,7 +81,7 @@ contract CurveFacet { if (to != address(this)) { if (tokenOut == NATIVE_ADDRESS) { - LibAsset.transferNative(to, amountOut); + SafeTransferLib.safeTransferETH(to, amountOut); } else { IERC20(tokenOut).safeTransfer(to, amountOut); } @@ -86,4 +89,42 @@ contract CurveFacet { return amountOut; } -} \ No newline at end of file +} + +library Approve { + /** + * @dev ERC20 approve that correct works with token.approve which returns bool or nothing (USDT for example) + * @param token The token targeted by the call. + * @param spender token spender + * @param amount token amount + */ + function approveStable( + IERC20 token, + address spender, + uint256 amount + ) internal returns (bool) { + (bool success, bytes memory data) = address(token).call( + abi.encodeWithSelector(token.approve.selector, spender, amount) + ); + return success && (data.length == 0 || abi.decode(data, (bool))); + } + + /** + * @dev ERC20 approve that correct works with token.approve which reverts if amount and + * current allowance are not zero simultaniously (USDT for example). + * In second case it tries to set allowance to 0, and then back to amount. + * @param token The token targeted by the call. + * @param spender token spender + * @param amount token amount + */ + function approveSafe( + IERC20 token, + address spender, + uint256 amount + ) internal returns (bool) { + return + approveStable(token, spender, amount) || + (approveStable(token, spender, 0) && + approveStable(token, spender, amount)); + } +} diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index c026987e8..bc53258a3 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -1,26 +1,112 @@ -// src/Periphery/Lda/Facets/IzumiV3Facet.sol +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +/// @title IzumiV3 Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles IzumiV3 swaps with callback management +/// @custom:version 1.0.0 contract IzumiV3Facet { using LibInputStream for uint256; using LibCallbackManager for *; + using SafeERC20 for IERC20; + + /// @dev iZiSwap pool price points boundaries + int24 internal constant IZUMI_LEFT_MOST_PT = -800000; + int24 internal constant IZUMI_RIGHT_MOST_PT = 800000; + uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + error IzumiV3SwapUnexpected(); + error IzumiV3SwapCallbackUnknownSource(); + error IzumiV3SwapCallbackNotPositiveAmount(); + + /// @notice Performs a swap through iZiSwap V3 pools + /// @dev This function handles both X to Y and Y to X swaps through iZiSwap V3 pools + /// @param stream [pool, direction, recipient] + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token + /// @param amountIn Amount of tokenIn to take for swap function swapIzumiV3( uint256 stream, address from, address tokenIn, uint256 amountIn ) external returns (uint256) { - // Move IzumiV3 swap logic here + address pool = stream.readAddress(); + uint8 direction = stream.readUint8(); // 0 = Y2X, 1 = X2Y + address recipient = stream.readAddress(); + + if ( + pool == address(0) || + recipient == address(0) || + amountIn > type(uint128).max + ) revert InvalidCallData(); + + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom( + msg.sender, + address(this), + amountIn + ); + } + + LibCallbackManager.arm(pool); + + if (direction == DIRECTION_TOKEN0_TO_TOKEN1) { + IiZiSwapPool(pool).swapX2Y( + recipient, + uint128(amountIn), + IZUMI_LEFT_MOST_PT + 1, + abi.encode(tokenIn) + ); + } else { + IiZiSwapPool(pool).swapY2X( + recipient, + uint128(amountIn), + IZUMI_RIGHT_MOST_PT - 1, + abi.encode(tokenIn) + ); + } + + // After the swapX2Y or swapY2X call, the callback should clear the registered pool + // If it hasn't, it means the callback either didn't happen, was incorrect, or the pool misbehaved + // so we revert to protect against misuse or faulty integrations + if (LibCallbackManager.callbackStorage().expected != address(0)) { + revert IzumiV3SwapUnexpected(); + } + + return 0; // Return value not used in current implementation } function swapX2YCallback(uint256 amountX, bytes calldata data) external { - LibCallbackManager.verifyCallbackSender(); _handleIzumiV3SwapCallback(amountX, data); - LibCallbackManager.clear(); } function swapY2XCallback(uint256 amountY, bytes calldata data) external { - LibCallbackManager.verifyCallbackSender(); _handleIzumiV3SwapCallback(amountY, data); + } + + /// @dev Common logic for iZiSwap callbacks + /// @param amountToPay The amount of tokens to be sent to the pool + /// @param data The data passed through by the caller + function _handleIzumiV3SwapCallback( + uint256 amountToPay, + bytes calldata data + ) private { + LibCallbackManager.verifyCallbackSender(); + + if (amountToPay == 0) { + revert IzumiV3SwapCallbackNotPositiveAmount(); + } + + address tokenIn = abi.decode(data, (address)); + IERC20(tokenIn).safeTransfer(msg.sender, amountToPay); + LibCallbackManager.clear(); } -} \ No newline at end of file +} diff --git a/src/Periphery/Lda/Facets/SyncSwapFacet.sol b/src/Periphery/Lda/Facets/SyncSwapFacet.sol index fc2502a68..07f4cfab0 100644 --- a/src/Periphery/Lda/Facets/SyncSwapFacet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapFacet.sol @@ -1,16 +1,69 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; +import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { ISyncSwapPool } from "lifi/Interfaces/ISyncSwapPool.sol"; +import { ISyncSwapVault } from "lifi/Interfaces/ISyncSwapVault.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; - -// src/Periphery/Lda/Facets/SyncSwapFacet.sol +/// @title SyncSwap Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles SyncSwap swaps with callback management +/// @custom:version 1.0.0 contract SyncSwapFacet { using LibInputStream for uint256; + using SafeERC20 for IERC20; + /// @notice Performs a swap through SyncSwap pools + /// @dev This function handles both X to Y and Y to X swaps through SyncSwap pools. + /// See [SyncSwap API documentation](https://docs.syncswap.xyz/api-documentation) for protocol details. + /// @param stream [pool, to, withdrawMode, isV1Pool, vault] + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token + /// @param amountIn Amount of tokenIn to take for swap function swapSyncSwap( uint256 stream, address from, address tokenIn, uint256 amountIn ) external returns (uint256) { - // Move SyncSwap logic here + address pool = stream.readAddress(); + address to = stream.readAddress(); + + if (pool == address(0) || to == address(0)) revert InvalidCallData(); + + // withdrawMode meaning for SyncSwap via vault: + // 1: Withdraw raw ETH (native) + // 2: Withdraw WETH (wrapped) + // 0: Let the vault decide (ETH for native, WETH for wrapped) + // For ERC-20 tokens the vault just withdraws the ERC-20 + // and this mode byte is read and ignored by the ERC-20 branch. + uint8 withdrawMode = stream.readUint8(); + + if (withdrawMode > 2) revert InvalidCallData(); + + bool isV1Pool = stream.readUint8() == 1; + + address target = isV1Pool ? stream.readAddress() : pool; // target is the vault for V1 pools, the pool for V2 pools + if (isV1Pool && target == address(0)) revert InvalidCallData(); + + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom(msg.sender, target, amountIn); + } else if (from == address(this)) { + IERC20(tokenIn).safeTransfer(target, amountIn); + } + // if from is not msg.sender or address(this), it must be INTERNAL_INPUT_SOURCE + // which means tokens are already in the vault/pool, no transfer needed + + if (isV1Pool) { + ISyncSwapVault(target).deposit(tokenIn, pool); + } + + bytes memory data = abi.encode(tokenIn, to, withdrawMode); + + ISyncSwapPool(pool).swap(data, from, address(0), new bytes(0)); + + return 0; // Return value not used in current implementation } -} \ No newline at end of file +} diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 318fc1db3..3acf0f229 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -42,111 +42,139 @@ contract UniV3StyleFacet { int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function ramsesV2SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function xeiV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function dragonswapV2SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function agniSwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function fusionXV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function vvsV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function supV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function zebraV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function hyperswapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function laminarV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function xswapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function rabbitSwapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } function enosysdexV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external LibCallbackManager.onlyExpectedCallback { + ) external { + LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); + LibCallbackManager.clear(); } -} \ No newline at end of file +} diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index eaf327a24..73146e054 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -1,13 +1,100 @@ -// src/Periphery/Lda/Facets/VelodromeV2Facet.sol +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +/// @title VelodromeV2 Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles VelodromeV2 swaps with callback management +/// @custom:version 1.0.0 contract VelodromeV2Facet { using LibInputStream for uint256; + using SafeERC20 for IERC20; + + uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + uint8 internal constant CALLBACK_ENABLED = 1; + address internal constant INTERNAL_INPUT_SOURCE = address(0); + error WrongPoolReserves(); + + /// @notice Performs a swap through VelodromeV2 pools + /// @dev This function does not handle native token swaps directly, so processNative command cannot be used + /// @param stream [pool, direction, to, callback] + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token + /// @param amountIn Amount of tokenIn to take for swap function swapVelodromeV2( uint256 stream, address from, address tokenIn, uint256 amountIn ) external returns (uint256) { - // Move Velodrome V2 swap logic here + address pool = stream.readAddress(); + uint8 direction = stream.readUint8(); + address to = stream.readAddress(); + if (pool == address(0) || to == address(0)) revert InvalidCallData(); + // solhint-disable-next-line max-line-length + bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee + + if (from == INTERNAL_INPUT_SOURCE) { + (uint256 reserve0, uint256 reserve1, ) = IVelodromeV2Pool(pool) + .getReserves(); + if (reserve0 == 0 || reserve1 == 0) revert WrongPoolReserves(); + uint256 reserveIn = direction == DIRECTION_TOKEN0_TO_TOKEN1 + ? reserve0 + : reserve1; + + amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; + } else { + if (from == address(this)) + IERC20(tokenIn).safeTransfer(pool, amountIn); + else if (from == msg.sender) + IERC20(tokenIn).safeTransferFrom(msg.sender, pool, amountIn); + } + + // calculate the expected output amount using the pool's getAmountOut function + uint256 amountOut = IVelodromeV2Pool(pool).getAmountOut( + amountIn, + tokenIn + ); + + // set the appropriate output amount based on which token is being swapped + // determine output amounts based on direction + uint256 amount0Out = direction == DIRECTION_TOKEN0_TO_TOKEN1 + ? 0 + : amountOut; + uint256 amount1Out = direction == DIRECTION_TOKEN0_TO_TOKEN1 + ? amountOut + : 0; + + // 'swap' function from IVelodromeV2Pool should be called from a contract which performs important safety checks. + // Safety Checks Covered: + // - Reentrancy: LDA has a custom lock() modifier + // - Token transfer safety: SafeERC20 is used to ensure token transfers revert on failure + // - Expected output verification: The contract calls getAmountOut (including fees) before executing the swap + // - Flashloan trigger: A flashloan flag is used to determine if the callback should be triggered + // - Post-swap verification: In processRouteInternal, it verifies that the recipient receives at least minAmountOut + // and that the sender's final balance is not less than the initial balance + // - Immutable interaction: Velodrome V2 pools and the router are not upgradable, + // so we can rely on the behavior of getAmountOut and swap + + // ATTENTION FOR CALLBACKS / HOOKS: + // - recipient contracts should validate that msg.sender is the Velodrome pool contract who is calling the hook + // - recipient contracts must not manipulate their own tokenOut balance + // (as this may bypass/invalidate the built-in slippage protection) + // - @developers: never trust balance-based slippage protection for callback recipients + // - @integrators: do not use slippage guarantees when recipient is a contract with side-effects + IVelodromeV2Pool(pool).swap( + amount0Out, + amount1Out, + to, + callback ? abi.encode(tokenIn) : bytes("") + ); + + return 0; // Return value not used in current implementation } -} \ No newline at end of file +} From 898424ccaaa6fdbd86c6d8502416b7cc8b7254c1 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 24 Jul 2025 23:22:28 +0200 Subject: [PATCH 004/220] changes --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 40 +- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 11 + .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 3945 ++++------------- test/solidity/utils/TestBase.sol | 3 + 4 files changed, 819 insertions(+), 3180 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 61edeba8f..661a68669 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -6,11 +6,12 @@ import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/Saf import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; +import { console2 } from "forge-std/console2.sol"; /// @title Core Route Facet /// @author LI.FI (https://li.fi) /// @notice Handles route processing and selector-based swap dispatching for LDA 2.0 -/// @custom:version 2.0.0 +/// @custom:version 1.0.0 contract CoreRouteFacet is ReentrancyGuard { using SafeERC20 for IERC20; using SafeERC20 for IERC20Permit; @@ -200,9 +201,13 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 amountTotal ) private { uint8 num = stream.readUint8(); + console2.log("num222"); + console2.log(num); unchecked { for (uint256 i = 0; i < num; ++i) { uint16 share = stream.readUint16(); + console2.log("share222"); + console2.log(share); uint256 amount = (amountTotal * share) / type(uint16).max; amountTotal -= amount; _dispatchSwap(stream, from, tokenIn, amount); @@ -218,24 +223,35 @@ contract CoreRouteFacet is ReentrancyGuard { address tokenIn, uint256 amountIn ) private { - // Read the function selector from the stream - bytes4 selector = stream.readBytes4(); + // Read the function selector from the stream (4 bytes in reverse order) + bytes4 selector = bytes4( + uint32(stream.readUint8()) | + (uint32(stream.readUint8()) << 8) | + (uint32(stream.readUint8()) << 16) | + (uint32(stream.readUint8()) << 24) + ); // Look up facet address using LibDiamondLoupe + console2.log("selector222"); + console2.logBytes4(selector); address facet = LibDiamondLoupe.facetAddress(selector); + console2.log("facet222"); + console2.logAddress(facet); if (facet == address(0)) revert UnknownSelector(); - // Prepare calldata: selector + remaining stream + additional parameters - bytes memory data = abi.encodePacked( - selector, - stream, - from, - tokenIn, - amountIn - ); + // Skip over the selector in the stream + stream.readBytes4(); // Skip the selector // Execute the swap via delegatecall to the facet - (bool success, ) = facet.delegatecall(data); + (bool success, ) = facet.delegatecall( + abi.encodeWithSelector( + selector, + stream, + from, + tokenIn, + amountIn + ) + ); if (!success) { revert SwapFailed(); } diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 73146e054..fde2a688b 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -6,6 +6,7 @@ import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { console2 } from "forge-std/console2.sol"; /// @title VelodromeV2 Facet /// @author LI.FI (https://li.fi) @@ -33,9 +34,19 @@ contract VelodromeV2Facet { address tokenIn, uint256 amountIn ) external returns (uint256) { + console2.log("swapVelodromeV222 here"); + console2.log("stream before reading:"); + console2.logBytes(abi.encode(stream)); // Add this to see the raw stream data + address pool = stream.readAddress(); + console2.log("pool222"); + console2.logAddress(pool); uint8 direction = stream.readUint8(); + console2.log("direction222"); + console2.log(direction); address to = stream.readAddress(); + console2.log("to222"); + console2.logAddress(to); if (pool == address(0) || to == address(0)) revert InvalidCallData(); // solhint-disable-next-line max-line-length bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index 1f8cac7d1..eb3076a2f 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -20,6 +20,11 @@ import { TestToken as ERC20 } from "../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; + +import { console2 } from "forge-std/console2.sol"; + // Command codes for route processing enum CommandType { None, // 0 - not used @@ -84,9 +89,7 @@ contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { abstract contract LiFiDexAggregatorUpgradeTest is TestBase { using SafeERC20 for IERC20; - // Common variables - LiFiDEXAggregator internal liFiDEXAggregator; - address[] internal privileged; + CoreRouteFacet internal coreRouteFacet; // Common events and errors event Route( @@ -108,18 +111,7 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { error WrongPoolReserves(); error PoolDoesNotExist(); - // helper function to initialize the aggregator - function _initializeDexAggregator(address owner) internal { - privileged = new address[](1); - privileged[0] = owner; - - liFiDEXAggregator = new LiFiDEXAggregator( - address(0xCAFE), - privileged, - owner - ); - vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); - } + function _addDexFacet() virtual internal; // Setup function for Apechain tests function setupApechain() internal { @@ -127,7 +119,7 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { customBlockNumberForForking = 12912470; fork(); - _initializeDexAggregator(address(USER_DIAMOND_OWNER)); + // _initializeDexAggregator(address(USER_DIAMOND_OWNER)); } function setupHyperEVM() internal { @@ -135,35 +127,46 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { customBlockNumberForForking = 4433562; fork(); - _initializeDexAggregator(USER_DIAMOND_OWNER); + // _initializeDexAggregator(USER_DIAMOND_OWNER); } function setUp() public virtual { initTestBase(); vm.label(USER_SENDER, "USER_SENDER"); - _initializeDexAggregator(USER_DIAMOND_OWNER); + _addCoreRouteFacet(); + _addDexFacet(); + // _initializeDexAggregator(USER_DIAMOND_OWNER); } - function test_ContractIsSetUpCorrectly() public { - assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); - assertEq( - liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), - true - ); - assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); - } - - function testRevert_FailsIfOwnerIsZeroAddress() public { - vm.expectRevert(InvalidConfig.selector); + function _addCoreRouteFacet() internal { + coreRouteFacet = new CoreRouteFacet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = CoreRouteFacet.processRoute.selector; + addFacet(address(ldaDiamond), address(coreRouteFacet), functionSelectors); - liFiDEXAggregator = new LiFiDEXAggregator( - address(0xCAFE), - privileged, - address(0) - ); + coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); } + // function test_ContractIsSetUpCorrectly() public { + // assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); + // assertEq( + // liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), + // true + // ); + // assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); + // } + + // function testRevert_FailsIfOwnerIsZeroAddress() public { + // vm.expectRevert(InvalidConfig.selector); + + // liFiDEXAggregator = new LiFiDEXAggregator( + // address(0xCAFE), + // privileged, + // address(0) + // ); + // } + // ============================ Abstract DEX Tests ============================ /** * @notice Abstract test for basic token swapping functionality @@ -196,3188 +199,794 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { } } -// /** -// * @title VelodromeV2 tests -// * @notice Tests specific to Velodrome V2 pool type -// */ -// contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorTest { -// // ==================== Velodrome V2 specific variables ==================== -// IVelodromeV2Router internal constant VELODROME_V2_ROUTER = -// IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router -// address internal constant VELODROME_V2_FACTORY_REGISTRY = -// 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; -// IERC20 internal constant STG_TOKEN = -// IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); -// IERC20 internal constant USDC_E_TOKEN = -// IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); - -// MockVelodromeV2FlashLoanCallbackReceiver -// internal mockFlashloanCallbackReceiver; - -// // Velodrome V2 structs -// struct VelodromeV2SwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// bool stable; -// SwapDirection direction; -// bool callback; -// } - -// struct MultiHopTestParams { -// address tokenIn; -// address tokenMid; -// address tokenOut; -// address pool1; -// address pool2; -// uint256[] amounts1; -// uint256[] amounts2; -// uint256 pool1Fee; -// uint256 pool2Fee; -// } - -// struct ReserveState { -// uint256 reserve0Pool1; -// uint256 reserve1Pool1; -// uint256 reserve0Pool2; -// uint256 reserve1Pool2; -// } - -// // Setup function for Optimism tests -// function setupOptimism() internal { -// customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; -// customBlockNumberForForking = 133999121; -// initTestBase(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// function setUp() public override { -// setupOptimism(); -// } - -// // // ============================ Velodrome V2 Tests ============================ - -// // no stable swap -// function test_CanSwap() public override { -// vm.startPrank(USER_SENDER); - -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(USER_SENDER), -// to: address(USER_SENDER), -// tokenIn: ADDRESS_USDC, -// amountIn: 1_000 * 1e6, -// tokenOut: address(STG_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_NoStable_Reverse() public { -// // first perform the forward swap. -// test_CanSwap(); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(STG_TOKEN), -// amountIn: 500 * 1e18, -// tokenOut: ADDRESS_USDC, -// stable: false, -// direction: SwapDirection.Token1ToToken0, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_Stable() public { -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: ADDRESS_USDC, -// amountIn: 1_000 * 1e6, -// tokenOut: address(USDC_E_TOKEN), -// stable: true, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_Stable_Reverse() public { -// // first perform the forward stable swap. -// test_CanSwap_Stable(); - -// vm.startPrank(USER_SENDER); - -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(USDC_E_TOKEN), -// amountIn: 500 * 1e6, -// tokenOut: ADDRESS_USDC, -// stable: false, -// direction: SwapDirection.Token1ToToken0, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // fund dex aggregator contract so that the contract holds USDC -// deal(ADDRESS_USDC, address(liFiDEXAggregator), 100_000 * 1e6); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(liFiDEXAggregator), -// to: address(USER_SENDER), -// tokenIn: ADDRESS_USDC, -// amountIn: IERC20(ADDRESS_USDC).balanceOf( -// address(liFiDEXAggregator) -// ) - 1, // adjust for slot undrain protection: subtract 1 token so that the aggregator's balance isn't completely drained, matching the contract's safeguard -// tokenOut: address(USDC_E_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_FlashloanCallback() public { -// mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(USER_SENDER), -// to: address(mockFlashloanCallbackReceiver), -// tokenIn: ADDRESS_USDC, -// amountIn: 1_000 * 1e6, -// tokenOut: address(USDC_E_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: true -// }) -// ); -// vm.stopPrank(); -// } - -// // Override the abstract test with VelodromeV2 implementation -// function test_CanSwap_MultiHop() public override { -// vm.startPrank(USER_SENDER); - -// // Setup routes and get amounts -// MultiHopTestParams memory params = _setupRoutes( -// ADDRESS_USDC, -// address(STG_TOKEN), -// address(USDC_E_TOKEN), -// false, -// false -// ); - -// // Get initial reserves BEFORE the swap -// ReserveState memory initialReserves; -// ( -// initialReserves.reserve0Pool1, -// initialReserves.reserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// initialReserves.reserve0Pool2, -// initialReserves.reserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( -// USER_SENDER -// ); - -// // Build route and execute swap -// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); - -// // Approve and execute -// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// params.tokenIn, -// params.tokenOut, -// 1000 * 1e6, -// params.amounts2[1], -// params.amounts2[1] -// ); - -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// 1000 * 1e6, -// params.tokenOut, -// params.amounts2[1], -// USER_SENDER, -// route -// ); - -// _verifyUserBalances(params, initialBalance1, initialBalance2); -// _verifyReserves(params, initialReserves); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop_WithStable() public { -// vm.startPrank(USER_SENDER); - -// // Setup routes and get amounts for stable->volatile path -// MultiHopTestParams memory params = _setupRoutes( -// ADDRESS_USDC, -// address(USDC_E_TOKEN), -// address(STG_TOKEN), -// true, // stable pool for first hop -// false // volatile pool for second hop -// ); - -// // Get initial reserves BEFORE the swap -// ReserveState memory initialReserves; -// ( -// initialReserves.reserve0Pool1, -// initialReserves.reserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// initialReserves.reserve0Pool2, -// initialReserves.reserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// // Record initial balances -// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( -// USER_SENDER -// ); - -// // Build route and execute swap -// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - -// // Approve and execute -// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// params.tokenIn, -// params.tokenOut, -// 1000 * 1e6, -// params.amounts2[1], -// params.amounts2[1] -// ); - -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// 1000 * 1e6, -// params.tokenOut, -// params.amounts2[1], -// USER_SENDER, -// route -// ); - -// _verifyUserBalances(params, initialBalance1, initialBalance2); -// _verifyReserves(params, initialReserves); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidPoolOrRecipient() public { -// vm.startPrank(USER_SENDER); - -// // Get a valid pool address first for comparison -// address validPool = VELODROME_V2_ROUTER.poolFor( -// ADDRESS_USDC, -// address(STG_TOKEN), -// false, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// // Test case 1: Zero pool address -// bytes memory routeWithZeroPool = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// ADDRESS_USDC, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// address(0), -// uint8(SwapDirection.Token1ToToken0), -// USER_SENDER, -// uint8(CallbackStatus.Disabled) -// ); - -// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// ADDRESS_USDC, -// 1000 * 1e6, -// address(STG_TOKEN), -// 0, -// USER_SENDER, -// routeWithZeroPool -// ); - -// // Test case 2: Zero recipient address -// bytes memory routeWithZeroRecipient = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// ADDRESS_USDC, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// validPool, -// uint8(SwapDirection.Token1ToToken0), -// address(0), -// uint8(CallbackStatus.Disabled) -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// ADDRESS_USDC, -// 1000 * 1e6, -// address(STG_TOKEN), -// 0, -// USER_SENDER, -// routeWithZeroRecipient -// ); - -// vm.stopPrank(); -// } - -// function testRevert_WrongPoolReserves() public { -// vm.startPrank(USER_SENDER); - -// // Setup multi-hop route: USDC -> STG -> USDC.e -// MultiHopTestParams memory params = _setupRoutes( -// ADDRESS_USDC, -// address(STG_TOKEN), -// address(USDC_E_TOKEN), -// false, -// false -// ); - -// // Build multi-hop route -// bytes memory firstHop = _buildFirstHop( -// params.tokenIn, -// params.pool1, -// params.pool2, -// 1 // direction -// ); - -// bytes memory secondHop = _buildSecondHop( -// params.tokenMid, -// params.pool2, -// USER_SENDER, -// 0 // direction -// ); - -// bytes memory route = bytes.concat(firstHop, secondHop); - -// deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); - -// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves -// vm.mockCall( -// params.pool2, -// abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), -// abi.encode(0, 0, block.timestamp) -// ); - -// vm.expectRevert(WrongPoolReserves.selector); - -// liFiDEXAggregator.processRoute( -// ADDRESS_USDC, -// 1000 * 1e6, -// address(USDC_E_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// // ============================ Velodrome V2 Helper Functions ============================ - -// /** -// * @dev Helper function to test a VelodromeV2 swap. -// * Uses a struct to group parameters and reduce stack depth. -// */ -// function _testSwap(VelodromeV2SwapTestParams memory params) internal { -// // get expected output amounts from the router. -// IVelodromeV2Router.Route[] -// memory routes = new IVelodromeV2Router.Route[](1); -// routes[0] = IVelodromeV2Router.Route({ -// from: params.tokenIn, -// to: params.tokenOut, -// stable: params.stable, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( -// params.amountIn, -// routes -// ); -// emit log_named_uint("Expected amount out", amounts[1]); - -// // Retrieve the pool address. -// address pool = VELODROME_V2_ROUTER.poolFor( -// params.tokenIn, -// params.tokenOut, -// params.stable, -// VELODROME_V2_FACTORY_REGISTRY -// ); -// emit log_named_uint("Pool address:", uint256(uint160(pool))); - -// // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. -// CommandType commandCode = params.from == address(liFiDEXAggregator) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; - -// // build the route. -// bytes memory route = abi.encodePacked( -// uint8(commandCode), -// params.tokenIn, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// pool, -// params.direction, -// params.to, -// params.callback -// ? uint8(CallbackStatus.Enabled) -// : uint8(CallbackStatus.Disabled) -// ); - -// // approve the aggregator to spend tokenIn. -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); - -// // capture initial token balances. -// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); -// emit log_named_uint("Initial tokenIn balance", initialTokenIn); - -// address from = params.from == address(liFiDEXAggregator) -// ? USER_SENDER -// : params.from; -// if (params.callback == true) { -// vm.expectEmit(true, false, false, false); -// emit HookCalled( -// address(liFiDEXAggregator), -// 0, -// 0, -// abi.encode(params.tokenIn) -// ); -// } -// vm.expectEmit(true, true, true, true); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// amounts[1], -// amounts[1] -// ); - -// // execute the swap -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// amounts[1], -// params.to, -// route -// ); - -// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); -// emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); -// emit log_named_uint( -// "TokenOut received", -// finalTokenOut - initialTokenOut -// ); - -// assertApproxEqAbs( -// initialTokenIn - finalTokenIn, -// params.amountIn, -// 1, // 1 wei tolerance -// "TokenIn amount mismatch" -// ); -// assertEq( -// finalTokenOut - initialTokenOut, -// amounts[1], -// "TokenOut amount mismatch" -// ); -// } - -// // Helper function to set up routes and get amounts -// function _setupRoutes( -// address tokenIn, -// address tokenMid, -// address tokenOut, -// bool isStableFirst, -// bool isStableSecond -// ) private view returns (MultiHopTestParams memory params) { -// params.tokenIn = tokenIn; -// params.tokenMid = tokenMid; -// params.tokenOut = tokenOut; - -// // Setup first hop route -// IVelodromeV2Router.Route[] -// memory routes1 = new IVelodromeV2Router.Route[](1); -// routes1[0] = IVelodromeV2Router.Route({ -// from: tokenIn, -// to: tokenMid, -// stable: isStableFirst, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( -// 1000 * 1e6, -// routes1 -// ); - -// // Setup second hop route -// IVelodromeV2Router.Route[] -// memory routes2 = new IVelodromeV2Router.Route[](1); -// routes2[0] = IVelodromeV2Router.Route({ -// from: tokenMid, -// to: tokenOut, -// stable: isStableSecond, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( -// params.amounts1[1], -// routes2 -// ); - -// // Get pool addresses -// params.pool1 = VELODROME_V2_ROUTER.poolFor( -// tokenIn, -// tokenMid, -// isStableFirst, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// params.pool2 = VELODROME_V2_ROUTER.poolFor( -// tokenMid, -// tokenOut, -// isStableSecond, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// // Get pool fees info -// params.pool1Fee = IVelodromeV2PoolFactory( -// VELODROME_V2_FACTORY_REGISTRY -// ).getFee(params.pool1, isStableFirst); -// params.pool2Fee = IVelodromeV2PoolFactory( -// VELODROME_V2_FACTORY_REGISTRY -// ).getFee(params.pool2, isStableSecond); - -// return params; -// } - -// // function to build first hop of the route -// function _buildFirstHop( -// address tokenIn, -// address pool1, -// address pool2, -// uint8 direction -// ) private pure returns (bytes memory) { -// return -// abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// tokenIn, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// pool1, -// direction, -// pool2, -// uint8(CallbackStatus.Disabled) -// ); -// } - -// // function to build second hop of the route -// function _buildSecondHop( -// address tokenMid, -// address pool2, -// address recipient, -// uint8 direction -// ) private pure returns (bytes memory) { -// return -// abi.encodePacked( -// uint8(CommandType.ProcessOnePool), -// tokenMid, -// uint8(PoolType.VelodromeV2), -// pool2, -// direction, -// recipient, -// uint8(CallbackStatus.Disabled) -// ); -// } - -// // route building function -// function _buildMultiHopRoute( -// MultiHopTestParams memory params, -// address recipient, -// uint8 firstHopDirection, -// uint8 secondHopDirection -// ) private pure returns (bytes memory) { -// bytes memory firstHop = _buildFirstHop( -// params.tokenIn, -// params.pool1, -// params.pool2, -// firstHopDirection -// ); - -// bytes memory secondHop = _buildSecondHop( -// params.tokenMid, -// params.pool2, -// recipient, -// secondHopDirection -// ); - -// return bytes.concat(firstHop, secondHop); -// } - -// function _verifyUserBalances( -// MultiHopTestParams memory params, -// uint256 initialBalance1, -// uint256 initialBalance2 -// ) private { -// // Verify token balances -// uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// assertApproxEqAbs( -// initialBalance1 - finalBalance1, -// 1000 * 1e6, -// 1, // 1 wei tolerance -// "Token1 spent amount mismatch" -// ); -// assertEq( -// finalBalance2 - initialBalance2, -// params.amounts2[1], -// "Token2 received amount mismatch" -// ); -// } - -// function _verifyReserves( -// MultiHopTestParams memory params, -// ReserveState memory initialReserves -// ) private { -// // Get reserves after swap -// ( -// uint256 finalReserve0Pool1, -// uint256 finalReserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// uint256 finalReserve0Pool2, -// uint256 finalReserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); -// address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); - -// // Calculate exact expected changes -// uint256 amountInAfterFees = 1000 * -// 1e6 - -// ((1000 * 1e6 * params.pool1Fee) / 10000); - -// // Assert exact reserve changes for Pool1 -// if (token0Pool1 == params.tokenIn) { -// // tokenIn is token0, so reserve0 should increase and reserve1 should decrease -// assertEq( -// finalReserve0Pool1 - initialReserves.reserve0Pool1, -// amountInAfterFees, -// "Pool1 reserve0 (tokenIn) change incorrect" -// ); -// assertEq( -// initialReserves.reserve1Pool1 - finalReserve1Pool1, -// params.amounts1[1], -// "Pool1 reserve1 (tokenMid) change incorrect" -// ); -// } else { -// // tokenIn is token1, so reserve1 should increase and reserve0 should decrease -// assertEq( -// finalReserve1Pool1 - initialReserves.reserve1Pool1, -// amountInAfterFees, -// "Pool1 reserve1 (tokenIn) change incorrect" -// ); -// assertEq( -// initialReserves.reserve0Pool1 - finalReserve0Pool1, -// params.amounts1[1], -// "Pool1 reserve0 (tokenMid) change incorrect" -// ); -// } - -// // Assert exact reserve changes for Pool2 -// if (token0Pool2 == params.tokenMid) { -// // tokenMid is token0, so reserve0 should increase and reserve1 should decrease -// assertEq( -// finalReserve0Pool2 - initialReserves.reserve0Pool2, -// params.amounts1[1] - -// ((params.amounts1[1] * params.pool2Fee) / 10000), -// "Pool2 reserve0 (tokenMid) change incorrect" -// ); -// assertEq( -// initialReserves.reserve1Pool2 - finalReserve1Pool2, -// params.amounts2[1], -// "Pool2 reserve1 (tokenOut) change incorrect" -// ); -// } else { -// // tokenMid is token1, so reserve1 should increase and reserve0 should decrease -// assertEq( -// finalReserve1Pool2 - initialReserves.reserve1Pool2, -// params.amounts1[1] - -// ((params.amounts1[1] * params.pool2Fee) / 10000), -// "Pool2 reserve1 (tokenMid) change incorrect" -// ); -// assertEq( -// initialReserves.reserve0Pool2 - finalReserve0Pool2, -// params.amounts2[1], -// "Pool2 reserve0 (tokenOut) change incorrect" -// ); -// } -// } -// } - -// contract AlgebraLiquidityAdderHelper { -// address public immutable TOKEN_0; -// address public immutable TOKEN_1; - -// constructor(address _token0, address _token1) { -// TOKEN_0 = _token0; -// TOKEN_1 = _token1; -// } - -// function addLiquidity( -// address pool, -// int24 bottomTick, -// int24 topTick, -// uint128 amount -// ) -// external -// returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) -// { -// // Get balances before -// uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Call mint -// (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( -// address(this), -// address(this), -// bottomTick, -// topTick, -// amount, -// abi.encode(TOKEN_0, TOKEN_1) -// ); - -// // Get balances after to account for fees -// uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Calculate actual amounts transferred accounting for fees -// amount0 = balance0Before - balance0After; -// amount1 = balance1Before - balance1After; - -// return (amount0, amount1, liquidityActual); -// } - -// function algebraMintCallback( -// uint256 amount0Owed, -// uint256 amount1Owed, -// bytes calldata -// ) external { -// // Check token balances -// uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Transfer what we can, limited by actual balance -// if (amount0Owed > 0) { -// uint256 amount0ToSend = amount0Owed > balance0 -// ? balance0 -// : amount0Owed; -// uint256 balance0Before = IERC20(TOKEN_0).balanceOf( -// address(msg.sender) -// ); -// IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); -// uint256 balance0After = IERC20(TOKEN_0).balanceOf( -// address(msg.sender) -// ); -// // solhint-disable-next-line gas-custom-errors -// require(balance0After > balance0Before, "Transfer failed"); -// } - -// if (amount1Owed > 0) { -// uint256 amount1ToSend = amount1Owed > balance1 -// ? balance1 -// : amount1Owed; -// uint256 balance1Before = IERC20(TOKEN_1).balanceOf( -// address(msg.sender) -// ); -// IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); -// uint256 balance1After = IERC20(TOKEN_1).balanceOf( -// address(msg.sender) -// ); -// // solhint-disable-next-line gas-custom-errors -// require(balance1After > balance1Before, "Transfer failed"); -// } -// } -// } - -// /** -// * @title Algebra tests -// * @notice Tests specific to Algebra pool type -// */ -// contract LiFiDexAggregatorAlgebraTest is LiFiDexAggregatorTest { -// address private constant APE_ETH_TOKEN = -// 0xcF800F4948D16F23333508191B1B1591daF70438; -// address private constant WETH_TOKEN = -// 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; -// address private constant ALGEBRA_FACTORY_APECHAIN = -// 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; -// address private constant ALGEBRA_QUOTER_V2_APECHAIN = -// 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; -// address private constant ALGEBRA_POOL_APECHAIN = -// 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; -// address private constant APE_ETH_HOLDER_APECHAIN = -// address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); - -// address private constant IMPOSSIBLE_POOL_ADDRESS = -// 0x0000000000000000000000000000000000000001; - -// struct AlgebraSwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// SwapDirection direction; -// bool supportsFeeOnTransfer; -// } - -// error AlgebraSwapUnexpected(); - -// function setUp() public override { -// setupApechain(); -// } - -// // Override the abstract test with Algebra implementation -// function test_CanSwap_FromDexAggregator() public override { -// // Fund LDA from whale address -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(address(liFiDEXAggregator), 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: address(liFiDEXAggregator), -// to: address(USER_SENDER), -// tokenIn: APE_ETH_TOKEN, -// amountIn: IERC20(APE_ETH_TOKEN).balanceOf( -// address(liFiDEXAggregator) -// ) - 1, -// tokenOut: address(WETH_TOKEN), -// direction: SwapDirection.Token0ToToken1, -// supportsFeeOnTransfer: true -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FeeOnTransferToken() public { -// setupApechain(); - -// uint256 amountIn = 534451326669177; -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); - -// vm.startPrank(APE_ETH_HOLDER_APECHAIN); - -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), amountIn); - -// // Build route for algebra swap with command code 2 (user funds) -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: APE_ETH_HOLDER_APECHAIN, -// pool: ALGEBRA_POOL_APECHAIN, -// supportsFeeOnTransfer: true -// }) -// ); - -// // Track initial balance -// uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( -// APE_ETH_HOLDER_APECHAIN -// ); - -// // Execute the swap -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// amountIn, -// WETH_TOKEN, -// 0, // minOut = 0 for this test -// APE_ETH_HOLDER_APECHAIN, -// route -// ); - -// // Verify balances -// uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( -// APE_ETH_HOLDER_APECHAIN -// ); -// assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); - -// vm.stopPrank(); -// } - -// function test_CanSwap() public override { -// vm.startPrank(APE_ETH_HOLDER_APECHAIN); - -// // Transfer tokens from whale to USER_SENDER -// uint256 amountToTransfer = 100 * 1e18; -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); - -// vm.stopPrank(); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: APE_ETH_TOKEN, -// amountIn: 10 * 1e18, -// tokenOut: address(WETH_TOKEN), -// direction: SwapDirection.Token0ToToken1, -// supportsFeeOnTransfer: true -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_Reverse() public { -// test_CanSwap(); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(WETH_TOKEN), -// amountIn: 5 * 1e18, -// tokenOut: APE_ETH_TOKEN, -// direction: SwapDirection.Token1ToToken0, -// supportsFeeOnTransfer: false -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { -// MultiHopTestState memory state; -// state.isFeeOnTransfer = true; - -// // Setup tokens and pools -// state = _setupTokensAndPools(state); - -// // Execute and verify swap -// _executeAndVerifyMultiHopSwap(state); -// } - -// function test_CanSwap_MultiHop() public override { -// MultiHopTestState memory state; -// state.isFeeOnTransfer = false; - -// // Setup tokens and pools -// state = _setupTokensAndPools(state); - -// // Execute and verify swap -// _executeAndVerifyMultiHopSwap(state); -// } - -// // Test that the proper error is thrown when algebra swap fails -// function testRevert_SwapUnexpected() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Create invalid pool address -// address invalidPool = address(0x999); - -// // Mock token0() call on invalid pool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Create a route with an invalid pool -// bytes memory invalidRoute = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: invalidPool, -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Mock the algebra pool to not reset lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSelector( -// IAlgebraPool.swapSupportingFeeOnInputTokens.selector -// ), -// abi.encode(0, 0) -// ); - -// // Expect the AlgebraSwapUnexpected error -// vm.expectRevert(AlgebraSwapUnexpected.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// invalidRoute -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// // Helper function to setup tokens and pools -// function _setupTokensAndPools( -// MultiHopTestState memory state -// ) private returns (MultiHopTestState memory) { -// // Create tokens -// ERC20 tokenA = new ERC20( -// "Token A", -// state.isFeeOnTransfer ? "FTA" : "TA", -// 18 -// ); -// IERC20 tokenB; -// ERC20 tokenC = new ERC20( -// "Token C", -// state.isFeeOnTransfer ? "FTC" : "TC", -// 18 -// ); - -// if (state.isFeeOnTransfer) { -// tokenB = IERC20( -// address( -// new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) -// ) -// ); -// } else { -// tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); -// } - -// state.tokenA = IERC20(address(tokenA)); -// state.tokenB = tokenB; -// state.tokenC = IERC20(address(tokenC)); - -// // Label addresses -// vm.label(address(state.tokenA), "Token A"); -// vm.label(address(state.tokenB), "Token B"); -// vm.label(address(state.tokenC), "Token C"); - -// // Mint initial token supplies -// tokenA.mint(address(this), 1_000_000 * 1e18); -// if (!state.isFeeOnTransfer) { -// ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); -// } else { -// MockFeeOnTransferToken(address(tokenB)).mint( -// address(this), -// 1_000_000 * 1e18 -// ); -// } -// tokenC.mint(address(this), 1_000_000 * 1e18); - -// // Create pools -// state.pool1 = _createAlgebraPool( -// address(state.tokenA), -// address(state.tokenB) -// ); -// state.pool2 = _createAlgebraPool( -// address(state.tokenB), -// address(state.tokenC) -// ); - -// vm.label(state.pool1, "Pool 1"); -// vm.label(state.pool2, "Pool 2"); - -// // Add liquidity -// _addLiquidityToPool( -// state.pool1, -// address(state.tokenA), -// address(state.tokenB) -// ); -// _addLiquidityToPool( -// state.pool2, -// address(state.tokenB), -// address(state.tokenC) -// ); - -// state.amountToTransfer = 100 * 1e18; -// state.amountIn = 50 * 1e18; - -// // Transfer tokens to USER_SENDER -// IERC20(address(state.tokenA)).transfer( -// USER_SENDER, -// state.amountToTransfer -// ); - -// return state; -// } - -// // Helper function to execute and verify the swap -// function _executeAndVerifyMultiHopSwap( -// MultiHopTestState memory state -// ) private { -// vm.startPrank(USER_SENDER); - -// uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( -// USER_SENDER -// ); - -// // Approve spending -// IERC20(address(state.tokenA)).approve( -// address(liFiDEXAggregator), -// state.amountIn -// ); - -// // Build route -// bytes memory route = _buildMultiHopRouteForTest(state); - -// // Execute swap -// liFiDEXAggregator.processRoute( -// address(state.tokenA), -// state.amountIn, -// address(state.tokenC), -// 0, // No minimum amount out for testing -// USER_SENDER, -// route -// ); - -// // Verify results -// _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); - -// vm.stopPrank(); -// } - -// // Helper function to build the multi-hop route for test -// function _buildMultiHopRouteForTest( -// MultiHopTestState memory state -// ) private view returns (bytes memory) { -// bytes memory firstHop = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: address(state.tokenA), -// recipient: address(liFiDEXAggregator), -// pool: state.pool1, -// supportsFeeOnTransfer: false -// }) -// ); - -// bytes memory secondHop = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessMyERC20, -// tokenIn: address(state.tokenB), -// recipient: USER_SENDER, -// pool: state.pool2, -// supportsFeeOnTransfer: state.isFeeOnTransfer -// }) -// ); - -// return bytes.concat(firstHop, secondHop); -// } - -// // Helper function to verify multi-hop results -// function _verifyMultiHopResults( -// MultiHopTestState memory state, -// uint256 initialBalanceA, -// uint256 initialBalanceC -// ) private { -// uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( -// USER_SENDER -// ); -// uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( -// USER_SENDER -// ); - -// assertApproxEqAbs( -// initialBalanceA - finalBalanceA, -// state.amountIn, -// 1, // 1 wei tolerance -// "TokenA spent amount mismatch" -// ); -// assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); - -// emit log_named_uint( -// state.isFeeOnTransfer -// ? "Output amount with fee tokens" -// : "Output amount with regular tokens", -// finalBalanceC - initialBalanceC -// ); -// } - -// // Helper function to create an Algebra pool -// function _createAlgebraPool( -// address tokenA, -// address tokenB -// ) internal returns (address pool) { -// // Call the actual Algebra factory to create a pool -// pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( -// tokenA, -// tokenB -// ); -// return pool; -// } - -// // Helper function to add liquidity to a pool -// function _addLiquidityToPool( -// address pool, -// address token0, -// address token1 -// ) internal { -// // For fee-on-transfer tokens, we need to send more to account for the fee -// // We'll use a small amount and send extra to cover fees -// uint256 initialAmount0 = 1e17; // 0.1 token -// uint256 initialAmount1 = 1e17; // 0.1 token - -// // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) -// uint256 transferAmount0 = (initialAmount0 * 110) / 100; -// uint256 transferAmount1 = (initialAmount1 * 110) / 100; - -// // Initialize with 1:1 price ratio (Q64.96 format) -// uint160 initialPrice = uint160(1 << 96); -// IAlgebraPool(pool).initialize(initialPrice); - -// // Create AlgebraLiquidityAdderHelper with safe transfer logic -// AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( -// token0, -// token1 -// ); - -// // Transfer tokens with extra amounts to account for fees -// IERC20(token0).transfer( -// address(algebraLiquidityAdderHelper), -// transferAmount0 -// ); -// IERC20(token1).transfer( -// address(algebraLiquidityAdderHelper), -// transferAmount1 -// ); - -// // Get actual balances to use for liquidity, accounting for any fees -// uint256 actualBalance0 = IERC20(token0).balanceOf( -// address(algebraLiquidityAdderHelper) -// ); -// uint256 actualBalance1 = IERC20(token1).balanceOf( -// address(algebraLiquidityAdderHelper) -// ); - -// // Use the smaller of the two balances for liquidity amount -// uint128 liquidityAmount = uint128( -// actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 -// ); - -// // Add liquidity using the actual token amounts we have -// algebraLiquidityAdderHelper.addLiquidity( -// pool, -// -887220, -// 887220, -// liquidityAmount / 2 // Use half of available liquidity to ensure success -// ); -// } - -// struct MultiHopTestState { -// IERC20 tokenA; -// IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken -// IERC20 tokenC; -// address pool1; -// address pool2; -// uint256 amountIn; -// uint256 amountToTransfer; -// bool isFeeOnTransfer; -// } - -// struct AlgebraRouteParams { -// CommandType commandCode; // 1 for contract funds, 2 for user funds -// address tokenIn; // Input token address -// address recipient; // Address receiving the output tokens -// address pool; // Algebra pool address -// bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens -// } - -// // Helper function to build route for Apechain Algebra swap -// function _buildAlgebraRoute( -// AlgebraRouteParams memory params -// ) internal view returns (bytes memory route) { -// address token0 = IAlgebraPool(params.pool).token0(); -// bool zeroForOne = (params.tokenIn == token0); -// SwapDirection direction = zeroForOne -// ? SwapDirection.Token0ToToken1 -// : SwapDirection.Token1ToToken0; - -// route = abi.encodePacked( -// params.commandCode, -// params.tokenIn, -// uint8(1), // one pool -// FULL_SHARE, // 100% share -// uint8(PoolType.Algebra), -// params.pool, -// uint8(direction), -// params.recipient, -// params.supportsFeeOnTransfer ? uint8(1) : uint8(0) -// ); - -// return route; -// } - -// // Helper function to test an Algebra swap -// function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { -// // Find or create a pool -// address pool = _getPool(params.tokenIn, params.tokenOut); - -// vm.label(pool, "AlgebraPool"); - -// // Get token0 from pool and label tokens accordingly -// address token0 = IAlgebraPool(pool).token0(); -// if (params.tokenIn == token0) { -// vm.label( -// params.tokenIn, -// string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") -// ); -// vm.label( -// params.tokenOut, -// string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") -// ); -// } else { -// vm.label( -// params.tokenIn, -// string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") -// ); -// vm.label( -// params.tokenOut, -// string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") -// ); -// } - -// // Record initial balances -// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - -// // Get expected output from QuoterV2 -// // NOTE: There may be a small discrepancy between the quoted amount and the actual output -// // because the Quoter uses the regular swap() function for simulation while the actual -// // execution may use swapSupportingFeeOnInputTokens() for fee-on-transfer tokens. -// // The Quoter cannot accurately predict transfer fees taken by the token contract itself, -// // resulting in minor "dust" differences that are normal and expected when dealing with -// // non-standard token implementations. -// uint256 expectedOutput = _getQuoteExactInput( -// params.tokenIn, -// params.tokenOut, -// params.amountIn -// ); - -// // Build the route -// CommandType commandCode = params.from == address(liFiDEXAggregator) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: commandCode, -// tokenIn: params.tokenIn, -// recipient: params.to, -// pool: pool, -// supportsFeeOnTransfer: params.supportsFeeOnTransfer -// }) -// ); - -// // Approve tokens -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); - -// // Execute the swap -// address from = params.from == address(liFiDEXAggregator) -// ? USER_SENDER -// : params.from; - -// vm.expectEmit(true, true, true, false); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// expectedOutput, -// expectedOutput -// ); - -// uint256 minOut = (expectedOutput * 995) / 1000; // 0.5% slippage - -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// minOut, -// params.to, -// route -// ); - -// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - -// assertApproxEqAbs( -// initialTokenIn - finalTokenIn, -// params.amountIn, -// 1, // 1 wei tolerance -// "TokenIn amount mismatch" -// ); -// assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); -// } - -// function _getPool( -// address tokenA, -// address tokenB -// ) private view returns (address pool) { -// pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( -// tokenA, -// tokenB -// ); -// if (pool == address(0)) revert PoolDoesNotExist(); -// return pool; -// } - -// function _getQuoteExactInput( -// address tokenIn, -// address tokenOut, -// uint256 amountIn -// ) private returns (uint256 amountOut) { -// (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) -// .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); -// return amountOut; -// } - -// function testRevert_AlgebraSwap_ZeroAddressPool() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on address(0) -// vm.mockCall( -// address(0), -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with address(0) as pool -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: address(0), // Zero address pool -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS -// vm.mockCall( -// IMPOSSIBLE_POOL_ADDRESS, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with IMPOSSIBLE_POOL_ADDRESS as pool -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// function testRevert_AlgebraSwap_ZeroAddressRecipient() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on the pool -// vm.mockCall( -// ALGEBRA_POOL_APECHAIN, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with address(0) as recipient -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: address(0), // Zero address recipient -// pool: ALGEBRA_POOL_APECHAIN, -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } -// } - -// /** -// * @title LiFiDexAggregatorIzumiV3Test -// * @notice Tests specific to iZiSwap V3 pool type -// */ -// contract LiFiDexAggregatorIzumiV3Test is LiFiDexAggregatorTest { -// // ==================== iZiSwap V3 specific variables ==================== -// // Base constants -// address internal constant USDC = -// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; -// address internal constant WETH = -// 0x4200000000000000000000000000000000000006; -// address internal constant USDB_C = -// 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; - -// // iZiSwap pools -// address internal constant IZUMI_WETH_USDC_POOL = -// 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; -// address internal constant IZUMI_WETH_USDB_C_POOL = -// 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; - -// // Test parameters -// uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals -// uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals - -// // structs -// struct IzumiV3SwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// SwapDirection direction; -// } - -// struct MultiHopTestParams { -// address tokenIn; -// address tokenMid; -// address tokenOut; -// address pool1; -// address pool2; -// uint256 amountIn; -// SwapDirection direction1; -// SwapDirection direction2; -// } - -// error IzumiV3SwapUnexpected(); -// error IzumiV3SwapCallbackUnknownSource(); -// error IzumiV3SwapCallbackNotPositiveAmount(); - -// function setUp() public override { -// super.setUp(); - -// string memory baseRpc = vm.envString("ETH_NODE_URI_BASE"); -// vm.createSelectFork(baseRpc, 29831758); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); - -// // Setup labels -// vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); -// vm.label(USDC, "USDC"); -// vm.label(WETH, "WETH"); -// vm.label(USDB_C, "USDB-C"); -// vm.label(IZUMI_WETH_USDC_POOL, "WETH-USDC Pool"); -// vm.label(IZUMI_WETH_USDB_C_POOL, "WETH-USDB-C Pool"); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // Test USDC -> WETH -// deal(USDC, address(liFiDEXAggregator), AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// IzumiV3SwapTestParams({ -// from: address(liFiDEXAggregator), -// to: USER_SENDER, -// tokenIn: USDC, -// amountIn: AMOUNT_USDC, -// tokenOut: WETH, -// direction: SwapDirection.Token1ToToken0 -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// _testMultiHopSwap( -// MultiHopTestParams({ -// tokenIn: USDC, -// tokenMid: WETH, -// tokenOut: USDB_C, -// pool1: IZUMI_WETH_USDC_POOL, -// pool2: IZUMI_WETH_USDB_C_POOL, -// amountIn: AMOUNT_USDC, -// direction1: SwapDirection.Token1ToToken0, -// direction2: SwapDirection.Token0ToToken1 -// }) -// ); -// } - -// function test_CanSwap() public override { -// deal(address(USDC), USER_SENDER, AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); -// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - -// // fix the swap data encoding -// bytes memory swapData = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// USDC, -// uint8(SwapDirection.Token1ToToken0), -// IZUMI_WETH_USDC_POOL, -// USER_RECEIVER -// ); - -// vm.expectEmit(true, true, true, false); -// emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); - -// liFiDEXAggregator.processRoute( -// USDC, -// AMOUNT_USDC, -// WETH, -// 0, -// USER_RECEIVER, -// swapData -// ); - -// vm.stopPrank(); -// } - -// function testRevert_IzumiV3SwapUnexpected() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); - -// // create invalid pool address -// address invalidPool = address(0x999); - -// // create a route with an invalid pool -// bytes memory invalidRoute = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// USDC, -// uint8(SwapDirection.Token1ToToken0), -// invalidPool, -// USER_SENDER -// ); - -// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - -// // mock the iZiSwap pool to return without updating lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), -// abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool -// ); - -// vm.expectRevert(IzumiV3SwapUnexpected.selector); - -// liFiDEXAggregator.processRoute( -// USDC, -// AMOUNT_USDC, -// WETH, -// 0, -// USER_SENDER, -// invalidRoute -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// function testRevert_IzumiV3SwapCallbackUnknownSource() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// // create invalid pool address -// address invalidPool = address(0x999); - -// vm.prank(USER_SENDER); -// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - -// // mock the pool to call the callback directly without setting lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), -// abi.encode(0, 0) -// ); - -// // try to call the callback directly from the pool without setting lastCalledPool -// vm.prank(invalidPool); -// vm.expectRevert(IzumiV3SwapCallbackUnknownSource.selector); -// liFiDEXAggregator.swapY2XCallback(0, AMOUNT_USDC, abi.encode(USDC)); - -// vm.clearMockedCalls(); -// } - -// function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// // set lastCalledPool to the pool address to pass the unknown source check -// vm.store( -// address(liFiDEXAggregator), -// bytes32(uint256(3)), // slot for lastCalledPool -// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) -// ); - -// // try to call the callback with zero amount -// vm.prank(IZUMI_WETH_USDC_POOL); -// vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); -// liFiDEXAggregator.swapY2XCallback( -// 0, -// 0, // zero amount should trigger the error -// abi.encode(USDC) -// ); -// } - -// function testRevert_FailsIfAmountInIsTooLarge() public { -// deal(address(WETH), USER_SENDER, type(uint256).max); - -// vm.startPrank(USER_SENDER); -// IERC20(WETH).approve(address(liFiDEXAggregator), type(uint256).max); - -// // fix the swap data encoding -// bytes memory swapData = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// WETH, -// uint8(SwapDirection.Token0ToToken1), -// IZUMI_WETH_USDC_POOL, -// USER_RECEIVER -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// WETH, -// type(uint216).max, -// USDC, -// 0, -// USER_RECEIVER, -// swapData -// ); - -// vm.stopPrank(); -// } - -// function _testSwap(IzumiV3SwapTestParams memory params) internal { -// // Fund the sender with tokens if not the contract itself -// if (params.from != address(liFiDEXAggregator)) { -// deal(params.tokenIn, params.from, params.amountIn); -// } - -// // Capture initial token balances -// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( -// params.from -// ); -// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( -// params.to -// ); - -// // Build the route based on the command type -// CommandType commandCode = params.from == address(liFiDEXAggregator) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; - -// // Construct the route -// bytes memory route = _buildIzumiV3Route( -// commandCode, -// params.tokenIn, -// uint8(params.direction == SwapDirection.Token0ToToken1 ? 1 : 0), -// IZUMI_WETH_USDC_POOL, -// params.to -// ); - -// // Approve tokens if necessary -// if (params.from == USER_SENDER) { -// vm.startPrank(USER_SENDER); -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); -// } - -// // Expect the Route event emission -// address from = params.from == address(liFiDEXAggregator) -// ? USER_SENDER -// : params.from; - -// vm.expectEmit(true, true, true, false); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// 0, // No minimum amount enforced in test -// 0 // Actual amount will be checked after the swap -// ); - -// // Execute the swap -// uint256 amountOut = liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// 0, // No minimum amount for testing -// params.to, -// route -// ); - -// if (params.from == USER_SENDER) { -// vm.stopPrank(); -// } - -// // Verify balances have changed correctly -// uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); - -// assertApproxEqAbs( -// initialBalanceIn - finalBalanceIn, -// params.amountIn, -// 1, // 1 wei tolerance because of undrain protection for dex aggregator -// "TokenIn amount mismatch" -// ); -// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); -// assertEq( -// amountOut, -// finalBalanceOut - initialBalanceOut, -// "AmountOut mismatch" -// ); - -// emit log_named_uint("Amount In", params.amountIn); -// emit log_named_uint("Amount Out", amountOut); -// } - -// function _testMultiHopSwap(MultiHopTestParams memory params) internal { -// // Fund the sender with tokens -// deal(params.tokenIn, USER_SENDER, params.amountIn); - -// // Capture initial token balances -// uint256 initialBalanceIn; -// uint256 initialBalanceOut; - -// initialBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// initialBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// // Build multi-hop route -// bytes memory route = _buildIzumiV3MultiHopRoute(params); - -// // Approve tokens -// vm.startPrank(USER_SENDER); -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); - -// // Execute the swap -// uint256 amountOut = liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// 0, // No minimum amount for testing -// USER_SENDER, -// route -// ); -// vm.stopPrank(); - -// // Verify balances have changed correctly -// uint256 finalBalanceIn; -// uint256 finalBalanceOut; - -// finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// assertEq( -// initialBalanceIn - finalBalanceIn, -// params.amountIn, -// "TokenIn amount mismatch" -// ); -// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); -// assertEq( -// amountOut, -// finalBalanceOut - initialBalanceOut, -// "AmountOut mismatch" -// ); -// } - -// function _buildIzumiV3Route( -// CommandType commandCode, -// address tokenIn, -// uint8 direction, -// address pool, -// address recipient -// ) internal pure returns (bytes memory) { -// return -// abi.encodePacked( -// uint8(commandCode), -// tokenIn, -// uint8(1), // number of pools (1) -// FULL_SHARE, // 100% share -// uint8(PoolType.iZiSwap), // pool type -// pool, -// uint8(direction), -// recipient -// ); -// } - -// function _buildIzumiV3MultiHopRoute( -// MultiHopTestParams memory params -// ) internal view returns (bytes memory) { -// // First hop: USER_ERC20 -> LDA -// bytes memory firstHop = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// params.tokenIn, -// uint8(params.direction1), -// params.pool1, -// address(liFiDEXAggregator) -// ); - -// // Second hop: MY_ERC20 (LDA) -> pool2 -// bytes memory secondHop = _buildIzumiV3Route( -// CommandType.ProcessMyERC20, -// params.tokenMid, -// uint8(params.direction2), -// params.pool2, -// USER_SENDER // final recipient -// ); - -// // Combine the two hops -// return bytes.concat(firstHop, secondHop); -// } -// } - -// ----------------------------------------------------------------------------- -// HyperswapV3 on HyperEVM -// ----------------------------------------------------------------------------- -contract LiFiDexAggregatorHyperswapV3Test is LiFiDexAggregatorUpgradeTest { - using SafeERC20 for IERC20; +/** + * @title VelodromeV2 tests + * @notice Tests specific to Velodrome V2 pool type + */ +contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { + + VelodromeV2Facet internal velodromeV2Facet; + + // ==================== Velodrome V2 specific variables ==================== + IVelodromeV2Router internal constant VELODROME_V2_ROUTER = + IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router + address internal constant VELODROME_V2_FACTORY_REGISTRY = + 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; + IERC20 internal constant STG_TOKEN = + IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); + IERC20 internal constant USDC_E_TOKEN = + IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); + + MockVelodromeV2FlashLoanCallbackReceiver + internal mockFlashloanCallbackReceiver; + + // Velodrome V2 structs + struct VelodromeV2SwapTestParams { + address from; + address to; + address tokenIn; + uint256 amountIn; + address tokenOut; + bool stable; + SwapDirection direction; + bool callback; + } + + struct MultiHopTestParams { + address tokenIn; + address tokenMid; + address tokenOut; + address pool1; + address pool2; + uint256[] amounts1; + uint256[] amounts2; + uint256 pool1Fee; + uint256 pool2Fee; + } + + struct ReserveState { + uint256 reserve0Pool1; + uint256 reserve1Pool1; + uint256 reserve0Pool2; + uint256 reserve1Pool2; + } - /// @dev HyperswapV3 router on HyperEVM chain - IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = - IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); - /// @dev HyperswapV3 quoter on HyperEVM chain - IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = - IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); - - /// @dev a liquid USDT on HyperEVM - IERC20 internal constant USDT0 = - IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); - /// @dev WHYPE on HyperEVM - IERC20 internal constant WHYPE = - IERC20(0x5555555555555555555555555555555555555555); - - struct HyperswapV3Params { - CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 - address tokenIn; // Input token address - address recipient; // Address receiving the output tokens - address pool; // HyperswapV3 pool address - bool zeroForOne; // Direction of the swap + // Setup function for Optimism tests + function setupOptimism() internal { + customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; + customBlockNumberForForking = 133999121; } function setUp() public override { - setupHyperEVM(); + setupOptimism(); + super.setUp(); } + function _addDexFacet() internal override { + velodromeV2Facet = new VelodromeV2Facet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; + addFacet(address(ldaDiamond), address(velodromeV2Facet), functionSelectors); + + velodromeV2Facet = VelodromeV2Facet(payable(address(ldaDiamond))); + } + + // ============================ Velodrome V2 Tests ============================ + + // no stable swap function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + vm.startPrank(USER_SENDER); + + _testSwap( + VelodromeV2SwapTestParams({ + from: address(USER_SENDER), + to: address(USER_SENDER), + tokenIn: ADDRESS_USDC, + amountIn: 1_000 * 1e6, + tokenOut: address(STG_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callback: false + }) + ); - deal(address(USDT0), USER_SENDER, amountIn); + vm.stopPrank(); + } - // user approves - vm.prank(USER_SENDER); - USDT0.approve(address(liFiDEXAggregator), amountIn); + function test_CanSwap_NoStable_Reverse() public { + // first perform the forward swap. + test_CanSwap(); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(STG_TOKEN), + amountIn: 500 * 1e18, + tokenOut: ADDRESS_USDC, + stable: false, + direction: SwapDirection.Token1ToToken0, + callback: false + }) + ); + vm.stopPrank(); + } - // fetch the real pool and quote - address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), - 3000 + function test_CanSwap_Stable() public { + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: ADDRESS_USDC, + amountIn: 1_000 * 1e6, + tokenOut: address(USDC_E_TOKEN), + stable: true, + direction: SwapDirection.Token0ToToken1, + callback: false + }) ); + vm.stopPrank(); + } - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn, - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params + function test_CanSwap_Stable_Reverse() public { + // first perform the forward stable swap. + test_CanSwap_Stable(); + + vm.startPrank(USER_SENDER); + + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(USDC_E_TOKEN), + amountIn: 500 * 1e6, + tokenOut: ADDRESS_USDC, + stable: false, + direction: SwapDirection.Token1ToToken0, + callback: false + }) ); + vm.stopPrank(); + } - // build the "off-chain" route - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDT0), - uint8(1), // 1 pool - uint16(65535), // FULL_SHARE - UniV3StyleFacet.swapUniV3.selector, // UNIV3 selector - pool, - uint8(0), // zeroForOne = true if USDT0 < WHYPE - address(USER_SENDER) + function test_CanSwap_FromDexAggregator() public override { + // fund dex aggregator contract so that the contract holds USDC + deal(ADDRESS_USDC, address(ldaDiamond), 100_000 * 1e6); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: address(ldaDiamond), + to: address(USER_SENDER), + tokenIn: ADDRESS_USDC, + amountIn: IERC20(ADDRESS_USDC).balanceOf( + address(ldaDiamond) + ) - 1, // adjust for slot undrain protection: subtract 1 token so that the aggregator's balance isn't completely drained, matching the contract's safeguard + tokenOut: address(USDC_E_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callback: false + }) ); + vm.stopPrank(); + } + + function test_CanSwap_FlashloanCallback() public { + mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: address(USER_SENDER), + to: address(mockFlashloanCallbackReceiver), + tokenIn: ADDRESS_USDC, + amountIn: 1_000 * 1e6, + tokenOut: address(USDC_E_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callback: true + }) + ); + vm.stopPrank(); + } + + // Override the abstract test with VelodromeV2 implementation + function test_CanSwap_MultiHop() public override { + vm.startPrank(USER_SENDER); + + // Setup routes and get amounts + MultiHopTestParams memory params = _setupRoutes( + ADDRESS_USDC, + address(STG_TOKEN), + address(USDC_E_TOKEN), + false, + false + ); + + // Get initial reserves BEFORE the swap + ReserveState memory initialReserves; + ( + initialReserves.reserve0Pool1, + initialReserves.reserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + initialReserves.reserve0Pool2, + initialReserves.reserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( + USER_SENDER + ); + uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( + USER_SENDER + ); + + // Build route and execute swap + bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); + + // Approve and execute + IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - // expect the Route event vm.expectEmit(true, true, true, true); emit Route( USER_SENDER, USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn, - quoted, - quoted + params.tokenIn, + params.tokenOut, + 1000 * 1e6, + params.amounts2[1], + params.amounts2[1] ); - // execute - vm.prank(USER_SENDER); - liFiDEXAggregator.processRoute( - address(USDT0), - amountIn, - address(WHYPE), - quoted, + coreRouteFacet.processRoute( + params.tokenIn, + 1000 * 1e6, + params.tokenOut, + params.amounts2[1], USER_SENDER, route ); - } - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + _verifyUserBalances(params, initialBalance1, initialBalance2); + _verifyReserves(params, initialReserves); - // Fund dex aggregator contract - deal(address(USDT0), address(liFiDEXAggregator), amountIn); + vm.stopPrank(); + } - // fetch the real pool and quote - address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), - 3000 - ); + function test_CanSwap_MultiHop_WithStable() public { + vm.startPrank(USER_SENDER); - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params + // Setup routes and get amounts for stable->volatile path + MultiHopTestParams memory params = _setupRoutes( + ADDRESS_USDC, + address(USDC_E_TOKEN), + address(STG_TOKEN), + true, // stable pool for first hop + false // volatile pool for second hop ); - // Build route using our helper function - bytes memory route = _buildHyperswapV3Route( - HyperswapV3Params({ - commandCode: CommandType.ProcessMyERC20, - tokenIn: address(USDT0), - recipient: USER_SENDER, - pool: pool, - zeroForOne: true // USDT0 < WHYPE - }) + // Get initial reserves BEFORE the swap + ReserveState memory initialReserves; + ( + initialReserves.reserve0Pool1, + initialReserves.reserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + initialReserves.reserve0Pool2, + initialReserves.reserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + // Record initial balances + uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( + USER_SENDER ); + uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( + USER_SENDER + ); + + // Build route and execute swap + bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + + // Approve and execute + IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - // expect the Route event vm.expectEmit(true, true, true, true); emit Route( USER_SENDER, USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn - 1, // Account for slot undrain protection - quoted, - quoted + params.tokenIn, + params.tokenOut, + 1000 * 1e6, + params.amounts2[1], + params.amounts2[1] ); - // execute - vm.prank(USER_SENDER); - liFiDEXAggregator.processRoute( - address(USDT0), - amountIn - 1, // Account for slot undrain protection - address(WHYPE), - quoted, + coreRouteFacet.processRoute( + params.tokenIn, + 1000 * 1e6, + params.tokenOut, + params.amounts2[1], USER_SENDER, route ); + + _verifyUserBalances(params, initialBalance1, initialBalance2); + _verifyReserves(params, initialReserves); + + vm.stopPrank(); } - function test_CanSwap_MultiHop() public override { - // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. - // HyperswapV3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. HyperswapV3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. + function testRevert_InvalidPoolOrRecipient() public { + vm.startPrank(USER_SENDER); + + // Get a valid pool address first for comparison + address validPool = VELODROME_V2_ROUTER.poolFor( + ADDRESS_USDC, + address(STG_TOKEN), + false, + VELODROME_V2_FACTORY_REGISTRY + ); + + // Test case 1: Zero pool address + bytes memory routeWithZeroPool = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + ADDRESS_USDC, + uint8(1), + FULL_SHARE, + uint8(PoolType.VelodromeV2), + address(0), + uint8(SwapDirection.Token1ToToken0), + USER_SENDER, + uint8(CallbackStatus.Disabled) + ); + + IERC20(ADDRESS_USDC).approve(address(ldaDiamond), 1000 * 1e6); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + ADDRESS_USDC, + 1000 * 1e6, + address(STG_TOKEN), + 0, + USER_SENDER, + routeWithZeroPool + ); + + // Test case 2: Zero recipient address + bytes memory routeWithZeroRecipient = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + ADDRESS_USDC, + uint8(1), + FULL_SHARE, + uint8(PoolType.VelodromeV2), + validPool, + uint8(SwapDirection.Token1ToToken0), + address(0), + uint8(CallbackStatus.Disabled) + ); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + ADDRESS_USDC, + 1000 * 1e6, + address(STG_TOKEN), + 0, + USER_SENDER, + routeWithZeroRecipient + ); + + vm.stopPrank(); } - function _buildHyperswapV3Route( - HyperswapV3Params memory params - ) internal pure returns (bytes memory route) { - route = abi.encodePacked( - uint8(params.commandCode), + function testRevert_WrongPoolReserves() public { + vm.startPrank(USER_SENDER); + + // Setup multi-hop route: USDC -> STG -> USDC.e + MultiHopTestParams memory params = _setupRoutes( + ADDRESS_USDC, + address(STG_TOKEN), + address(USDC_E_TOKEN), + false, + false + ); + + // Build multi-hop route + bytes memory firstHop = _buildFirstHop( params.tokenIn, - uint8(1), // 1 pool - FULL_SHARE, // 65535 - 100% share - UniV3StyleFacet.swapUniV3.selector, // UNIV3 selector - params.pool, - uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false - params.recipient + params.pool1, + params.pool2, + 1 // direction ); - return route; + bytes memory secondHop = _buildSecondHop( + params.tokenMid, + params.pool2, + USER_SENDER, + 0 // direction + ); + + bytes memory route = bytes.concat(firstHop, secondHop); + + deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); + + IERC20(ADDRESS_USDC).approve(address(ldaDiamond), 1000 * 1e6); + + // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves + vm.mockCall( + params.pool2, + abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), + abi.encode(0, 0, block.timestamp) + ); + + vm.expectRevert(WrongPoolReserves.selector); + + coreRouteFacet.processRoute( + ADDRESS_USDC, + 1000 * 1e6, + address(USDC_E_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); } -} -// // ----------------------------------------------------------------------------- -// // LaminarV3 on HyperEVM -// // ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorLaminarV3Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// IERC20 internal constant WHYPE = -// IERC20(0x5555555555555555555555555555555555555555); -// IERC20 internal constant LHYPE = -// IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); - -// address internal constant WHYPE_LHYPE_POOL = -// 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; - -// function setUp() public override { -// setupHyperEVM(); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // Fund the user with WHYPE -// deal(address(WHYPE), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WHYPE.approve(address(liFiDEXAggregator), amountIn); - -// // Build a single-pool UniV3 route -// bool zeroForOne = address(WHYPE) > address(LHYPE); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(WHYPE), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), -// WHYPE_LHYPE_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// // Record balances -// uint256 inBefore = WHYPE.balanceOf(USER_SENDER); -// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - -// // Execute swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(WHYPE), -// amountIn, -// address(LHYPE), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify -// uint256 inAfter = WHYPE.balanceOf(USER_SENDER); -// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the aggregator directly -// deal(address(WHYPE), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); - -// bool zeroForOne = address(WHYPE) > address(LHYPE); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(WHYPE), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// WHYPE_LHYPE_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - -// // Withdraw 1 wei to avoid slot-undrain protection -// liFiDEXAggregator.processRoute( -// address(WHYPE), -// amountIn - 1, -// address(LHYPE), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. -// // Laminar V3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. Laminar V3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } -// } - -// contract LiFiDexAggregatorXSwapV3Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// address internal constant USDC_E_WXDC_POOL = -// 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; - -// /// @dev our two tokens: USDC.e and wrapped XDC -// IERC20 internal constant USDC_E = -// IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); -// IERC20 internal constant WXDC = -// IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); - -// function setUp() public override { -// customRpcUrlForForking = "ETH_NODE_URI_XDC"; -// customBlockNumberForForking = 89279495; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e6; -// deal(address(USDC_E), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// USDC_E.approve(address(liFiDEXAggregator), amountIn); - -// // Build a one-pool V3 route -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(USDC_E), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), -// USDC_E_WXDC_POOL, -// uint8(1), // zeroForOne (USDC.e > WXDC) -// USER_SENDER -// ); - -// // Record balances before swap -// uint256 inBefore = USDC_E.balanceOf(USER_SENDER); -// uint256 outBefore = WXDC.balanceOf(USER_SENDER); - -// // Execute swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(USDC_E), -// amountIn, -// address(WXDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify balances after swap -// uint256 inAfter = USDC_E.balanceOf(USER_SENDER); -// uint256 outAfter = WXDC.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - -// vm.stopPrank(); -// } - -// /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 5_000 * 1e6; - -// // fund the aggregator -// deal(address(USDC_E), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); - -// // Account for slot-undrain protection -// uint256 swapAmount = amountIn - 1; - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(USDC_E), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// USDC_E_WXDC_POOL, -// uint8(1), // zeroForOne (USDC.e > WXDC) -// USER_SENDER -// ); - -// // Record balances before swap -// uint256 outBefore = WXDC.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(USDC_E), -// swapAmount, -// address(WXDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify balances after swap -// uint256 outAfter = WXDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. -// // XSwap V3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. XSwap V3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } -// } - -// // ----------------------------------------------------------------------------- -// // RabbitSwap on Viction -// // ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorRabbitSwapTest is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// // Constants for RabbitSwap on Viction -// IERC20 internal constant SOROS = -// IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); -// IERC20 internal constant C98 = -// IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); -// address internal constant SOROS_C98_POOL = -// 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; - -// function setUp() public override { -// // setup for Viction network -// customRpcUrlForForking = "ETH_NODE_URI_VICTION"; -// customBlockNumberForForking = 94490946; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the user with SOROS -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(liFiDEXAggregator), amountIn); - -// // build a single-pool UniV3-style route -// bool zeroForOne = address(SOROS) > address(C98); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), // RabbitSwap uses UniV3 pool type -// SOROS_C98_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// // record balances before swap -// uint256 inBefore = SOROS.balanceOf(USER_SENDER); -// uint256 outBefore = C98.balanceOf(USER_SENDER); - -// // execute swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// // verify balances after swap -// uint256 inAfter = SOROS.balanceOf(USER_SENDER); -// uint256 outAfter = C98.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive C98"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the aggregator directly -// deal(address(SOROS), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); - -// bool zeroForOne = address(SOROS) > address(C98); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// SOROS_C98_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// uint256 outBefore = C98.balanceOf(USER_SENDER); - -// // withdraw 1 wei less to avoid slot-undrain protection -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn - 1, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 outAfter = C98.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive C98"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. -// // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, -// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. UniV3-style pools immediately revert on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// function testRevert_RabbitSwapInvalidPool() public { -// uint256 amountIn = 1_000 * 1e18; -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(liFiDEXAggregator), amountIn); - -// // build route with invalid pool address -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// address(0), // invalid pool address -// uint8(0), -// USER_SENDER -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } - -// function testRevert_RabbitSwapInvalidRecipient() public { -// uint256 amountIn = 1_000 * 1e18; -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(liFiDEXAggregator), amountIn); - -// // build route with invalid recipient -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// SOROS_C98_POOL, -// uint8(0), -// address(0) // invalid recipient -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } -// } - -// // ---------------------------------------------- -// // EnosysDexV3 on Flare -// // ---------------------------------------------- -// contract LiFiDexAggregatorEnosysDexV3Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// /// @dev HLN token on Flare -// IERC20 internal constant HLN = -// IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); - -// /// @dev USDT0 token on Flare -// IERC20 internal constant USDT0 = -// IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); - -// /// @dev The single EnosysDexV3 pool for HLN–USDT0 -// address internal constant ENOSYS_V3_POOL = -// 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; - -// /// @notice Set up a fork of Flare at block 42652369 and initialize the aggregator -// function setUp() public override { -// customRpcUrlForForking = "ETH_NODE_URI_FLARE"; -// customBlockNumberForForking = 42652369; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// /// @notice Single‐pool swap: USER sends HLN → receives USDT0 -// function test_CanSwap() public override { -// // Mint 1 000 HLN to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(HLN), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// HLN.approve(address(liFiDEXAggregator), amountIn); - -// bool zeroForOne = address(HLN) > address(USDT0); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(HLN), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), // V3‐style pool -// ENOSYS_V3_POOL, // pool address -// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1, 1 = token1→token0 -// address(USER_SENDER) // recipient -// ); - -// // Record balances before swap -// uint256 inBefore = HLN.balanceOf(USER_SENDER); -// uint256 outBefore = USDT0.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(HLN), -// amountIn, -// address(USDT0), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that HLN was spent and some USDT0 was received -// uint256 inAfter = HLN.balanceOf(USER_SENDER); -// uint256 outAfter = USDT0.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - -// vm.stopPrank(); -// } - -// /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 -// function test_CanSwap_FromDexAggregator() public override { -// // Fund the aggregator with 1 000 HLN -// uint256 amountIn = 1_000 * 1e18; -// deal(address(HLN), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); -// bool zeroForOne = address(HLN) > address(USDT0); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(HLN), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), // V3‐style pool -// ENOSYS_V3_POOL, // pool address -// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1 -// address(USER_SENDER) // recipient -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDT0.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(HLN), -// swapAmount, -// address(USDT0), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDT0 was received -// uint256 outAfter = USDT0.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. -// // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, -// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. UniV3-style pools immediately revert on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } -// } - -// // ---------------------------------------------- -// // SyncSwapV2 on Linea -// // ---------------------------------------------- -// contract LiFiDexAggregatorSyncSwapV2Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// IERC20 internal constant USDC = -// IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); -// IERC20 internal constant WETH = -// IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); -// address internal constant USDC_WETH_POOL_V1 = -// address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); -// address internal constant SYNC_SWAP_VAULT = -// address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); - -// address internal constant USDC_WETH_POOL_V2 = -// address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); - -// IERC20 internal constant USDT = -// IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); -// address internal constant USDC_USDT_POOL_V1 = -// address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); - -// /// @notice Set up a fork of Linea at block 20077881 and initialize the aggregator -// function setUp() public override { -// customRpcUrlForForking = "ETH_NODE_URI_LINEA"; -// customBlockNumberForForking = 20077881; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// /// @notice Single‐pool swap: USER sends WETH → receives USDC -// function test_CanSwap() public override { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Record balances before swap -// uint256 inBefore = WETH.balanceOf(USER_SENDER); -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that WETH was spent and some USDC_C was received -// uint256 inAfter = WETH.balanceOf(USER_SENDER); -// uint256 outAfter = USDC.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_PoolV2() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V2, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(0) // isV1Pool -// ); - -// // Record balances before swap -// uint256 inBefore = WETH.balanceOf(USER_SENDER); -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that WETH was spent and some USDC_C was received -// uint256 inAfter = WETH.balanceOf(USER_SENDER); -// uint256 outAfter = USDC.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // Fund the aggregator with 1 000 WETH -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(WETH), -// swapAmount, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDC was received -// uint256 outAfter = USDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator_PoolV2() public { -// // Fund the aggregator with 1 000 WETH -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V2, // pool address -// address(USER_SENDER), // recipient -// uint8(2) // withdrawMode -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(WETH), -// swapAmount, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDC was received -// uint256 outAfter = USDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// uint256 amountIn = 1_000e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); -// uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); - -// // -// // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) -// // -// bytes memory hop1 = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(WETH), -// uint8(1), // one pool -// FULL_SHARE, // 100% of the WETH -// uint8(PoolType.SyncSwapV2), -// USDC_WETH_POOL_V1, // the V1 pool -// SYNC_SWAP_VAULT, // “to” = the vault address -// uint8(2), // withdrawMode = 2 -// uint8(1), // isV1Pool = true -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // -// // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 -// // -// bytes memory hop2 = abi.encodePacked( -// uint8(CommandType.ProcessOnePool), -// address(USDC), -// uint8(PoolType.SyncSwapV2), -// USDC_USDT_POOL_V1, // V1 USDC⟶USDT pool -// address(USER_SENDER), // send the USDT home -// uint8(2), // withdrawMode = 2 -// uint8(1), // isV1Pool = true -// SYNC_SWAP_VAULT // vault -// ); - -// bytes memory route = bytes.concat(hop1, hop2); - -// uint256 amountOut = liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDT), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); -// uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); - -// assertEq( -// initialBalanceIn - afterBalanceIn, -// amountIn, -// "WETH spent mismatch" -// ); -// assertEq( -// amountOut, -// afterBalanceOut - initialBalanceOut, -// "USDT amountOut mismatch" -// ); -// vm.stopPrank(); -// } - -// function testRevert_V1PoolMissingVaultAddress() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(0) // vault (invalid address) -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidPoolOrRecipient() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory routeWithInvalidPool = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// address(0), // pool address (invalid address) -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidPool -// ); - -// bytes memory routeWithInvalidRecipient = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(0), // recipient (invalid address) -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidRecipient -// ); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidWithdrawMode() public { -// vm.startPrank(USER_SENDER); - -// bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address (invalid address) -// address(USER_SENDER), // recipient -// uint8(3), // withdrawMode (invalid) -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Expect revert with InvalidCallData because withdrawMode is invalid -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// 1, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidWithdrawMode -// ); - -// vm.stopPrank(); -// } -// } + // ============================ Velodrome V2 Helper Functions ============================ + + /** + * @dev Helper function to test a VelodromeV2 swap. + * Uses a struct to group parameters and reduce stack depth. + */ + function _testSwap(VelodromeV2SwapTestParams memory params) internal { + // get expected output amounts from the router. + IVelodromeV2Router.Route[] + memory routes = new IVelodromeV2Router.Route[](1); + routes[0] = IVelodromeV2Router.Route({ + from: params.tokenIn, + to: params.tokenOut, + stable: params.stable, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( + params.amountIn, + routes + ); + emit log_named_uint("Expected amount out", amounts[1]); + + // Retrieve the pool address. + address pool = VELODROME_V2_ROUTER.poolFor( + params.tokenIn, + params.tokenOut, + params.stable, + VELODROME_V2_FACTORY_REGISTRY + ); + emit log_named_uint("Pool address:", uint256(uint160(pool))); + + // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. + CommandType commandCode = params.from == address(ldaDiamond) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20; + + console2.log("VelodromeV2Facet.swapVelodromeV2.selector"); + console2.logBytes4(VelodromeV2Facet.swapVelodromeV2.selector); + // build the route. + bytes memory route = abi.encodePacked( + uint8(commandCode), + params.tokenIn, + uint8(1), + FULL_SHARE, + VelodromeV2Facet.swapVelodromeV2.selector, + pool, + params.direction, + params.to, + params.callback + ? uint8(CallbackStatus.Enabled) + : uint8(CallbackStatus.Disabled) + ); + + // approve the aggregator to spend tokenIn. + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + + // capture initial token balances. + uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + emit log_named_uint("Initial tokenIn balance", initialTokenIn); + + address from = params.from == address(ldaDiamond) + ? USER_SENDER + : params.from; + if (params.callback == true) { + vm.expectEmit(true, false, false, false); + emit HookCalled( + address(ldaDiamond), + 0, + 0, + abi.encode(params.tokenIn) + ); + } + // vm.expectEmit(true, true, true, true); + // emit Route( + // from, + // params.to, + // params.tokenIn, + // params.tokenOut, + // params.amountIn, + // amounts[1], + // amounts[1] + // ); + + // execute the swap + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + amounts[1], + params.to, + route + ); + + uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); + emit log_named_uint( + "TokenOut received", + finalTokenOut - initialTokenOut + ); + + assertApproxEqAbs( + initialTokenIn - finalTokenIn, + params.amountIn, + 1, // 1 wei tolerance + "TokenIn amount mismatch" + ); + assertEq( + finalTokenOut - initialTokenOut, + amounts[1], + "TokenOut amount mismatch" + ); + } + + // Helper function to set up routes and get amounts + function _setupRoutes( + address tokenIn, + address tokenMid, + address tokenOut, + bool isStableFirst, + bool isStableSecond + ) private view returns (MultiHopTestParams memory params) { + params.tokenIn = tokenIn; + params.tokenMid = tokenMid; + params.tokenOut = tokenOut; + + // Setup first hop route + IVelodromeV2Router.Route[] + memory routes1 = new IVelodromeV2Router.Route[](1); + routes1[0] = IVelodromeV2Router.Route({ + from: tokenIn, + to: tokenMid, + stable: isStableFirst, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( + 1000 * 1e6, + routes1 + ); + + // Setup second hop route + IVelodromeV2Router.Route[] + memory routes2 = new IVelodromeV2Router.Route[](1); + routes2[0] = IVelodromeV2Router.Route({ + from: tokenMid, + to: tokenOut, + stable: isStableSecond, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( + params.amounts1[1], + routes2 + ); + + // Get pool addresses + params.pool1 = VELODROME_V2_ROUTER.poolFor( + tokenIn, + tokenMid, + isStableFirst, + VELODROME_V2_FACTORY_REGISTRY + ); + + params.pool2 = VELODROME_V2_ROUTER.poolFor( + tokenMid, + tokenOut, + isStableSecond, + VELODROME_V2_FACTORY_REGISTRY + ); + + // Get pool fees info + params.pool1Fee = IVelodromeV2PoolFactory( + VELODROME_V2_FACTORY_REGISTRY + ).getFee(params.pool1, isStableFirst); + params.pool2Fee = IVelodromeV2PoolFactory( + VELODROME_V2_FACTORY_REGISTRY + ).getFee(params.pool2, isStableSecond); + + return params; + } + + // function to build first hop of the route + function _buildFirstHop( + address tokenIn, + address pool1, + address pool2, + uint8 direction + ) private pure returns (bytes memory) { + return + abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + uint8(CommandType.ProcessUserERC20), + tokenIn, + uint8(1), + FULL_SHARE, + pool1, + direction, + pool2, + uint8(CallbackStatus.Disabled) + ); + } + + // function to build second hop of the route + function _buildSecondHop( + address tokenMid, + address pool2, + address recipient, + uint8 direction + ) private pure returns (bytes memory) { + return + abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + uint8(CommandType.ProcessOnePool), + tokenMid, + pool2, + direction, + recipient, + uint8(CallbackStatus.Disabled) + ); + } + + // route building function + function _buildMultiHopRoute( + MultiHopTestParams memory params, + address recipient, + uint8 firstHopDirection, + uint8 secondHopDirection + ) private pure returns (bytes memory) { + bytes memory firstHop = _buildFirstHop( + params.tokenIn, + params.pool1, + params.pool2, + firstHopDirection + ); + + bytes memory secondHop = _buildSecondHop( + params.tokenMid, + params.pool2, + recipient, + secondHopDirection + ); + + return bytes.concat(firstHop, secondHop); + } + + function _verifyUserBalances( + MultiHopTestParams memory params, + uint256 initialBalance1, + uint256 initialBalance2 + ) private { + // Verify token balances + uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); + uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); + + assertApproxEqAbs( + initialBalance1 - finalBalance1, + 1000 * 1e6, + 1, // 1 wei tolerance + "Token1 spent amount mismatch" + ); + assertEq( + finalBalance2 - initialBalance2, + params.amounts2[1], + "Token2 received amount mismatch" + ); + } + + function _verifyReserves( + MultiHopTestParams memory params, + ReserveState memory initialReserves + ) private { + // Get reserves after swap + ( + uint256 finalReserve0Pool1, + uint256 finalReserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + uint256 finalReserve0Pool2, + uint256 finalReserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); + address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); + + // Calculate exact expected changes + uint256 amountInAfterFees = 1000 * + 1e6 - + ((1000 * 1e6 * params.pool1Fee) / 10000); + + // Assert exact reserve changes for Pool1 + if (token0Pool1 == params.tokenIn) { + // tokenIn is token0, so reserve0 should increase and reserve1 should decrease + assertEq( + finalReserve0Pool1 - initialReserves.reserve0Pool1, + amountInAfterFees, + "Pool1 reserve0 (tokenIn) change incorrect" + ); + assertEq( + initialReserves.reserve1Pool1 - finalReserve1Pool1, + params.amounts1[1], + "Pool1 reserve1 (tokenMid) change incorrect" + ); + } else { + // tokenIn is token1, so reserve1 should increase and reserve0 should decrease + assertEq( + finalReserve1Pool1 - initialReserves.reserve1Pool1, + amountInAfterFees, + "Pool1 reserve1 (tokenIn) change incorrect" + ); + assertEq( + initialReserves.reserve0Pool1 - finalReserve0Pool1, + params.amounts1[1], + "Pool1 reserve0 (tokenMid) change incorrect" + ); + } + + // Assert exact reserve changes for Pool2 + if (token0Pool2 == params.tokenMid) { + // tokenMid is token0, so reserve0 should increase and reserve1 should decrease + assertEq( + finalReserve0Pool2 - initialReserves.reserve0Pool2, + params.amounts1[1] - + ((params.amounts1[1] * params.pool2Fee) / 10000), + "Pool2 reserve0 (tokenMid) change incorrect" + ); + assertEq( + initialReserves.reserve1Pool2 - finalReserve1Pool2, + params.amounts2[1], + "Pool2 reserve1 (tokenOut) change incorrect" + ); + } else { + // tokenMid is token1, so reserve1 should increase and reserve0 should decrease + assertEq( + finalReserve1Pool2 - initialReserves.reserve1Pool2, + params.amounts1[1] - + ((params.amounts1[1] * params.pool2Fee) / 10000), + "Pool2 reserve1 (tokenMid) change incorrect" + ); + assertEq( + initialReserves.reserve0Pool2 - finalReserve0Pool2, + params.amounts2[1], + "Pool2 reserve0 (tokenOut) change incorrect" + ); + } + } +} diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 39f9c9c04..cceb2efc2 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -13,6 +13,7 @@ import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; import { ReentrancyError, ETHTransferFailed } from "src/Errors/GenericErrors.sol"; import { stdJson } from "forge-std/StdJson.sol"; +import { console2 } from "forge-std/console2.sol"; using stdJson for string; @@ -325,7 +326,9 @@ abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { // deploy & configure diamond diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + console2.log("diamond", address(diamond)); ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER); + console2.log("ldaDiamond", address(ldaDiamond)); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From 322bdd7d2da547780f7929c64be33483f5095c0d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 28 Jul 2025 19:54:50 +0200 Subject: [PATCH 005/220] changes --- src/Libraries/LibInputStream2.sol | 150 ++++++++++++++++++ src/Periphery/Lda/Facets/CoreRouteFacet.sol | 46 ++++-- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 20 +-- src/Periphery/Lda/LdaDiamond.sol | 64 ++++---- src/Periphery/LiFiDEXAggregator.sol | 15 ++ .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 10 +- 6 files changed, 245 insertions(+), 60 deletions(-) create mode 100644 src/Libraries/LibInputStream2.sol diff --git a/src/Libraries/LibInputStream2.sol b/src/Libraries/LibInputStream2.sol new file mode 100644 index 000000000..175e25900 --- /dev/null +++ b/src/Libraries/LibInputStream2.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: LGPL-3.0-only +/// @custom:version 1.0.2 +pragma solidity ^0.8.17; + +/// @title InputStream Library (Corrected) +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for reading data from packed byte streams. +library LibInputStream2 { + /** @notice Creates stream from data + * @param data data + */ +function createStream( + bytes memory data +) internal pure returns (uint256 stream) { + assembly { + stream := mload(0x40) + mstore(0x40, add(stream, 64)) + let dataContentPtr := add(data, 32) + mstore(stream, dataContentPtr) + let length := mload(data) + let endPtr := add(dataContentPtr, length) + mstore(add(stream, 32), endPtr) + } +} + + + /** @notice Checks if stream is not empty + * @param stream stream + */ + function isNotEmpty(uint256 stream) internal pure returns (bool) { + uint256 pos; + uint256 finish; + assembly { + pos := mload(stream) + finish := mload(add(stream, 32)) + } + return pos < finish; + } + + /** @notice Reads uint8 from the stream + * @param stream stream + */ + function readUint8(uint256 stream) internal pure returns (uint8 res) { + assembly { + let pos := mload(stream) + res := byte(0, mload(pos)) + mstore(stream, add(pos, 1)) + } + } + + /** @notice Reads uint16 from the stream + * @param stream stream + */ + function readUint16(uint256 stream) internal pure returns (uint16 res) { + assembly { + let pos := mload(stream) + res := shr(240, mload(pos)) + mstore(stream, add(pos, 2)) + } + } + + /** @notice Reads uint32 from the stream + * @param stream stream + */ + function readUint32(uint256 stream) internal pure returns (uint32 res) { + assembly { + let pos := mload(stream) + res := shr(224, mload(pos)) + mstore(stream, add(pos, 4)) + } + } + + /** @notice Reads bytes4 from the stream (for function selectors) + * @param stream stream + */ + function readBytes4(uint256 stream) internal pure returns (bytes4 res) { + assembly { + let pos := mload(stream) + res := mload(pos) + mstore(stream, add(pos, 4)) + } + } + + /** @notice Reads uint256 from the stream + * @param stream stream + */ + function readUint(uint256 stream) internal pure returns (uint256 res) { + assembly { + let pos := mload(stream) + res := mload(pos) + mstore(stream, add(pos, 32)) + } + } + + /** @notice Reads bytes32 from the stream + * @param stream stream + */ + function readBytes32(uint256 stream) internal pure returns (bytes32 res) { + assembly { + let pos := mload(stream) + res := mload(pos) + mstore(stream, add(pos, 32)) + } + } + +/** @notice Reads address from the stream + * @param stream stream + */ +function readAddress(uint256 stream) internal pure returns (address res) { + assembly { + let pos := mload(stream) + // CORRECT: Load a 32-byte word. The address is the first 20 bytes. + // To get it, we must shift the word right by (32-20)*8 = 96 bits. + res := shr(96, mload(pos)) + // Then, advance the pointer by the size of an address + mstore(stream, add(pos, 20)) + } +} + +// In LibInputStream2.sol + +/** @notice Reads all remaining bytes from the stream into a new bytes array + * @param stream stream + */ +function readRemainingBytes( + uint256 stream +) internal view returns (bytes memory res) { + uint256 pos; + uint256 finish; + assembly { + pos := mload(stream) + finish := mload(add(stream, 32)) + } + + uint256 len = finish - pos; + + if (len > 0) { + assembly { + res := mload(0x40) + mstore(0x40, add(res, add(len, 32))) + mstore(res, len) + pop(staticcall(gas(), 4, pos, len, add(res, 32), len)) + } + } + + assembly { + mstore(stream, finish) + } +} +} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 661a68669..54c6ab61f 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; @@ -15,7 +15,7 @@ import { console2 } from "forge-std/console2.sol"; contract CoreRouteFacet is ReentrancyGuard { using SafeERC20 for IERC20; using SafeERC20 for IERC20Permit; - using LibInputStream for uint256; + using LibInputStream2 for uint256; /// Constants /// address internal constant NATIVE_ADDRESS = @@ -89,9 +89,11 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 realAmountIn = amountIn; { uint256 step = 0; - uint256 stream = LibInputStream.createStream(route); + uint256 stream = LibInputStream2.createStream(route); while (stream.isNotEmpty()) { uint8 commandCode = stream.readUint8(); + console2.log("commandCode222"); + console2.log(commandCode); if (commandCode == 1) { uint256 usedAmount = _processMyERC20(stream); if (step == 0) realAmountIn = usedAmount; @@ -124,6 +126,16 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to); + console2.log("tokenOut222"); + console2.log(tokenOut); + console2.log("to222"); + console2.log(to); + console2.log("balanceOutFinal222"); + console2.log(balanceOutFinal); + console2.log("balanceOutInitial222"); + console2.log(balanceOutInitial); + console2.log("amountOutMin222"); + console2.log(amountOutMin); if (balanceOutFinal < balanceOutInitial + amountOutMin) { revert MinimalOutputBalanceViolation( balanceOutFinal - balanceOutInitial @@ -224,12 +236,7 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 amountIn ) private { // Read the function selector from the stream (4 bytes in reverse order) - bytes4 selector = bytes4( - uint32(stream.readUint8()) | - (uint32(stream.readUint8()) << 8) | - (uint32(stream.readUint8()) << 16) | - (uint32(stream.readUint8()) << 24) - ); + bytes4 selector = stream.readBytes4(); // Look up facet address using LibDiamondLoupe console2.log("selector222"); @@ -238,15 +245,30 @@ contract CoreRouteFacet is ReentrancyGuard { console2.log("facet222"); console2.logAddress(facet); if (facet == address(0)) revert UnknownSelector(); + console2.log("stream222:"); + console2.logBytes(abi.encode(stream)); + + // Add logging for VelodromeV2Facet calls + if (selector == 0x334b26d9) { // VelodromeV2Facet.swapVelodromeV2.selector + console2.log("=== VelodromeV2Facet call detected in CoreRouteFacet _dispatchSwap ==="); + console2.log("calldatasize:"); + console2.log(msg.data.length); + console2.log("calldata:"); + console2.logBytes(msg.data); + console2.log("stream:"); + console2.logBytes(abi.encode(stream)); + } - // Skip over the selector in the stream - stream.readBytes4(); // Skip the selector + // Read ALL remaining data into a single bytes variable + bytes memory remainingData = stream.readRemainingBytes(); + console2.log("remainingData222"); + console2.logBytes(remainingData); // Execute the swap via delegatecall to the facet (bool success, ) = facet.delegatecall( abi.encodeWithSelector( selector, - stream, + remainingData, from, tokenIn, amountIn diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index fde2a688b..836121f4f 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -13,7 +13,7 @@ import { console2 } from "forge-std/console2.sol"; /// @notice Handles VelodromeV2 swaps with callback management /// @custom:version 1.0.0 contract VelodromeV2Facet { - using LibInputStream for uint256; + using LibInputStream2 for uint256; using SafeERC20 for IERC20; uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; @@ -24,29 +24,21 @@ contract VelodromeV2Facet { /// @notice Performs a swap through VelodromeV2 pools /// @dev This function does not handle native token swaps directly, so processNative command cannot be used - /// @param stream [pool, direction, to, callback] + /// @param swapData [pool, direction, to, callback] /// @param from Where to take liquidity for swap /// @param tokenIn Input token /// @param amountIn Amount of tokenIn to take for swap function swapVelodromeV2( - uint256 stream, + bytes memory swapData, address from, address tokenIn, uint256 amountIn ) external returns (uint256) { - console2.log("swapVelodromeV222 here"); - console2.log("stream before reading:"); - console2.logBytes(abi.encode(stream)); // Add this to see the raw stream data + uint256 stream = LibInputStream2.createStream(swapData); address pool = stream.readAddress(); - console2.log("pool222"); - console2.logAddress(pool); uint8 direction = stream.readUint8(); - console2.log("direction222"); - console2.log(direction); address to = stream.readAddress(); - console2.log("to222"); - console2.logAddress(to); if (pool == address(0) || to == address(0)) revert InvalidCallData(); // solhint-disable-next-line max-line-length bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee @@ -108,4 +100,4 @@ contract VelodromeV2Facet { return 0; // Return value not used in current implementation } -} +} \ No newline at end of file diff --git a/src/Periphery/Lda/LdaDiamond.sol b/src/Periphery/Lda/LdaDiamond.sol index 961a0b3cb..4a4180e0f 100644 --- a/src/Periphery/Lda/LdaDiamond.sol +++ b/src/Periphery/Lda/LdaDiamond.sol @@ -5,6 +5,7 @@ import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; // solhint-disable-next-line no-unused-import import { LibUtil } from "lifi/Libraries/LibUtil.sol"; +import { console2 } from "forge-std/console2.sol"; /// @title LDA Diamond /// @author LI.FI (https://li.fi) @@ -29,42 +30,45 @@ contract LdaDiamond { // Find facet for function that is called and execute the // function if a facet is found and return any value. // solhint-disable-next-line no-complex-fallback - fallback() external payable { - LibDiamond.DiamondStorage storage ds; - bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; +fallback() external payable { + LibDiamond.DiamondStorage storage ds; + bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; - // get diamond storage - // solhint-disable-next-line no-inline-assembly - assembly { - ds.slot := position - } + // get diamond storage + assembly { + ds.slot := position + } - // get facet from function selector - address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; + // get facet from function selector + address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; - if (facet == address(0)) { - revert LibDiamond.FunctionDoesNotExist(); - } + if (facet == address(0)) { + revert LibDiamond.FunctionDoesNotExist(); + } - // Execute external function from facet using delegatecall and return any value. - // solhint-disable-next-line no-inline-assembly - assembly { - // copy function selector and any arguments - calldatacopy(0, 0, calldatasize()) - // execute function call using the facet - let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) - // get any return value - returndatacopy(0, 0, returndatasize()) - // return any return value or error back to the caller - switch result - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } + // Execute external function from facet using delegatecall and return any value. + assembly { + // Forward all calldata to the facet + calldatacopy(0, 0, calldatasize()) + + // Perform the delegatecall + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + + // Copy the returned data + returndatacopy(0, 0, returndatasize()) + + // Bubble up the result using the correct opcode + switch result + case 0 { + // Revert with the data if the call failed + revert(0, returndatasize()) + } + default { + // Return the data if the call succeeded + return(0, returndatasize()) } } +} // Able to receive ether // solhint-disable-next-line no-empty-blocks diff --git a/src/Periphery/LiFiDEXAggregator.sol b/src/Periphery/LiFiDEXAggregator.sol index 92e5e1e45..da43d5414 100644 --- a/src/Periphery/LiFiDEXAggregator.sol +++ b/src/Periphery/LiFiDEXAggregator.sol @@ -11,6 +11,7 @@ import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { ISyncSwapVault } from "lifi/Interfaces/ISyncSwapVault.sol"; import { ISyncSwapPool } from "lifi/Interfaces/ISyncSwapPool.sol"; import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { console2 } from "forge-std/console2.sol"; address constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; @@ -1070,9 +1071,23 @@ contract LiFiDEXAggregator is WithdrawablePeriphery { address tokenIn, uint256 amountIn ) private { + console2.log("swapVelodromeV2"); + console2.log("stream before reading:"); + console2.logBytes(abi.encode(stream)); // Add this to see the raw stream data + + console2.log("from"); + console2.logAddress(from); + console2.log("tokenIn"); + console2.logAddress(tokenIn); address pool = stream.readAddress(); + console2.log("pool"); + console2.logAddress(pool); uint8 direction = stream.readUint8(); + console2.log("direction"); + console2.log(direction); address to = stream.readAddress(); + console2.log("to"); + console2.logAddress(to); if (pool == address(0) || to == address(0)) revert InvalidCallData(); // solhint-disable-next-line max-line-length bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index eb3076a2f..3605a4cef 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -539,7 +539,7 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { ADDRESS_USDC, uint8(1), FULL_SHARE, - uint8(PoolType.VelodromeV2), + VelodromeV2Facet.swapVelodromeV2.selector, address(0), uint8(SwapDirection.Token1ToToken0), USER_SENDER, @@ -564,7 +564,7 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { ADDRESS_USDC, uint8(1), FULL_SHARE, - uint8(PoolType.VelodromeV2), + VelodromeV2Facet.swapVelodromeV2.selector, validPool, uint8(SwapDirection.Token1ToToken0), address(0), @@ -726,6 +726,8 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { // amounts[1] // ); + console2.log("route222:"); + console2.logBytes(route); // execute the swap coreRouteFacet.processRoute( params.tokenIn, @@ -832,11 +834,11 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { ) private pure returns (bytes memory) { return abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, uint8(CommandType.ProcessUserERC20), tokenIn, uint8(1), FULL_SHARE, + VelodromeV2Facet.swapVelodromeV2.selector, pool1, direction, pool2, @@ -853,9 +855,9 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { ) private pure returns (bytes memory) { return abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, uint8(CommandType.ProcessOnePool), tokenMid, + VelodromeV2Facet.swapVelodromeV2.selector, pool2, direction, recipient, From 1516d88a30fc9a03beeec411f8f2c7830cd8b298 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 29 Jul 2025 13:37:51 +0200 Subject: [PATCH 006/220] adjusted new CoreRouteFacet, modified LibInputStream2, fixed LiFiDexAggregatorVelodromeV2UpgradeTest tests --- src/Libraries/LibInputStream2.sol | 94 ++++---- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 79 ++----- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 4 +- .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 206 +++++++++--------- 4 files changed, 175 insertions(+), 208 deletions(-) diff --git a/src/Libraries/LibInputStream2.sol b/src/Libraries/LibInputStream2.sol index 175e25900..94b56fd04 100644 --- a/src/Libraries/LibInputStream2.sol +++ b/src/Libraries/LibInputStream2.sol @@ -9,20 +9,19 @@ library LibInputStream2 { /** @notice Creates stream from data * @param data data */ -function createStream( - bytes memory data -) internal pure returns (uint256 stream) { - assembly { - stream := mload(0x40) - mstore(0x40, add(stream, 64)) - let dataContentPtr := add(data, 32) - mstore(stream, dataContentPtr) - let length := mload(data) - let endPtr := add(dataContentPtr, length) - mstore(add(stream, 32), endPtr) + function createStream( + bytes memory data + ) internal pure returns (uint256 stream) { + assembly { + stream := mload(0x40) + mstore(0x40, add(stream, 64)) + let dataContentPtr := add(data, 32) + mstore(stream, dataContentPtr) + let length := mload(data) + let endPtr := add(dataContentPtr, length) + mstore(add(stream, 32), endPtr) + } } -} - /** @notice Checks if stream is not empty * @param stream stream @@ -103,48 +102,39 @@ function createStream( } } -/** @notice Reads address from the stream - * @param stream stream - */ -function readAddress(uint256 stream) internal pure returns (address res) { - assembly { - let pos := mload(stream) - // CORRECT: Load a 32-byte word. The address is the first 20 bytes. - // To get it, we must shift the word right by (32-20)*8 = 96 bits. - res := shr(96, mload(pos)) - // Then, advance the pointer by the size of an address - mstore(stream, add(pos, 20)) - } -} - -// In LibInputStream2.sol - -/** @notice Reads all remaining bytes from the stream into a new bytes array - * @param stream stream - */ -function readRemainingBytes( - uint256 stream -) internal view returns (bytes memory res) { - uint256 pos; - uint256 finish; - assembly { - pos := mload(stream) - finish := mload(add(stream, 32)) + /** @notice Reads address from the stream + * @param stream stream + */ + function readAddress(uint256 stream) internal pure returns (address res) { + assembly { + let pos := mload(stream) + // CORRECT: Load a 32-byte word. The address is the first 20 bytes. + // To get it, we must shift the word right by (32-20)*8 = 96 bits. + res := shr(96, mload(pos)) + // Then, advance the pointer by the size of an address + mstore(stream, add(pos, 20)) + } } - uint256 len = finish - pos; + /** @notice Reads a uint16 length prefix, then that many bytes. */ + function readBytesWithLength( + uint256 stream + ) internal view returns (bytes memory res) { + uint16 len = LibInputStream2.readUint16(stream); // Read the 2-byte length - if (len > 0) { - assembly { - res := mload(0x40) - mstore(0x40, add(res, add(len, 32))) - mstore(res, len) - pop(staticcall(gas(), 4, pos, len, add(res, 32), len)) + if (len > 0) { + uint256 pos; + assembly { + pos := mload(stream) + } + assembly { + res := mload(0x40) + mstore(0x40, add(res, add(len, 32))) + mstore(res, len) + pop(staticcall(gas(), 4, pos, len, add(res, 32), len)) + // IMPORTANT: Update the stream's position pointer + mstore(stream, add(pos, len)) + } } } - - assembly { - mstore(stream, finish) - } } -} \ No newline at end of file diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 54c6ab61f..9b7182cc9 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; +import { LibUtil } from "lifi/Libraries/LibUtil.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; -import { console2 } from "forge-std/console2.sol"; /// @title Core Route Facet /// @author LI.FI (https://li.fi) @@ -92,8 +92,6 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 stream = LibInputStream2.createStream(route); while (stream.isNotEmpty()) { uint8 commandCode = stream.readUint8(); - console2.log("commandCode222"); - console2.log(commandCode); if (commandCode == 1) { uint256 usedAmount = _processMyERC20(stream); if (step == 0) realAmountIn = usedAmount; @@ -126,16 +124,6 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to); - console2.log("tokenOut222"); - console2.log(tokenOut); - console2.log("to222"); - console2.log(to); - console2.log("balanceOutFinal222"); - console2.log(balanceOutFinal); - console2.log("balanceOutInitial222"); - console2.log(balanceOutInitial); - console2.log("amountOutMin222"); - console2.log(amountOutMin); if (balanceOutFinal < balanceOutInitial + amountOutMin) { revert MinimalOutputBalanceViolation( balanceOutFinal - balanceOutInitial @@ -213,13 +201,9 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 amountTotal ) private { uint8 num = stream.readUint8(); - console2.log("num222"); - console2.log(num); unchecked { for (uint256 i = 0; i < num; ++i) { uint16 share = stream.readUint16(); - console2.log("share222"); - console2.log(share); uint256 amount = (amountTotal * share) / type(uint16).max; amountTotal -= amount; _dispatchSwap(stream, from, tokenIn, amount); @@ -235,49 +219,34 @@ contract CoreRouteFacet is ReentrancyGuard { address tokenIn, uint256 amountIn ) private { - // Read the function selector from the stream (4 bytes in reverse order) - bytes4 selector = stream.readBytes4(); + bytes memory swapDataForThisHop = stream.readBytesWithLength(); + + bytes4 selector; + // We need to slice the data to separate the selector from the rest of the payload + bytes memory payload; + + assembly { + // Read the selector from the first 4 bytes of the data + selector := mload(add(swapDataForThisHop, 32)) + + // Create a new memory slice for the payload that starts 4 bytes after the + // beginning of swapDataForThisHop's content. + // It points to the same underlying data, just with a different start and length. + payload := add(swapDataForThisHop, 4) + // Adjust the length of the new slice + mstore(payload, sub(mload(swapDataForThisHop), 4)) + } - // Look up facet address using LibDiamondLoupe - console2.log("selector222"); - console2.logBytes4(selector); address facet = LibDiamondLoupe.facetAddress(selector); - console2.log("facet222"); - console2.logAddress(facet); if (facet == address(0)) revert UnknownSelector(); - console2.log("stream222:"); - console2.logBytes(abi.encode(stream)); - - // Add logging for VelodromeV2Facet calls - if (selector == 0x334b26d9) { // VelodromeV2Facet.swapVelodromeV2.selector - console2.log("=== VelodromeV2Facet call detected in CoreRouteFacet _dispatchSwap ==="); - console2.log("calldatasize:"); - console2.log(msg.data.length); - console2.log("calldata:"); - console2.logBytes(msg.data); - console2.log("stream:"); - console2.logBytes(abi.encode(stream)); - } - // Read ALL remaining data into a single bytes variable - bytes memory remainingData = stream.readRemainingBytes(); - console2.log("remainingData222"); - console2.logBytes(remainingData); - - // Execute the swap via delegatecall to the facet - (bool success, ) = facet.delegatecall( - abi.encodeWithSelector( - selector, - remainingData, - from, - tokenIn, - amountIn - ) + (bool success, bytes memory returnData) = facet.delegatecall( + abi.encodeWithSelector(selector, payload, from, tokenIn, amountIn) ); + if (!success) { - revert SwapFailed(); + // If the call failed, revert with the EXACT error from the facet. + LibUtil.revertWith(returnData); } - - // Note: Individual facets can return amounts if needed, but for now we rely on balance checks } } diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 836121f4f..516967da4 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -6,7 +6,6 @@ import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { console2 } from "forge-std/console2.sol"; /// @title VelodromeV2 Facet /// @author LI.FI (https://li.fi) @@ -35,7 +34,6 @@ contract VelodromeV2Facet { uint256 amountIn ) external returns (uint256) { uint256 stream = LibInputStream2.createStream(swapData); - address pool = stream.readAddress(); uint8 direction = stream.readUint8(); address to = stream.readAddress(); @@ -100,4 +98,4 @@ contract VelodromeV2Facet { return 0; // Return value not used in current implementation } -} \ No newline at end of file +} diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index 3605a4cef..00bacc021 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -7,24 +7,18 @@ import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; -import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; -import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; -import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; -import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +// import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +// import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; +// import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; +// import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; +// import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; +// import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { TestBase } from "../../utils/TestBase.sol"; -import { TestToken as ERC20 } from "../../utils/TestToken.sol"; -import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; -import { console2 } from "forge-std/console2.sol"; - // Command codes for route processing enum CommandType { None, // 0 - not used @@ -111,7 +105,7 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { error WrongPoolReserves(); error PoolDoesNotExist(); - function _addDexFacet() virtual internal; + function _addDexFacet() internal virtual; // Setup function for Apechain tests function setupApechain() internal { @@ -143,7 +137,11 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { coreRouteFacet = new CoreRouteFacet(); bytes4[] memory functionSelectors = new bytes4[](1); functionSelectors[0] = CoreRouteFacet.processRoute.selector; - addFacet(address(ldaDiamond), address(coreRouteFacet), functionSelectors); + addFacet( + address(ldaDiamond), + address(coreRouteFacet), + functionSelectors + ); coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); } @@ -203,8 +201,9 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { * @title VelodromeV2 tests * @notice Tests specific to Velodrome V2 pool type */ -contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { - +contract LiFiDexAggregatorVelodromeV2UpgradeTest is + LiFiDexAggregatorUpgradeTest +{ VelodromeV2Facet internal velodromeV2Facet; // ==================== Velodrome V2 specific variables ==================== @@ -266,7 +265,11 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { velodromeV2Facet = new VelodromeV2Facet(); bytes4[] memory functionSelectors = new bytes4[](1); functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; - addFacet(address(ldaDiamond), address(velodromeV2Facet), functionSelectors); + addFacet( + address(ldaDiamond), + address(velodromeV2Facet), + functionSelectors + ); velodromeV2Facet = VelodromeV2Facet(payable(address(ldaDiamond))); } @@ -361,9 +364,9 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { from: address(ldaDiamond), to: address(USER_SENDER), tokenIn: ADDRESS_USDC, - amountIn: IERC20(ADDRESS_USDC).balanceOf( - address(ldaDiamond) - ) - 1, // adjust for slot undrain protection: subtract 1 token so that the aggregator's balance isn't completely drained, matching the contract's safeguard + amountIn: IERC20(ADDRESS_USDC).balanceOf(address(ldaDiamond)) - + 1, // adjust for slot undrain protection: subtract 1 token so that the + // aggregator's balance isn't completely drained, matching the contract's safeguard tokenOut: address(USDC_E_TOKEN), stable: false, direction: SwapDirection.Token0ToToken1, @@ -496,16 +499,16 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { // Approve and execute IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - params.tokenIn, - params.tokenOut, - 1000 * 1e6, - params.amounts2[1], - params.amounts2[1] - ); + // vm.expectEmit(true, true, true, true); + // emit Route( + // USER_SENDER, + // USER_SENDER, + // params.tokenIn, + // params.tokenOut, + // 1000 * 1e6, + // params.amounts2[1], + // params.amounts2[1] + // ); coreRouteFacet.processRoute( params.tokenIn, @@ -533,17 +536,24 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { VELODROME_V2_FACTORY_REGISTRY ); - // Test case 1: Zero pool address + // --- Test case 1: Zero pool address --- + // 1. Create the specific swap data blob + bytes memory swapDataZeroPool = abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + address(0), // Invalid pool + uint8(SwapDirection.Token1ToToken0), + USER_SENDER, + uint8(CallbackStatus.Disabled) + ); + + // 2. Create the full route with the length-prefixed swap data bytes memory routeWithZeroPool = abi.encodePacked( uint8(CommandType.ProcessUserERC20), ADDRESS_USDC, uint8(1), FULL_SHARE, - VelodromeV2Facet.swapVelodromeV2.selector, - address(0), - uint8(SwapDirection.Token1ToToken0), - USER_SENDER, - uint8(CallbackStatus.Disabled) + uint16(swapDataZeroPool.length), // Length prefix + swapDataZeroPool ); IERC20(ADDRESS_USDC).approve(address(ldaDiamond), 1000 * 1e6); @@ -558,17 +568,22 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { routeWithZeroPool ); - // Test case 2: Zero recipient address + // --- Test case 2: Zero recipient address --- + bytes memory swapDataZeroRecipient = abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + validPool, + uint8(SwapDirection.Token1ToToken0), + address(0), // Invalid recipient + uint8(CallbackStatus.Disabled) + ); + bytes memory routeWithZeroRecipient = abi.encodePacked( uint8(CommandType.ProcessUserERC20), ADDRESS_USDC, uint8(1), FULL_SHARE, - VelodromeV2Facet.swapVelodromeV2.selector, - validPool, - uint8(SwapDirection.Token1ToToken0), - address(0), - uint8(CallbackStatus.Disabled) + uint16(swapDataZeroRecipient.length), // Length prefix + swapDataZeroRecipient ); vm.expectRevert(InvalidCallData.selector); @@ -597,21 +612,7 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { ); // Build multi-hop route - bytes memory firstHop = _buildFirstHop( - params.tokenIn, - params.pool1, - params.pool2, - 1 // direction - ); - - bytes memory secondHop = _buildSecondHop( - params.tokenMid, - params.pool2, - USER_SENDER, - 0 // direction - ); - - bytes memory route = bytes.concat(firstHop, secondHop); + bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); @@ -675,14 +676,8 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { ? CommandType.ProcessMyERC20 : CommandType.ProcessUserERC20; - console2.log("VelodromeV2Facet.swapVelodromeV2.selector"); - console2.logBytes4(VelodromeV2Facet.swapVelodromeV2.selector); - // build the route. - bytes memory route = abi.encodePacked( - uint8(commandCode), - params.tokenIn, - uint8(1), - FULL_SHARE, + // 1. Pack the data for the specific swap FIRST + bytes memory swapData = abi.encodePacked( VelodromeV2Facet.swapVelodromeV2.selector, pool, params.direction, @@ -691,12 +686,18 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { ? uint8(CallbackStatus.Enabled) : uint8(CallbackStatus.Disabled) ); + // build the route. + bytes memory route = abi.encodePacked( + uint8(commandCode), + params.tokenIn, + uint8(1), // num splits + FULL_SHARE, + uint16(swapData.length), // <--- Add length prefix + swapData + ); // approve the aggregator to spend tokenIn. - IERC20(params.tokenIn).approve( - address(ldaDiamond), - params.amountIn - ); + IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); // capture initial token balances. uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); @@ -715,19 +716,17 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { abi.encode(params.tokenIn) ); } - // vm.expectEmit(true, true, true, true); - // emit Route( - // from, - // params.to, - // params.tokenIn, - // params.tokenOut, - // params.amountIn, - // amounts[1], - // amounts[1] - // ); + vm.expectEmit(true, true, true, true); + emit Route( + from, + params.to, + params.tokenIn, + params.tokenOut, + params.amountIn, + amounts[1], + amounts[1] + ); - console2.log("route222:"); - console2.logBytes(route); // execute the swap coreRouteFacet.processRoute( params.tokenIn, @@ -827,40 +826,32 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { // function to build first hop of the route function _buildFirstHop( - address tokenIn, address pool1, - address pool2, + address pool2, // The recipient of the first hop is the next pool uint8 direction ) private pure returns (bytes memory) { return abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - tokenIn, - uint8(1), - FULL_SHARE, VelodromeV2Facet.swapVelodromeV2.selector, pool1, direction, - pool2, + pool2, // Send intermediate tokens to the next pool for the second hop uint8(CallbackStatus.Disabled) ); } // function to build second hop of the route function _buildSecondHop( - address tokenMid, address pool2, address recipient, uint8 direction ) private pure returns (bytes memory) { return abi.encodePacked( - uint8(CommandType.ProcessOnePool), - tokenMid, VelodromeV2Facet.swapVelodromeV2.selector, pool2, direction, - recipient, + recipient, // Final recipient uint8(CallbackStatus.Disabled) ); } @@ -872,21 +863,40 @@ contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorUpgradeTest { uint8 firstHopDirection, uint8 secondHopDirection ) private pure returns (bytes memory) { - bytes memory firstHop = _buildFirstHop( - params.tokenIn, + // 1. Get the specific data for each hop + bytes memory firstHopData = _buildFirstHop( params.pool1, params.pool2, firstHopDirection ); - bytes memory secondHop = _buildSecondHop( - params.tokenMid, + bytes memory secondHopData = _buildSecondHop( params.pool2, recipient, secondHopDirection ); - return bytes.concat(firstHop, secondHop); + // 2. Assemble the first command + bytes memory firstCommand = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + params.tokenIn, + uint8(1), // num splits + FULL_SHARE, + uint16(firstHopData.length), // <--- Add length prefix + firstHopData + ); + + // 3. Assemble the second command + // The second hop takes tokens already held by the diamond, so we use ProcessOnePool + bytes memory secondCommand = abi.encodePacked( + uint8(CommandType.ProcessOnePool), + params.tokenMid, + uint16(secondHopData.length), // <--- Add length prefix + secondHopData + ); + + // 4. Concatenate the commands to create the final route + return bytes.concat(firstCommand, secondCommand); } function _verifyUserBalances( From c5645ec70e2db89a97914669e062a4fb7a6a0cf8 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 29 Jul 2025 15:26:37 +0200 Subject: [PATCH 007/220] added comments and changed var name --- src/Libraries/LibInputStream2.sol | 16 ++++++++++++++-- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 12 +++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Libraries/LibInputStream2.sol b/src/Libraries/LibInputStream2.sol index 94b56fd04..5769101ed 100644 --- a/src/Libraries/LibInputStream2.sol +++ b/src/Libraries/LibInputStream2.sol @@ -116,11 +116,23 @@ library LibInputStream2 { } } - /** @notice Reads a uint16 length prefix, then that many bytes. */ + /** + * @notice Reads a length-prefixed data blob from the stream. + * @dev This is the key function for enabling multi-hop swaps. It allows the + * router to read the data for a single hop without consuming the rest + * of the stream, which may contain data for subsequent hops. + * It expects the stream to be encoded like this: + * [uint16 length_of_data][bytes memory data] + * For a multi-hop route, the stream would look like: + * [uint16 hop1_len][bytes hop1_data][uint16 hop2_len][bytes hop2_data]... + * @param stream The data stream to read from. + * @return res The data blob for the current hop. + */ function readBytesWithLength( uint256 stream ) internal view returns (bytes memory res) { - uint16 len = LibInputStream2.readUint16(stream); // Read the 2-byte length + // Read the 2-byte length prefix to know how many bytes to read next. + uint16 len = LibInputStream2.readUint16(stream); if (len > 0) { uint256 pos; diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 9b7182cc9..e41f8e9a2 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -219,7 +219,9 @@ contract CoreRouteFacet is ReentrancyGuard { address tokenIn, uint256 amountIn ) private { - bytes memory swapDataForThisHop = stream.readBytesWithLength(); + // Read the length-prefixed data blob for this specific swap step. + // This data blob contains the target function selector and its arguments. + bytes memory swapDataForCurrentHop = stream.readBytesWithLength(); bytes4 selector; // We need to slice the data to separate the selector from the rest of the payload @@ -227,14 +229,14 @@ contract CoreRouteFacet is ReentrancyGuard { assembly { // Read the selector from the first 4 bytes of the data - selector := mload(add(swapDataForThisHop, 32)) + selector := mload(add(swapDataForCurrentHop, 32)) // Create a new memory slice for the payload that starts 4 bytes after the - // beginning of swapDataForThisHop's content. + // beginning of swapDataForCurrentHop's content. // It points to the same underlying data, just with a different start and length. - payload := add(swapDataForThisHop, 4) + payload := add(swapDataForCurrentHop, 4) // Adjust the length of the new slice - mstore(payload, sub(mload(swapDataForThisHop), 4)) + mstore(payload, sub(mload(swapDataForCurrentHop), 4)) } address facet = LibDiamondLoupe.facetAddress(selector); From 89d4f3903512092e59dc599825397a3776875c1b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 29 Jul 2025 19:13:23 +0200 Subject: [PATCH 008/220] adjusted tests for Algebra --- src/Periphery/Lda/Facets/AlgebraFacet.sol | 7 +- test/solidity/Facets/GenericSwapFacetV3.t.sol | 2 +- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 337 +++--- .../Lda/Facets/UniV3StyleFacet.t.sol | 40 +- .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 1029 ++++++++++++++++- .../Periphery/Lda/utils/LdaDiamondTest.sol | 10 +- test/solidity/utils/TestBase.sol | 113 +- .../solidity/utils/TestBaseForksConstants.sol | 86 ++ .../utils/TestBaseRandomConstants.sol | 11 + test/solidity/utils/TestHelpers.sol | 14 + 10 files changed, 1310 insertions(+), 339 deletions(-) create mode 100644 test/solidity/utils/TestBaseForksConstants.sol create mode 100644 test/solidity/utils/TestBaseRandomConstants.sol diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 4d56069e0..dbf474e80 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibInputStream } from "../../../Libraries/LibInputStream.sol"; +import { LibInputStream2 } from "../../../Libraries/LibInputStream2.sol"; import { LibCallbackManager } from "../../../Libraries/LibCallbackManager.sol"; import { LibUniV3Logic } from "../../../Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "../../../Interfaces/IAlgebraPool.sol"; @@ -14,7 +14,7 @@ import { InvalidCallData } from "../../../Errors/GenericErrors.sol"; /// @notice Handles Algebra swaps with callback management /// @custom:version 1.0.0 contract AlgebraFacet { - using LibInputStream for uint256; + using LibInputStream2 for uint256; using SafeERC20 for IERC20; /// Constants /// @@ -26,11 +26,12 @@ contract AlgebraFacet { error AlgebraSwapUnexpected(); function swapAlgebra( - uint256 stream, + bytes memory swapData, address from, address tokenIn, uint256 amountIn ) external returns (uint256) { + uint256 stream = LibInputStream2.createStream(swapData); address pool = stream.readAddress(); bool direction = stream.readUint8() > 0; address recipient = stream.readAddress(); diff --git a/test/solidity/Facets/GenericSwapFacetV3.t.sol b/test/solidity/Facets/GenericSwapFacetV3.t.sol index 69ab33746..2fa066682 100644 --- a/test/solidity/Facets/GenericSwapFacetV3.t.sol +++ b/test/solidity/Facets/GenericSwapFacetV3.t.sol @@ -39,7 +39,7 @@ contract TestGenericSwapFacet is GenericSwapFacet { } } -contract GenericSwapFacetV3Test is TestBase, TestHelpers { +contract GenericSwapFacetV3Test is TestBase { using SafeTransferLib for ERC20; // These values are for Mainnet diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index e6818dcb2..7f8985d29 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -1,174 +1,175 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; -import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; -import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; -import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; -import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; -import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; -import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { TestBase } from "../../utils/TestBase.sol"; -import { TestToken as ERC20 } from "../../utils/TestToken.sol"; -import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; - -// Command codes for route processing -enum CommandType { - None, // 0 - not used - ProcessMyERC20, // 1 - processMyERC20 - ProcessUserERC20, // 2 - processUserERC20 - ProcessNative, // 3 - processNative - ProcessOnePool, // 4 - processOnePool - ProcessInsideBento, // 5 - processInsideBento - ApplyPermit // 6 - applyPermit -} - -// Pool type identifiers -enum PoolType { - UniV2, // 0 - UniV3, // 1 - WrapNative, // 2 - BentoBridge, // 3 - Trident, // 4 - Curve, // 5 - VelodromeV2, // 6 - Algebra, // 7 - iZiSwap, // 8 - SyncSwapV2 // 9 -} - -// Direction constants -enum SwapDirection { - Token1ToToken0, // 0 - Token0ToToken1 // 1 -} - -// Callback constants -enum CallbackStatus { - Disabled, // 0 - Enabled // 1 -} - -// Other constants -uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps - -contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - function hook( - address sender, - uint256 amount0, - uint256 amount1, - bytes calldata data - ) external { - emit HookCalled(sender, amount0, amount1, data); - } -} -/** - * @title BaseDexFacetTest - * @notice Base test contract with common functionality and abstractions for DEX-specific tests - */ -abstract contract BaseDexFacetTest is TestBase { - using SafeERC20 for IERC20; - - // Common variables - LiFiDEXAggregator internal liFiDEXAggregator; - address[] internal privileged; - - // Common events and errors - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - error WrongPoolReserves(); - error PoolDoesNotExist(); - - // helper function to initialize the aggregator - function _initializeDexAggregator(address owner) internal { - privileged = new address[](1); - privileged[0] = owner; - - liFiDEXAggregator = new LiFiDEXAggregator( - address(0xCAFE), - privileged, - owner - ); - vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); - } - - // Setup function for Apechain tests - function setupApechain() internal { - customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; - customBlockNumberForForking = 12912470; - fork(); - - _initializeDexAggregator(address(USER_DIAMOND_OWNER)); - } - - function setupHyperEVM() internal { - customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; - customBlockNumberForForking = 4433562; - fork(); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - - // ============================ Abstract DEX Tests ============================ - /** - * @notice Abstract test for basic token swapping functionality - * Each DEX implementation should override this - */ - function test_CanSwap() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap: Not implemented"); - } - - /** - * @notice Abstract test for swapping tokens from the DEX aggregator - * Each DEX implementation should override this - */ - function test_CanSwap_FromDexAggregator() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_FromDexAggregator: Not implemented"); - } - - /** - * @notice Abstract test for multi-hop swapping - * Each DEX implementation should override this - */ - function test_CanSwap_MultiHop() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_MultiHop: Not implemented"); - } -} +// import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; +// import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; +// import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; +// import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; +// import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +// import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; +// import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; +// import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; +// import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; +// import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; +// import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; +// import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +// import { TestBase } from "../../utils/TestBase.sol"; +// import { TestToken as ERC20 } from "../../utils/TestToken.sol"; +// import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; +// import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; + +// // Command codes for route processing +// enum CommandType { +// None, // 0 - not used +// ProcessMyERC20, // 1 - processMyERC20 +// ProcessUserERC20, // 2 - processUserERC20 +// ProcessNative, // 3 - processNative +// ProcessOnePool, // 4 - processOnePool +// ProcessInsideBento, // 5 - processInsideBento +// ApplyPermit // 6 - applyPermit +// } + +// // Pool type identifiers +// enum PoolType { +// UniV2, // 0 +// UniV3, // 1 +// WrapNative, // 2 +// BentoBridge, // 3 +// Trident, // 4 +// Curve, // 5 +// VelodromeV2, // 6 +// Algebra, // 7 +// iZiSwap, // 8 +// SyncSwapV2 // 9 +// } + +// // Direction constants +// enum SwapDirection { +// Token1ToToken0, // 0 +// Token0ToToken1 // 1 +// } + +// // Callback constants +// enum CallbackStatus { +// Disabled, // 0 +// Enabled // 1 +// } + +// // Other constants +// uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps + +// contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { +// event HookCalled( +// address sender, +// uint256 amount0, +// uint256 amount1, +// bytes data +// ); + +// function hook( +// address sender, +// uint256 amount0, +// uint256 amount1, +// bytes calldata data +// ) external { +// emit HookCalled(sender, amount0, amount1, data); +// } +// } +// /** +// * @title BaseDexFacetTest +// * @notice Base test contract with common functionality and abstractions for DEX-specific tests +// */ +// abstract contract BaseDexFacetTest is LdaDiamondTest { +// using SafeERC20 for IERC20; + +// // Common variables +// LiFiDEXAggregator internal liFiDEXAggregator; +// address[] internal privileged; + +// // Common events and errors +// event Route( +// address indexed from, +// address to, +// address indexed tokenIn, +// address indexed tokenOut, +// uint256 amountIn, +// uint256 amountOutMin, +// uint256 amountOut +// ); +// event HookCalled( +// address sender, +// uint256 amount0, +// uint256 amount1, +// bytes data +// ); + +// error WrongPoolReserves(); +// error PoolDoesNotExist(); + +// // helper function to initialize the aggregator +// function _initializeDexAggregator(address owner) internal { +// privileged = new address[](1); +// privileged[0] = owner; + +// liFiDEXAggregator = new LiFiDEXAggregator( +// address(0xCAFE), +// privileged, +// owner +// ); +// vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); +// } + +// // Setup function for Apechain tests +// function setupApechain() internal { +// customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; +// customBlockNumberForForking = 12912470; +// fork(); + +// _initializeDexAggregator(address(USER_DIAMOND_OWNER)); +// } + +// function setupHyperEVM() internal { +// customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; +// customBlockNumberForForking = 4433562; +// fork(); + +// _initializeDexAggregator(USER_DIAMOND_OWNER); +// } + + +// // ============================ Abstract DEX Tests ============================ +// /** +// * @notice Abstract test for basic token swapping functionality +// * Each DEX implementation should override this +// */ +// function test_CanSwap() public virtual { +// // Each DEX implementation must override this +// // solhint-disable-next-line gas-custom-errors +// revert("test_CanSwap: Not implemented"); +// } + +// /** +// * @notice Abstract test for swapping tokens from the DEX aggregator +// * Each DEX implementation should override this +// */ +// function test_CanSwap_FromDexAggregator() public virtual { +// // Each DEX implementation must override this +// // solhint-disable-next-line gas-custom-errors +// revert("test_CanSwap_FromDexAggregator: Not implemented"); +// } + +// /** +// * @notice Abstract test for multi-hop swapping +// * Each DEX implementation should override this +// */ +// function test_CanSwap_MultiHop() public virtual { +// // Each DEX implementation must override this +// // solhint-disable-next-line gas-custom-errors +// revert("test_CanSwap_MultiHop: Not implemented"); +// } +// } // /** // * @title VelodromeV2 tests diff --git a/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol b/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol index 45e984e22..be1176120 100644 --- a/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol @@ -1,28 +1,28 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +// import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +// import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -contract UniV3StyleFacetTest is BaseDexFacetTest { - UniV3StyleFacet internal uniV3StyleFacet; +// contract UniV3StyleFacetTest is BaseDexFacetTest { +// UniV3StyleFacet internal uniV3StyleFacet; - function setUp() public { - customBlockNumberForForking = 18277082; - initTestBase(); +// function setUp() public { +// customBlockNumberForForking = 18277082; +// initTestBase(); - uniV3StyleFacet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3StyleFacet - .swapUniV3 - .selector; - functionSelectors[1] = uniV3StyleFacet - .uniswapV3SwapCallback - .selector; +// uniV3StyleFacet = new UniV3StyleFacet(); +// bytes4[] memory functionSelectors = new bytes4[](2); +// functionSelectors[0] = uniV3StyleFacet +// .swapUniV3 +// .selector; +// functionSelectors[1] = uniV3StyleFacet +// .uniswapV3SwapCallback +// .selector; - addFacet(address(ldaDiamond), address(uniV3StyleFacet), functionSelectors); - uniV3StyleFacet = UniV3StyleFacet(address(ldaDiamond)); +// addFacet(address(ldaDiamond), address(uniV3StyleFacet), functionSelectors); +// uniV3StyleFacet = UniV3StyleFacet(address(ldaDiamond)); - setFacetAddressInTestBase(address(uniV3StyleFacet), "UniV3StyleFacet"); - } -} +// setFacetAddressInTestBase(address(uniV3StyleFacet), "UniV3StyleFacet"); +// } +// } diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index 00bacc021..4929bc1b8 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -7,18 +7,26 @@ import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -// import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -// import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; -// import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; -// import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; +import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; +import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; +import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; // import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; // import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { TestBase } from "../../utils/TestBase.sol"; import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { TestToken as ERC20 } from "../../utils/TestToken.sol"; +import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; +import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; +import { TestBase } from "../../utils/TestBase.sol"; +import { TestBaseRandomConstants } from "../../utils/TestBaseRandomConstants.sol"; +import { TestHelpers } from "../../utils/TestHelpers.sol"; + + // Command codes for route processing enum CommandType { None, // 0 - not used @@ -80,7 +88,7 @@ contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { * @title LiFiDexAggregatorUpgradeTest * @notice Base test contract with common functionality and abstractions for DEX-specific tests */ -abstract contract LiFiDexAggregatorUpgradeTest is TestBase { +abstract contract LiFiDexAggregatorUpgradeTest is LdaDiamondTest, TestHelpers { using SafeERC20 for IERC20; CoreRouteFacet internal coreRouteFacet; @@ -111,26 +119,18 @@ abstract contract LiFiDexAggregatorUpgradeTest is TestBase { function setupApechain() internal { customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; customBlockNumberForForking = 12912470; - fork(); - - // _initializeDexAggregator(address(USER_DIAMOND_OWNER)); } function setupHyperEVM() internal { customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; customBlockNumberForForking = 4433562; - fork(); - - // _initializeDexAggregator(USER_DIAMOND_OWNER); } - function setUp() public virtual { - initTestBase(); - vm.label(USER_SENDER, "USER_SENDER"); - + function setUp() public virtual override { + fork(); + LdaDiamondTest.setUp(); _addCoreRouteFacet(); _addDexFacet(); - // _initializeDexAggregator(USER_DIAMOND_OWNER); } function _addCoreRouteFacet() internal { @@ -211,6 +211,8 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router address internal constant VELODROME_V2_FACTORY_REGISTRY = 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; + IERC20 internal constant USDC_TOKEN = + IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); IERC20 internal constant STG_TOKEN = IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); IERC20 internal constant USDC_E_TOKEN = @@ -284,7 +286,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is VelodromeV2SwapTestParams({ from: address(USER_SENDER), to: address(USER_SENDER), - tokenIn: ADDRESS_USDC, + tokenIn: address(USDC_TOKEN), amountIn: 1_000 * 1e6, tokenOut: address(STG_TOKEN), stable: false, @@ -307,7 +309,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is to: USER_SENDER, tokenIn: address(STG_TOKEN), amountIn: 500 * 1e18, - tokenOut: ADDRESS_USDC, + tokenOut: address(USDC_TOKEN), stable: false, direction: SwapDirection.Token1ToToken0, callback: false @@ -322,7 +324,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is VelodromeV2SwapTestParams({ from: USER_SENDER, to: USER_SENDER, - tokenIn: ADDRESS_USDC, + tokenIn: address(USDC_TOKEN), amountIn: 1_000 * 1e6, tokenOut: address(USDC_E_TOKEN), stable: true, @@ -345,7 +347,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is to: USER_SENDER, tokenIn: address(USDC_E_TOKEN), amountIn: 500 * 1e6, - tokenOut: ADDRESS_USDC, + tokenOut: address(USDC_TOKEN), stable: false, direction: SwapDirection.Token1ToToken0, callback: false @@ -356,15 +358,15 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is function test_CanSwap_FromDexAggregator() public override { // fund dex aggregator contract so that the contract holds USDC - deal(ADDRESS_USDC, address(ldaDiamond), 100_000 * 1e6); + deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); vm.startPrank(USER_SENDER); _testSwap( VelodromeV2SwapTestParams({ from: address(ldaDiamond), to: address(USER_SENDER), - tokenIn: ADDRESS_USDC, - amountIn: IERC20(ADDRESS_USDC).balanceOf(address(ldaDiamond)) - + tokenIn: address(USDC_TOKEN), + amountIn: IERC20(address(USDC_TOKEN)).balanceOf(address(ldaDiamond)) - 1, // adjust for slot undrain protection: subtract 1 token so that the // aggregator's balance isn't completely drained, matching the contract's safeguard tokenOut: address(USDC_E_TOKEN), @@ -384,7 +386,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is VelodromeV2SwapTestParams({ from: address(USER_SENDER), to: address(mockFlashloanCallbackReceiver), - tokenIn: ADDRESS_USDC, + tokenIn: address(USDC_TOKEN), amountIn: 1_000 * 1e6, tokenOut: address(USDC_E_TOKEN), stable: false, @@ -401,7 +403,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is // Setup routes and get amounts MultiHopTestParams memory params = _setupRoutes( - ADDRESS_USDC, + address(USDC_TOKEN), address(STG_TOKEN), address(USDC_E_TOKEN), false, @@ -465,7 +467,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is // Setup routes and get amounts for stable->volatile path MultiHopTestParams memory params = _setupRoutes( - ADDRESS_USDC, + address(USDC_TOKEN), address(USDC_E_TOKEN), address(STG_TOKEN), true, // stable pool for first hop @@ -530,7 +532,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is // Get a valid pool address first for comparison address validPool = VELODROME_V2_ROUTER.poolFor( - ADDRESS_USDC, + address(USDC_TOKEN), address(STG_TOKEN), false, VELODROME_V2_FACTORY_REGISTRY @@ -549,18 +551,18 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is // 2. Create the full route with the length-prefixed swap data bytes memory routeWithZeroPool = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - ADDRESS_USDC, + address(USDC_TOKEN), uint8(1), FULL_SHARE, uint16(swapDataZeroPool.length), // Length prefix swapDataZeroPool ); - IERC20(ADDRESS_USDC).approve(address(ldaDiamond), 1000 * 1e6); + IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); vm.expectRevert(InvalidCallData.selector); coreRouteFacet.processRoute( - ADDRESS_USDC, + address(USDC_TOKEN), 1000 * 1e6, address(STG_TOKEN), 0, @@ -579,7 +581,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is bytes memory routeWithZeroRecipient = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - ADDRESS_USDC, + address(USDC_TOKEN), uint8(1), FULL_SHARE, uint16(swapDataZeroRecipient.length), // Length prefix @@ -588,7 +590,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is vm.expectRevert(InvalidCallData.selector); coreRouteFacet.processRoute( - ADDRESS_USDC, + address(USDC_TOKEN), 1000 * 1e6, address(STG_TOKEN), 0, @@ -604,7 +606,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is // Setup multi-hop route: USDC -> STG -> USDC.e MultiHopTestParams memory params = _setupRoutes( - ADDRESS_USDC, + address(USDC_TOKEN), address(STG_TOKEN), address(USDC_E_TOKEN), false, @@ -614,9 +616,9 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is // Build multi-hop route bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); + deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); - IERC20(ADDRESS_USDC).approve(address(ldaDiamond), 1000 * 1e6); + IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves vm.mockCall( @@ -628,7 +630,7 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is vm.expectRevert(WrongPoolReserves.selector); coreRouteFacet.processRoute( - ADDRESS_USDC, + address(USDC_TOKEN), 1000 * 1e6, address(USDC_E_TOKEN), 0, @@ -1002,3 +1004,956 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is } } } + + +contract AlgebraLiquidityAdderHelper { + address public immutable TOKEN_0; + address public immutable TOKEN_1; + + constructor(address _token0, address _token1) { + TOKEN_0 = _token0; + TOKEN_1 = _token1; + } + + function addLiquidity( + address pool, + int24 bottomTick, + int24 topTick, + uint128 amount + ) + external + returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) + { + // Get balances before + uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); + + // Call mint + (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( + address(this), + address(this), + bottomTick, + topTick, + amount, + abi.encode(TOKEN_0, TOKEN_1) + ); + + // Get balances after to account for fees + uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); + + // Calculate actual amounts transferred accounting for fees + amount0 = balance0Before - balance0After; + amount1 = balance1Before - balance1After; + + return (amount0, amount1, liquidityActual); + } + + function algebraMintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata + ) external { + // Check token balances + uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); + + // Transfer what we can, limited by actual balance + if (amount0Owed > 0) { + uint256 amount0ToSend = amount0Owed > balance0 + ? balance0 + : amount0Owed; + uint256 balance0Before = IERC20(TOKEN_0).balanceOf( + address(msg.sender) + ); + IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); + uint256 balance0After = IERC20(TOKEN_0).balanceOf( + address(msg.sender) + ); + // solhint-disable-next-line gas-custom-errors + require(balance0After > balance0Before, "Transfer failed"); + } + + if (amount1Owed > 0) { + uint256 amount1ToSend = amount1Owed > balance1 + ? balance1 + : amount1Owed; + uint256 balance1Before = IERC20(TOKEN_1).balanceOf( + address(msg.sender) + ); + IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); + uint256 balance1After = IERC20(TOKEN_1).balanceOf( + address(msg.sender) + ); + // solhint-disable-next-line gas-custom-errors + require(balance1After > balance1Before, "Transfer failed"); + } + } +} + +/** + * @title Algebra tests + * @notice Tests specific to Algebra pool type + */ +contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { + AlgebraFacet private algebraFacet; + + address private constant APE_ETH_TOKEN = + 0xcF800F4948D16F23333508191B1B1591daF70438; + address private constant WETH_TOKEN = + 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; + address private constant ALGEBRA_FACTORY_APECHAIN = + 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; + address private constant ALGEBRA_QUOTER_V2_APECHAIN = + 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; + address private constant ALGEBRA_POOL_APECHAIN = + 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; + address private constant APE_ETH_HOLDER_APECHAIN = + address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); + + address private constant IMPOSSIBLE_POOL_ADDRESS = + 0x0000000000000000000000000000000000000001; + + struct AlgebraSwapTestParams { + address from; + address to; + address tokenIn; + uint256 amountIn; + address tokenOut; + SwapDirection direction; + bool supportsFeeOnTransfer; + } + + error AlgebraSwapUnexpected(); + + function setUp() public override { + setupApechain(); + super.setUp(); + } + + function _addDexFacet() internal override { + algebraFacet = new AlgebraFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = algebraFacet.swapAlgebra.selector; + functionSelectors[1] = algebraFacet.algebraSwapCallback.selector; + addFacet( + address(ldaDiamond), + address(algebraFacet), + functionSelectors + ); + + algebraFacet = AlgebraFacet(payable(address(ldaDiamond))); + } + + // Override the abstract test with Algebra implementation + function test_CanSwap_FromDexAggregator() public override { + // Fund LDA from whale address + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(address(coreRouteFacet), 1 * 1e18); + + vm.startPrank(USER_SENDER); + + _testAlgebraSwap( + AlgebraSwapTestParams({ + from: address(coreRouteFacet), + to: address(USER_SENDER), + tokenIn: APE_ETH_TOKEN, + amountIn: IERC20(APE_ETH_TOKEN).balanceOf( + address(coreRouteFacet) + ) - 1, + tokenOut: address(WETH_TOKEN), + direction: SwapDirection.Token0ToToken1, + supportsFeeOnTransfer: true + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_FeeOnTransferToken() public { + setupApechain(); + + uint256 amountIn = 534451326669177; + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); + + vm.startPrank(APE_ETH_HOLDER_APECHAIN); + + IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), amountIn); + + // Build route for algebra swap with command code 2 (user funds) + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: APE_ETH_HOLDER_APECHAIN, + pool: ALGEBRA_POOL_APECHAIN, + supportsFeeOnTransfer: true + }) + ); + + // 2. Build the final route with the command and length-prefixed swapData + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Track initial balance + uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( + APE_ETH_HOLDER_APECHAIN + ); + + // Execute the swap + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + amountIn, + WETH_TOKEN, + 0, // minOut = 0 for this test + APE_ETH_HOLDER_APECHAIN, + route + ); + + // Verify balances + uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( + APE_ETH_HOLDER_APECHAIN + ); + assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); + + vm.stopPrank(); + } + + function test_CanSwap() public override { + vm.startPrank(APE_ETH_HOLDER_APECHAIN); + + // Transfer tokens from whale to USER_SENDER + uint256 amountToTransfer = 100 * 1e18; + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); + + vm.stopPrank(); + + vm.startPrank(USER_SENDER); + + _testAlgebraSwap( + AlgebraSwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: APE_ETH_TOKEN, + amountIn: 10 * 1e18, + tokenOut: address(WETH_TOKEN), + direction: SwapDirection.Token0ToToken1, + supportsFeeOnTransfer: true + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_Reverse() public { + test_CanSwap(); + + vm.startPrank(USER_SENDER); + + _testAlgebraSwap( + AlgebraSwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(WETH_TOKEN), + amountIn: 5 * 1e18, + tokenOut: APE_ETH_TOKEN, + direction: SwapDirection.Token1ToToken0, + supportsFeeOnTransfer: false + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { + MultiHopTestState memory state; + state.isFeeOnTransfer = true; + + // Setup tokens and pools + state = _setupTokensAndPools(state); + + // Execute and verify swap + _executeAndVerifyMultiHopSwap(state); + } + + function test_CanSwap_MultiHop() public override { + MultiHopTestState memory state; + state.isFeeOnTransfer = false; + + // Setup tokens and pools + state = _setupTokensAndPools(state); + + // Execute and verify swap + _executeAndVerifyMultiHopSwap(state); + } + + // Test that the proper error is thrown when algebra swap fails + function testRevert_SwapUnexpected() public { + // Transfer tokens from whale to user + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Create invalid pool address + address invalidPool = address(0x999); + + // Mock token0() call on invalid pool + vm.mockCall( + invalidPool, + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(APE_ETH_TOKEN) + ); + + // Create a route with an invalid pool + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: USER_SENDER, + pool: invalidPool, + supportsFeeOnTransfer: true + }) + ); + + bytes memory invalidRoute = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Approve tokens + IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + + // Mock the algebra pool to not reset lastCalledPool + vm.mockCall( + invalidPool, + abi.encodeWithSelector( + IAlgebraPool.swapSupportingFeeOnInputTokens.selector + ), + abi.encode(0, 0) + ); + + // Expect the AlgebraSwapUnexpected error + vm.expectRevert(AlgebraSwapUnexpected.selector); + + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + 1 * 1e18, + address(WETH_TOKEN), + 0, + USER_SENDER, + invalidRoute + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + // Helper function to setup tokens and pools + function _setupTokensAndPools( + MultiHopTestState memory state + ) private returns (MultiHopTestState memory) { + // Create tokens + ERC20 tokenA = new ERC20( + "Token A", + state.isFeeOnTransfer ? "FTA" : "TA", + 18 + ); + IERC20 tokenB; + ERC20 tokenC = new ERC20( + "Token C", + state.isFeeOnTransfer ? "FTC" : "TC", + 18 + ); + + if (state.isFeeOnTransfer) { + tokenB = IERC20( + address( + new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) + ) + ); + } else { + tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); + } + + state.tokenA = IERC20(address(tokenA)); + state.tokenB = tokenB; + state.tokenC = IERC20(address(tokenC)); + + // Label addresses + vm.label(address(state.tokenA), "Token A"); + vm.label(address(state.tokenB), "Token B"); + vm.label(address(state.tokenC), "Token C"); + + // Mint initial token supplies + tokenA.mint(address(this), 1_000_000 * 1e18); + if (!state.isFeeOnTransfer) { + ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); + } else { + MockFeeOnTransferToken(address(tokenB)).mint( + address(this), + 1_000_000 * 1e18 + ); + } + tokenC.mint(address(this), 1_000_000 * 1e18); + + // Create pools + state.pool1 = _createAlgebraPool( + address(state.tokenA), + address(state.tokenB) + ); + state.pool2 = _createAlgebraPool( + address(state.tokenB), + address(state.tokenC) + ); + + vm.label(state.pool1, "Pool 1"); + vm.label(state.pool2, "Pool 2"); + + // Add liquidity + _addLiquidityToPool( + state.pool1, + address(state.tokenA), + address(state.tokenB) + ); + _addLiquidityToPool( + state.pool2, + address(state.tokenB), + address(state.tokenC) + ); + + state.amountToTransfer = 100 * 1e18; + state.amountIn = 50 * 1e18; + + // Transfer tokens to USER_SENDER + IERC20(address(state.tokenA)).transfer( + USER_SENDER, + state.amountToTransfer + ); + + return state; + } + + // Helper function to execute and verify the swap + function _executeAndVerifyMultiHopSwap( + MultiHopTestState memory state + ) private { + vm.startPrank(USER_SENDER); + + uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( + USER_SENDER + ); + uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( + USER_SENDER + ); + + // Approve spending + IERC20(address(state.tokenA)).approve( + address(coreRouteFacet), + state.amountIn + ); + + // Build route + bytes memory route = _buildMultiHopRouteForTest(state); + + // Execute swap + coreRouteFacet.processRoute( + address(state.tokenA), + state.amountIn, + address(state.tokenC), + 0, // No minimum amount out for testing + USER_SENDER, + route + ); + + // Verify results + _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); + + vm.stopPrank(); + } + + // Helper function to build the multi-hop route for test + function _buildMultiHopRouteForTest( + MultiHopTestState memory state +) private view returns (bytes memory) { + // 1. Get the specific data payload for each hop + bytes memory firstHopData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: address(state.tokenA), + recipient: address(ldaDiamond), // Hop 1 sends to the contract itself + pool: state.pool1, + supportsFeeOnTransfer: false + }) + ); + + bytes memory secondHopData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessMyERC20, + tokenIn: address(state.tokenB), + recipient: USER_SENDER, // Hop 2 sends to the final user + pool: state.pool2, + supportsFeeOnTransfer: state.isFeeOnTransfer + }) + ); + + // 2. Assemble the first full command with its length prefix + bytes memory firstCommand = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + state.tokenA, + uint8(1), + FULL_SHARE, + uint16(firstHopData.length), + firstHopData + ); + + // 3. Assemble the second full command with its length prefix + bytes memory secondCommand = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + state.tokenB, + uint8(1), // num splits for the second hop + FULL_SHARE, // full share for the second hop + uint16(secondHopData.length), + secondHopData + ); + + // 4. Concatenate the commands to create the final route + return bytes.concat(firstCommand, secondCommand); +} + + // Helper function to verify multi-hop results + function _verifyMultiHopResults( + MultiHopTestState memory state, + uint256 initialBalanceA, + uint256 initialBalanceC + ) private { + uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( + USER_SENDER + ); + uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( + USER_SENDER + ); + + assertApproxEqAbs( + initialBalanceA - finalBalanceA, + state.amountIn, + 1, // 1 wei tolerance + "TokenA spent amount mismatch" + ); + assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); + + emit log_named_uint( + state.isFeeOnTransfer + ? "Output amount with fee tokens" + : "Output amount with regular tokens", + finalBalanceC - initialBalanceC + ); + } + + // Helper function to create an Algebra pool + function _createAlgebraPool( + address tokenA, + address tokenB + ) internal returns (address pool) { + // Call the actual Algebra factory to create a pool + pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( + tokenA, + tokenB + ); + return pool; + } + + // Helper function to add liquidity to a pool + function _addLiquidityToPool( + address pool, + address token0, + address token1 + ) internal { + // For fee-on-transfer tokens, we need to send more to account for the fee + // We'll use a small amount and send extra to cover fees + uint256 initialAmount0 = 1e17; // 0.1 token + uint256 initialAmount1 = 1e17; // 0.1 token + + // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) + uint256 transferAmount0 = (initialAmount0 * 110) / 100; + uint256 transferAmount1 = (initialAmount1 * 110) / 100; + + // Initialize with 1:1 price ratio (Q64.96 format) + uint160 initialPrice = uint160(1 << 96); + IAlgebraPool(pool).initialize(initialPrice); + + // Create AlgebraLiquidityAdderHelper with safe transfer logic + AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( + token0, + token1 + ); + + // Transfer tokens with extra amounts to account for fees + IERC20(token0).transfer( + address(algebraLiquidityAdderHelper), + transferAmount0 + ); + IERC20(token1).transfer( + address(algebraLiquidityAdderHelper), + transferAmount1 + ); + + // Get actual balances to use for liquidity, accounting for any fees + uint256 actualBalance0 = IERC20(token0).balanceOf( + address(algebraLiquidityAdderHelper) + ); + uint256 actualBalance1 = IERC20(token1).balanceOf( + address(algebraLiquidityAdderHelper) + ); + + // Use the smaller of the two balances for liquidity amount + uint128 liquidityAmount = uint128( + actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 + ); + + // Add liquidity using the actual token amounts we have + algebraLiquidityAdderHelper.addLiquidity( + pool, + -887220, + 887220, + liquidityAmount / 2 // Use half of available liquidity to ensure success + ); + } + + struct MultiHopTestState { + IERC20 tokenA; + IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken + IERC20 tokenC; + address pool1; + address pool2; + uint256 amountIn; + uint256 amountToTransfer; + bool isFeeOnTransfer; + } + + struct AlgebraRouteParams { + CommandType commandCode; // 1 for contract funds, 2 for user funds + address tokenIn; // Input token address + address recipient; // Address receiving the output tokens + address pool; // Algebra pool address + bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens + } + + // Helper function to build route for Apechain Algebra swap + function _buildAlgebraSwapData( + AlgebraRouteParams memory params +) private view returns (bytes memory) { + address token0 = IAlgebraPool(params.pool).token0(); + bool zeroForOne = (params.tokenIn == token0); + SwapDirection direction = zeroForOne + ? SwapDirection.Token0ToToken1 + : SwapDirection.Token1ToToken0; + + // This data blob is what the AlgebraFacet will receive and parse + return abi.encodePacked( + AlgebraFacet.swapAlgebra.selector, + params.pool, + uint8(direction), + params.recipient, + params.supportsFeeOnTransfer ? uint8(1) : uint8(0) + ); +} + +// Helper function to test an Algebra swap +function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { + // Find or create a pool + address pool = _getPool(params.tokenIn, params.tokenOut); + vm.label(pool, "AlgebraPool"); + + // Get token0 from pool for labeling + address token0 = IAlgebraPool(pool).token0(); + if (params.tokenIn == token0) { + vm.label( + params.tokenIn, + string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") + ); + vm.label( + params.tokenOut, + string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") + ); + } else { + vm.label( + params.tokenIn, + string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") + ); + vm.label( + params.tokenOut, + string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") + ); + } + + // Record initial balances + uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + + // Get expected output from QuoterV2 + uint256 expectedOutput = _getQuoteExactInput( + params.tokenIn, + params.tokenOut, + params.amountIn + ); + + // 1. Pack the specific data for this swap + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper + tokenIn: params.tokenIn, + recipient: params.to, + pool: pool, + supportsFeeOnTransfer: params.supportsFeeOnTransfer + }) + ); + + // 2. Approve tokens + IERC20(params.tokenIn).approve( + address(coreRouteFacet), + params.amountIn + ); + + // 3. Set up event expectations + address fromAddress = params.from == address(coreRouteFacet) + ? USER_SENDER + : params.from; + + vm.expectEmit(true, true, true, false); + emit Route( + fromAddress, + params.to, + params.tokenIn, + params.tokenOut, + params.amountIn, + expectedOutput, + expectedOutput + ); + + // 4. Build the route inline and execute the swap to save stack space + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + (expectedOutput * 995) / 1000, // minOut calculated inline + params.to, + abi.encodePacked( + uint8( + params.from == address(coreRouteFacet) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 + ), + params.tokenIn, + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ) + ); + + // 5. Verify final balances + uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + + assertApproxEqAbs( + initialTokenIn - finalTokenIn, + params.amountIn, + 1, + "TokenIn amount mismatch" + ); + assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); +} + + function _getPool( + address tokenA, + address tokenB + ) private view returns (address pool) { + pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( + tokenA, + tokenB + ); + if (pool == address(0)) revert PoolDoesNotExist(); + return pool; + } + + function _getQuoteExactInput( + address tokenIn, + address tokenOut, + uint256 amountIn + ) private returns (uint256 amountOut) { + (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) + .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); + return amountOut; + } + + function testRevert_AlgebraSwap_ZeroAddressPool() public { + // Transfer tokens from whale to user + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Mock token0() call on address(0) + vm.mockCall( + address(0), + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(APE_ETH_TOKEN) + ); + + // Build route with address(0) as pool + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: USER_SENDER, + pool: address(0), // Zero address pool + supportsFeeOnTransfer: true + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Approve tokens + IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + 1 * 1e18, + address(WETH_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { + // Transfer tokens from whale to user + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS + vm.mockCall( + IMPOSSIBLE_POOL_ADDRESS, + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(APE_ETH_TOKEN) + ); + + // Build route with IMPOSSIBLE_POOL_ADDRESS as pool + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: USER_SENDER, + pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address + supportsFeeOnTransfer: true + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Approve tokens + IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + 1 * 1e18, + address(WETH_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + function testRevert_AlgebraSwap_ZeroAddressRecipient() public { + // Transfer tokens from whale to user + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Mock token0() call on the pool + vm.mockCall( + ALGEBRA_POOL_APECHAIN, + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(APE_ETH_TOKEN) + ); + + // Build route with address(0) as recipient + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: address(0), // Zero address recipient + pool: ALGEBRA_POOL_APECHAIN, + supportsFeeOnTransfer: true + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Approve tokens + IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + 1 * 1e18, + address(WETH_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } +} diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index e2e950565..5dbcc2114 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -6,8 +6,16 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; +import { TestHelpers } from "../../../utils/TestHelpers.sol"; +import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; + +contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { + LdaDiamond internal ldaDiamond; + + function setUp() public virtual { + ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER); + } -contract LdaDiamondTest is BaseDiamondTest { function createLdaDiamond( address _diamondOwner ) internal returns (LdaDiamond) { diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index cceb2efc2..2eeac233e 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -14,6 +14,9 @@ import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; import { ReentrancyError, ETHTransferFailed } from "src/Errors/GenericErrors.sol"; import { stdJson } from "forge-std/StdJson.sol"; import { console2 } from "forge-std/console2.sol"; +import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; +import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; +import { TestHelpers } from "./TestHelpers.sol"; using stdJson for string; @@ -91,7 +94,7 @@ contract ReentrancyChecker is DSTest { //common utilities for forge tests // solhint-disable max-states-count -abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { +abstract contract TestBase is Test, TestBaseForksConstants, TestBaseRandomConstants, TestHelpers, DiamondTest, ILiFi { address internal _facetTestContractAddress; uint64 internal currentTxId; bytes32 internal nextUser = keccak256(abi.encodePacked("user address")); @@ -101,7 +104,6 @@ abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { ERC20 internal dai; ERC20 internal weth; LiFiDiamond internal diamond; - LdaDiamond internal ldaDiamond; FeeCollector internal feeCollector; ILiFi.BridgeData internal bridgeData; LibSwap.SwapData[] internal swapData; @@ -111,9 +113,6 @@ abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { // tokenAddress => userAddress => balance mapping(address => mapping(address => uint256)) internal initialBalances; uint256 internal addToMessageValue; - // set these custom values in your test file to - uint256 internal customBlockNumberForForking; - string internal customRpcUrlForForking; string internal logFilePath; // EVENTS @@ -131,95 +130,6 @@ abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { error NativeBridgeFailed(); error ERC20BridgeFailed(); - // CONSTANTS - // Forking - uint256 internal constant DEFAULT_BLOCK_NUMBER_MAINNET = 15588208; - - // WALLET ADDRESSES (all networks) - address internal constant REFUND_WALLET = - 0x317F8d18FB16E49a958Becd0EA72f8E153d25654; - address internal constant WITHDRAW_WALLET = - 0x08647cc950813966142A416D40C382e2c5DB73bB; - - // Contract addresses (MAINNET) - // solhint-disable var-name-mixedcase - address internal ADDRESS_UNISWAP = - 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; - // solhint-disable var-name-mixedcase - address internal ADDRESS_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - // solhint-disable var-name-mixedcase - address internal ADDRESS_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; - // solhint-disable var-name-mixedcase - address internal ADDRESS_DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; - // solhint-disable var-name-mixedcase - address internal ADDRESS_WRAPPED_NATIVE = - 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - // Contract addresses (ARBITRUM) - address internal constant ADDRESS_UNISWAP_ARB = - 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24; - address internal constant ADDRESS_SUSHISWAP_ARB = - 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; - address internal constant ADDRESS_USDC_ARB = - 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; - address internal constant ADDRESS_USDT_ARB = - 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; - address internal constant ADDRESS_DAI_ARB = - 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; - address internal constant ADDRESS_WRAPPED_NATIVE_ARB = - 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; - // Contract addresses (POLYGON) - address internal constant ADDRESS_UNISWAP_POL = - 0xedf6066a2b290C185783862C7F4776A2C8077AD1; - address internal constant ADDRESS_SUSHISWAP_POL = - 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; - address internal constant ADDRESS_USDC_POL = - 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359; // Circle USDC, decimals: 6 - address internal constant ADDRESS_USDCE_POL = - 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; // USDC.e - address internal constant ADDRESS_USDT_POL = - 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; - address internal constant ADDRESS_DAI_POL = - 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063; - address internal constant ADDRESS_WETH_POL = - 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; - address internal constant ADDRESS_WRAPPED_NATIVE_POL = - 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; // WMATIC - // Contract addresses (BASE) - address internal constant ADDRESS_UNISWAP_BASE = - 0x6BDED42c6DA8FBf0d2bA55B2fa120C5e0c8D7891; - address internal constant ADDRESS_USDC_BASE = - 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - address internal constant ADDRESS_USDT_BASE = - 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2; - address internal constant ADDRESS_DAI_BASE = - 0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb; - address internal constant ADDRESS_WRAPPED_NATIVE_BASE = - 0x4200000000000000000000000000000000000006; - // Contract addresses (OPTIMISM) - address internal constant ADDRESS_UNISWAP_OPTIMISM = - 0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2; - address internal constant ADDRESS_USDC_OPTIMISM = - 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85; - address internal constant ADDRESS_USDT_OPTIMISM = - 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; - address internal constant ADDRESS_DAI_OPTIMISM = - 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; - address internal constant ADDRESS_WRAPPED_NATIVE_OPTIMISM = - 0x4200000000000000000000000000000000000006; - // User accounts (Whales: ETH only) - address internal constant USER_SENDER = address(0xabc123456); // initially funded with 100,000 DAI, USDC, USDT, WETH & ETHER - address internal constant USER_RECEIVER = address(0xabc654321); - address internal constant USER_REFUND = address(0xabcdef281); - address internal constant USER_PAUSER = address(0xdeadbeef); - address internal constant USER_DIAMOND_OWNER = - 0x5042255A3F3FD7727e419CeA387cAFDfad3C3aF8; - address internal constant USER_USDC_WHALE = - 0x72A53cDBBcc1b9efa39c834A540550e23463AAcB; - address internal constant USER_DAI_WHALE = - 0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016; - address internal constant USER_WETH_WHALE = - 0xF04a5cC80B1E94C69B48f5ee68a08CD2F09A7c3E; - // MODIFIERS //@dev token == address(0) => check balance of native token @@ -326,10 +236,6 @@ abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { // deploy & configure diamond diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); - console2.log("diamond", address(diamond)); - ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER); - console2.log("ldaDiamond", address(ldaDiamond)); - // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); @@ -373,17 +279,6 @@ abstract contract TestBase is Test, DiamondTest, LdaDiamondTest, ILiFi { vm.label(facetAddress, facetName); } - function fork() internal virtual { - string memory rpcUrl = bytes(customRpcUrlForForking).length > 0 - ? vm.envString(customRpcUrlForForking) - : vm.envString("ETH_NODE_URI_MAINNET"); - uint256 blockNumber = customBlockNumberForForking > 0 - ? customBlockNumberForForking - : 14847528; - - vm.createSelectFork(rpcUrl, blockNumber); - } - function setDefaultBridgeData() internal { bridgeData = ILiFi.BridgeData({ transactionId: "someId", diff --git a/test/solidity/utils/TestBaseForksConstants.sol b/test/solidity/utils/TestBaseForksConstants.sol new file mode 100644 index 000000000..ef5dc7e55 --- /dev/null +++ b/test/solidity/utils/TestBaseForksConstants.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +abstract contract TestBaseForksConstants { + // Forking + uint256 internal constant DEFAULT_BLOCK_NUMBER_MAINNET = 15588208; + + // WALLET ADDRESSES (all networks) + address internal constant REFUND_WALLET = + 0x317F8d18FB16E49a958Becd0EA72f8E153d25654; + address internal constant WITHDRAW_WALLET = + 0x08647cc950813966142A416D40C382e2c5DB73bB; + + // Contract addresses (MAINNET) + // solhint-disable var-name-mixedcase + address internal ADDRESS_UNISWAP = + 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; + // solhint-disable var-name-mixedcase + address internal ADDRESS_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + // solhint-disable var-name-mixedcase + address internal ADDRESS_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + // solhint-disable var-name-mixedcase + address internal ADDRESS_DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + // solhint-disable var-name-mixedcase + address internal ADDRESS_WRAPPED_NATIVE = + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + // Contract addresses (ARBITRUM) + address internal constant ADDRESS_UNISWAP_ARB = + 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24; + address internal constant ADDRESS_SUSHISWAP_ARB = + 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; + address internal constant ADDRESS_USDC_ARB = + 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + address internal constant ADDRESS_USDT_ARB = + 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9; + address internal constant ADDRESS_DAI_ARB = + 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + address internal constant ADDRESS_WRAPPED_NATIVE_ARB = + 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; + // Contract addresses (POLYGON) + address internal constant ADDRESS_UNISWAP_POL = + 0xedf6066a2b290C185783862C7F4776A2C8077AD1; + address internal constant ADDRESS_SUSHISWAP_POL = + 0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506; + address internal constant ADDRESS_USDC_POL = + 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359; // Circle USDC, decimals: 6 + address internal constant ADDRESS_USDCE_POL = + 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; // USDC.e + address internal constant ADDRESS_USDT_POL = + 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; + address internal constant ADDRESS_DAI_POL = + 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063; + address internal constant ADDRESS_WETH_POL = + 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; + address internal constant ADDRESS_WRAPPED_NATIVE_POL = + 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; // WMATIC + // Contract addresses (BASE) + address internal constant ADDRESS_UNISWAP_BASE = + 0x6BDED42c6DA8FBf0d2bA55B2fa120C5e0c8D7891; + address internal constant ADDRESS_USDC_BASE = + 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + address internal constant ADDRESS_USDT_BASE = + 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2; + address internal constant ADDRESS_DAI_BASE = + 0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb; + address internal constant ADDRESS_WRAPPED_NATIVE_BASE = + 0x4200000000000000000000000000000000000006; + // Contract addresses (OPTIMISM) + address internal constant ADDRESS_UNISWAP_OPTIMISM = + 0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2; + address internal constant ADDRESS_USDC_OPTIMISM = + 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85; + address internal constant ADDRESS_USDT_OPTIMISM = + 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; + address internal constant ADDRESS_DAI_OPTIMISM = + 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + address internal constant ADDRESS_WRAPPED_NATIVE_OPTIMISM = + 0x4200000000000000000000000000000000000006; + // User accounts (Whales: ETH only) + address internal constant USER_USDC_WHALE = + 0x72A53cDBBcc1b9efa39c834A540550e23463AAcB; + address internal constant USER_DAI_WHALE = + 0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016; + address internal constant USER_WETH_WHALE = + 0xF04a5cC80B1E94C69B48f5ee68a08CD2F09A7c3E; +} diff --git a/test/solidity/utils/TestBaseRandomConstants.sol b/test/solidity/utils/TestBaseRandomConstants.sol new file mode 100644 index 000000000..4fad2acdd --- /dev/null +++ b/test/solidity/utils/TestBaseRandomConstants.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +abstract contract TestBaseRandomConstants { + address internal constant USER_SENDER = address(0xabc123456); + address internal constant USER_RECEIVER = address(0xabc654321); + address internal constant USER_REFUND = address(0xabcdef281); + address internal constant USER_PAUSER = address(0xdeadbeef); + address internal constant USER_DIAMOND_OWNER = + 0x5042255A3F3FD7727e419CeA387cAFDfad3C3aF8; +} diff --git a/test/solidity/utils/TestHelpers.sol b/test/solidity/utils/TestHelpers.sol index 5c1d6d062..d4febb1ad 100644 --- a/test/solidity/utils/TestHelpers.sol +++ b/test/solidity/utils/TestHelpers.sol @@ -13,6 +13,9 @@ interface DexManager { //common utilities for forge tests contract TestHelpers is Test { + uint256 internal customBlockNumberForForking; + string internal customRpcUrlForForking; + /// @notice will deploy and fund a mock DEX that can simulate the following behaviour for both ERC20/Native: /// positive slippage#1: uses less input tokens as expected /// positive slippage#2: returns more output tokens as expected @@ -77,6 +80,17 @@ contract TestHelpers is Test { mockDex.mockSwapWillRevertWithReason.selector ); } + + function fork() internal virtual { + string memory rpcUrl = bytes(customRpcUrlForForking).length > 0 + ? vm.envString(customRpcUrlForForking) + : vm.envString("ETH_NODE_URI_MAINNET"); + uint256 blockNumber = customBlockNumberForForking > 0 + ? customBlockNumberForForking + : 14847528; + + vm.createSelectFork(rpcUrl, blockNumber); + } } contract NonETHReceiver { From e7937a7fed262ce1541b2344192388e8ba03d0b7 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 5 Aug 2025 11:24:10 +0200 Subject: [PATCH 009/220] added deal --- .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 338 +++++++++--------- 1 file changed, 169 insertions(+), 169 deletions(-) diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index 4929bc1b8..50b90eff1 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -22,11 +22,8 @@ import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; import { TestToken as ERC20 } from "../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; -import { TestBase } from "../../utils/TestBase.sol"; -import { TestBaseRandomConstants } from "../../utils/TestBaseRandomConstants.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; - // Command codes for route processing enum CommandType { None, // 0 - not used @@ -261,6 +258,8 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is function setUp() public override { setupOptimism(); super.setUp(); + + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); } function _addDexFacet() internal override { @@ -366,8 +365,9 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is from: address(ldaDiamond), to: address(USER_SENDER), tokenIn: address(USDC_TOKEN), - amountIn: IERC20(address(USDC_TOKEN)).balanceOf(address(ldaDiamond)) - - 1, // adjust for slot undrain protection: subtract 1 token so that the + amountIn: IERC20(address(USDC_TOKEN)).balanceOf( + address(ldaDiamond) + ) - 1, // adjust for slot undrain protection: subtract 1 token so that the // aggregator's balance isn't completely drained, matching the contract's safeguard tokenOut: address(USDC_E_TOKEN), stable: false, @@ -1005,7 +1005,6 @@ contract LiFiDexAggregatorVelodromeV2UpgradeTest is } } - contract AlgebraLiquidityAdderHelper { address public immutable TOKEN_0; address public immutable TOKEN_1; @@ -1192,15 +1191,15 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { }) ); - // 2. Build the final route with the command and length-prefixed swapData - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix - swapData - ); + // 2. Build the final route with the command and length-prefixed swapData + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); // Track initial balance uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( @@ -1485,52 +1484,52 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { // Helper function to build the multi-hop route for test function _buildMultiHopRouteForTest( - MultiHopTestState memory state -) private view returns (bytes memory) { - // 1. Get the specific data payload for each hop - bytes memory firstHopData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: address(state.tokenA), - recipient: address(ldaDiamond), // Hop 1 sends to the contract itself - pool: state.pool1, - supportsFeeOnTransfer: false - }) - ); + MultiHopTestState memory state + ) private view returns (bytes memory) { + // 1. Get the specific data payload for each hop + bytes memory firstHopData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: address(state.tokenA), + recipient: address(ldaDiamond), // Hop 1 sends to the contract itself + pool: state.pool1, + supportsFeeOnTransfer: false + }) + ); - bytes memory secondHopData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessMyERC20, - tokenIn: address(state.tokenB), - recipient: USER_SENDER, // Hop 2 sends to the final user - pool: state.pool2, - supportsFeeOnTransfer: state.isFeeOnTransfer - }) - ); + bytes memory secondHopData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessMyERC20, + tokenIn: address(state.tokenB), + recipient: USER_SENDER, // Hop 2 sends to the final user + pool: state.pool2, + supportsFeeOnTransfer: state.isFeeOnTransfer + }) + ); - // 2. Assemble the first full command with its length prefix - bytes memory firstCommand = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - state.tokenA, - uint8(1), - FULL_SHARE, - uint16(firstHopData.length), - firstHopData - ); + // 2. Assemble the first full command with its length prefix + bytes memory firstCommand = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + state.tokenA, + uint8(1), + FULL_SHARE, + uint16(firstHopData.length), + firstHopData + ); - // 3. Assemble the second full command with its length prefix - bytes memory secondCommand = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - state.tokenB, - uint8(1), // num splits for the second hop - FULL_SHARE, // full share for the second hop - uint16(secondHopData.length), - secondHopData - ); + // 3. Assemble the second full command with its length prefix + bytes memory secondCommand = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + state.tokenB, + uint8(1), // num splits for the second hop + FULL_SHARE, // full share for the second hop + uint16(secondHopData.length), + secondHopData + ); - // 4. Concatenate the commands to create the final route - return bytes.concat(firstCommand, secondCommand); -} + // 4. Concatenate the commands to create the final route + return bytes.concat(firstCommand, secondCommand); + } // Helper function to verify multi-hop results function _verifyMultiHopResults( @@ -1652,129 +1651,130 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { // Helper function to build route for Apechain Algebra swap function _buildAlgebraSwapData( - AlgebraRouteParams memory params -) private view returns (bytes memory) { - address token0 = IAlgebraPool(params.pool).token0(); - bool zeroForOne = (params.tokenIn == token0); - SwapDirection direction = zeroForOne - ? SwapDirection.Token0ToToken1 - : SwapDirection.Token1ToToken0; - - // This data blob is what the AlgebraFacet will receive and parse - return abi.encodePacked( - AlgebraFacet.swapAlgebra.selector, - params.pool, - uint8(direction), - params.recipient, - params.supportsFeeOnTransfer ? uint8(1) : uint8(0) - ); -} + AlgebraRouteParams memory params + ) private view returns (bytes memory) { + address token0 = IAlgebraPool(params.pool).token0(); + bool zeroForOne = (params.tokenIn == token0); + SwapDirection direction = zeroForOne + ? SwapDirection.Token0ToToken1 + : SwapDirection.Token1ToToken0; + + // This data blob is what the AlgebraFacet will receive and parse + return + abi.encodePacked( + AlgebraFacet.swapAlgebra.selector, + params.pool, + uint8(direction), + params.recipient, + params.supportsFeeOnTransfer ? uint8(1) : uint8(0) + ); + } -// Helper function to test an Algebra swap -function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { - // Find or create a pool - address pool = _getPool(params.tokenIn, params.tokenOut); - vm.label(pool, "AlgebraPool"); + // Helper function to test an Algebra swap + function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { + // Find or create a pool + address pool = _getPool(params.tokenIn, params.tokenOut); + vm.label(pool, "AlgebraPool"); + + // Get token0 from pool for labeling + address token0 = IAlgebraPool(pool).token0(); + if (params.tokenIn == token0) { + vm.label( + params.tokenIn, + string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") + ); + vm.label( + params.tokenOut, + string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") + ); + } else { + vm.label( + params.tokenIn, + string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") + ); + vm.label( + params.tokenOut, + string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") + ); + } - // Get token0 from pool for labeling - address token0 = IAlgebraPool(pool).token0(); - if (params.tokenIn == token0) { - vm.label( - params.tokenIn, - string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( - params.tokenOut, - string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") - ); - } else { - vm.label( + // Record initial balances + uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + + // Get expected output from QuoterV2 + uint256 expectedOutput = _getQuoteExactInput( params.tokenIn, - string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( params.tokenOut, - string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") + params.amountIn ); - } - - // Record initial balances - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - // Get expected output from QuoterV2 - uint256 expectedOutput = _getQuoteExactInput( - params.tokenIn, - params.tokenOut, - params.amountIn - ); - - // 1. Pack the specific data for this swap - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper - tokenIn: params.tokenIn, - recipient: params.to, - pool: pool, - supportsFeeOnTransfer: params.supportsFeeOnTransfer - }) - ); + // 1. Pack the specific data for this swap + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper + tokenIn: params.tokenIn, + recipient: params.to, + pool: pool, + supportsFeeOnTransfer: params.supportsFeeOnTransfer + }) + ); - // 2. Approve tokens - IERC20(params.tokenIn).approve( - address(coreRouteFacet), - params.amountIn - ); + // 2. Approve tokens + IERC20(params.tokenIn).approve( + address(coreRouteFacet), + params.amountIn + ); - // 3. Set up event expectations - address fromAddress = params.from == address(coreRouteFacet) - ? USER_SENDER - : params.from; - - vm.expectEmit(true, true, true, false); - emit Route( - fromAddress, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - expectedOutput, - expectedOutput - ); + // 3. Set up event expectations + address fromAddress = params.from == address(coreRouteFacet) + ? USER_SENDER + : params.from; - // 4. Build the route inline and execute the swap to save stack space - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - (expectedOutput * 995) / 1000, // minOut calculated inline - params.to, - abi.encodePacked( - uint8( - params.from == address(coreRouteFacet) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 - ), + vm.expectEmit(true, true, true, false); + emit Route( + fromAddress, + params.to, params.tokenIn, - uint8(1), - FULL_SHARE, - uint16(swapData.length), - swapData - ) - ); + params.tokenOut, + params.amountIn, + expectedOutput, + expectedOutput + ); - // 5. Verify final balances - uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + // 4. Build the route inline and execute the swap to save stack space + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + (expectedOutput * 995) / 1000, // minOut calculated inline + params.to, + abi.encodePacked( + uint8( + params.from == address(coreRouteFacet) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 + ), + params.tokenIn, + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ) + ); + + // 5. Verify final balances + uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - assertApproxEqAbs( - initialTokenIn - finalTokenIn, - params.amountIn, - 1, - "TokenIn amount mismatch" - ); - assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); -} + assertApproxEqAbs( + initialTokenIn - finalTokenIn, + params.amountIn, + 1, + "TokenIn amount mismatch" + ); + assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); + } function _getPool( address tokenA, @@ -1884,7 +1884,7 @@ function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { uint16(swapData.length), // <--- Add the length prefix swapData ); - + // Approve tokens IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); From dac9e04417f8ace5e61111db7b610e769c0503ef Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 6 Aug 2025 20:38:49 +0200 Subject: [PATCH 010/220] changes --- src/Libraries/LibUniV3Logic.sol | 69 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 21 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 74 +- .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 1030 ++++++++++++++++- 4 files changed, 1062 insertions(+), 132 deletions(-) diff --git a/src/Libraries/LibUniV3Logic.sol b/src/Libraries/LibUniV3Logic.sol index 767d12bd0..ea07300fa 100644 --- a/src/Libraries/LibUniV3Logic.sol +++ b/src/Libraries/LibUniV3Logic.sol @@ -3,19 +3,7 @@ pragma solidity ^0.8.17; import { LibInputStream } from "./LibInputStream.sol"; -import { LibCallbackManager } from "./LibCallbackManager.sol"; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { InvalidCallData } from "../Errors/GenericErrors.sol"; - -interface IUniV3StylePool { - function swap( - address recipient, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bytes calldata data - ) external returns (int256 amount0, int256 amount1); -} /// @title UniV3 Logic Library /// @author LI.FI (https://li.fi) @@ -24,61 +12,6 @@ library LibUniV3Logic { using SafeERC20 for IERC20; using LibInputStream for uint256; - /// Constants /// - address internal constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; - uint160 internal constant MIN_SQRT_RATIO = 4295128739; - uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - - /// Errors /// - error UniV3SwapUnexpected(); - - /// @notice Executes a generic UniV3-style swap - /// @param stream The input stream containing swap parameters - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token address - /// @param amountIn Amount of input tokens - function executeSwap( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) internal returns (uint256 amountOut) { - address pool = stream.readAddress(); - bool direction = stream.readUint8() > 0; - address recipient = stream.readAddress(); - - if ( - pool == address(0) || - pool == IMPOSSIBLE_POOL_ADDRESS || - recipient == address(0) - ) { - revert InvalidCallData(); - } - - // Transfer tokens if needed - if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); - } - - // Arm callback protection - LibCallbackManager.arm(pool); - - // Execute swap - IUniV3StylePool(pool).swap( - recipient, - direction, - int256(amountIn), - direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, - abi.encode(tokenIn) - ); - - // Verify callback was called (arm should be cleared by callback) - LibCallbackManager.CallbackStorage storage cbStor = LibCallbackManager.callbackStorage(); - if (cbStor.expected != address(0)) { - revert UniV3SwapUnexpected(); - } - } - /// @notice Handles a generic UniV3-style callback /// @param amount0Delta The amount of token0 owed to pool /// @param amount1Delta The amount of token1 owed to pool @@ -96,4 +29,4 @@ library LibUniV3Logic { address tokenIn = abi.decode(data, (address)); IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount)); } -} \ No newline at end of file +} diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index bc53258a3..0d59c3a92 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; -import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; @@ -12,7 +12,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @notice Handles IzumiV3 swaps with callback management /// @custom:version 1.0.0 contract IzumiV3Facet { - using LibInputStream for uint256; + using LibInputStream2 for uint256; using LibCallbackManager for *; using SafeERC20 for IERC20; @@ -27,16 +27,17 @@ contract IzumiV3Facet { /// @notice Performs a swap through iZiSwap V3 pools /// @dev This function handles both X to Y and Y to X swaps through iZiSwap V3 pools - /// @param stream [pool, direction, recipient] + /// @param swapData [pool, direction, recipient] /// @param from Where to take liquidity for swap /// @param tokenIn Input token /// @param amountIn Amount of tokenIn to take for swap function swapIzumiV3( - uint256 stream, + bytes memory swapData, address from, address tokenIn, uint256 amountIn ) external returns (uint256) { + uint256 stream = LibInputStream2.createStream(swapData); address pool = stream.readAddress(); uint8 direction = stream.readUint8(); // 0 = Y2X, 1 = X2Y address recipient = stream.readAddress(); @@ -83,11 +84,19 @@ contract IzumiV3Facet { return 0; // Return value not used in current implementation } - function swapX2YCallback(uint256 amountX, bytes calldata data) external { + function swapX2YCallback( + uint256 amountX, + uint256, + bytes calldata data + ) external { _handleIzumiV3SwapCallback(amountX, data); } - function swapY2XCallback(uint256 amountY, bytes calldata data) external { + function swapY2XCallback( + uint256, + uint256 amountY, + bytes calldata data + ) external { _handleIzumiV3SwapCallback(amountY, data); } diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 3acf0f229..f08f1d64d 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -1,30 +1,92 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; -import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +interface IUniV3StylePool { + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); +} -/// @title UniV3 Facet +/// @title UniV3StyleFacet /// @author LI.FI (https://li.fi) /// @notice Handles Uniswap V3 swaps with callback management /// @custom:version 1.0.0 contract UniV3StyleFacet { + using SafeERC20 for IERC20; using LibCallbackManager for *; - using LibInputStream for uint256; + using LibInputStream2 for uint256; + + /// Constants /// + address internal constant IMPOSSIBLE_POOL_ADDRESS = + 0x0000000000000000000000000000000000000001; + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + uint160 internal constant MAX_SQRT_RATIO = + 1461446703485210103287273052203988822378723970342; + + /// Errors /// + error UniV3SwapUnexpected(); /// @notice Executes a UniswapV3 swap - /// @param stream The input stream containing swap parameters + /// @param swapData The input stream containing swap parameters /// @param from Where to take liquidity for swap /// @param tokenIn Input token address /// @param amountIn Amount of input tokens function swapUniV3( - uint256 stream, + bytes memory swapData, address from, address tokenIn, uint256 amountIn ) external returns (uint256 amountOut) { - return LibUniV3Logic.executeSwap(stream, from, tokenIn, amountIn); + uint256 stream = LibInputStream2.createStream(swapData); + address pool = stream.readAddress(); + bool direction = stream.readUint8() > 0; + address recipient = stream.readAddress(); + + if ( + pool == address(0) || + pool == IMPOSSIBLE_POOL_ADDRESS || + recipient == address(0) + ) { + revert InvalidCallData(); + } + + // Transfer tokens if needed + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom( + msg.sender, + address(this), + amountIn + ); + } + + // Arm callback protection + LibCallbackManager.arm(pool); + + // Execute swap + IUniV3StylePool(pool).swap( + recipient, + direction, + int256(amountIn), + direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + abi.encode(tokenIn) + ); + + // Verify callback was called (arm should be cleared by callback) + LibCallbackManager.CallbackStorage storage cbStor = LibCallbackManager + .callbackStorage(); + if (cbStor.expected != address(0)) { + revert UniV3SwapUnexpected(); + } } /// @notice Callback for UniswapV3 swaps diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index 50b90eff1..10698ec9b 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -11,13 +11,16 @@ import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -// import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -// import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; +import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; +import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; import { TestToken as ERC20 } from "../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; @@ -34,21 +37,6 @@ enum CommandType { ProcessInsideBento, // 5 - processInsideBento ApplyPermit // 6 - applyPermit } - -// Pool type identifiers -enum PoolType { - UniV2, // 0 - UniV3, // 1 - WrapNative, // 2 - BentoBridge, // 3 - Trident, // 4 - Curve, // 5 - VelodromeV2, // 6 - Algebra, // 7 - iZiSwap, // 8 - SyncSwapV2 // 9 -} - // Direction constants enum SwapDirection { Token1ToToken0, // 0 @@ -196,7 +184,7 @@ abstract contract LiFiDexAggregatorUpgradeTest is LdaDiamondTest, TestHelpers { /** * @title VelodromeV2 tests - * @notice Tests specific to Velodrome V2 pool type + * @notice Tests specific to Velodrome V2 */ contract LiFiDexAggregatorVelodromeV2UpgradeTest is LiFiDexAggregatorUpgradeTest @@ -1092,7 +1080,7 @@ contract AlgebraLiquidityAdderHelper { /** * @title Algebra tests - * @notice Tests specific to Algebra pool type + * @notice Tests specific to Algebra */ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { AlgebraFacet private algebraFacet; @@ -1851,27 +1839,80 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { vm.clearMockedCalls(); } - function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { + // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { + // // Transfer tokens from whale to user + // vm.prank(APE_ETH_HOLDER_APECHAIN); + // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + // vm.startPrank(USER_SENDER); + + // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS + // vm.mockCall( + // IMPOSSIBLE_POOL_ADDRESS, + // abi.encodeWithSelector(IAlgebraPool.token0.selector), + // abi.encode(APE_ETH_TOKEN) + // ); + + // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool + // bytes memory swapData = _buildAlgebraSwapData( + // AlgebraRouteParams({ + // commandCode: CommandType.ProcessUserERC20, + // tokenIn: APE_ETH_TOKEN, + // recipient: USER_SENDER, + // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address + // supportsFeeOnTransfer: true + // }) + // ); + + // bytes memory route = abi.encodePacked( + // uint8(CommandType.ProcessUserERC20), + // APE_ETH_TOKEN, + // uint8(1), // number of pools/splits + // FULL_SHARE, // 100% share + // uint16(swapData.length), // <--- Add the length prefix + // swapData + // ); + + // // Approve tokens + // IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + + // // Expect revert with InvalidCallData + // vm.expectRevert(InvalidCallData.selector); + + // coreRouteFacet.processRoute( + // APE_ETH_TOKEN, + // 1 * 1e18, + // address(WETH_TOKEN), + // 0, + // USER_SENDER, + // route + // ); + + // vm.stopPrank(); + // vm.clearMockedCalls(); + // } + + function testRevert_AlgebraSwap_ZeroAddressRecipient() public { // Transfer tokens from whale to user vm.prank(APE_ETH_HOLDER_APECHAIN); IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); vm.startPrank(USER_SENDER); - // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS + // Mock token0() call on the pool vm.mockCall( - IMPOSSIBLE_POOL_ADDRESS, + ALGEBRA_POOL_APECHAIN, abi.encodeWithSelector(IAlgebraPool.token0.selector), abi.encode(APE_ETH_TOKEN) ); - // Build route with IMPOSSIBLE_POOL_ADDRESS as pool + // Build route with address(0) as recipient bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, tokenIn: APE_ETH_TOKEN, - recipient: USER_SENDER, - pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address + recipient: address(0), // Zero address recipient + pool: ALGEBRA_POOL_APECHAIN, supportsFeeOnTransfer: true }) ); @@ -1903,57 +1944,942 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { vm.stopPrank(); vm.clearMockedCalls(); } +} - function testRevert_AlgebraSwap_ZeroAddressRecipient() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); +/** + * @title LiFiDexAggregatorIzumiV3UpgradeTest + * @notice Tests specific to iZiSwap V3 selector + */ +contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { + IzumiV3Facet private izumiV3Facet; + + // ==================== iZiSwap V3 specific variables ==================== + // Base constants + address internal constant USDC = + 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + address internal constant WETH = + 0x4200000000000000000000000000000000000006; + address internal constant USDB_C = + 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; + + // iZiSwap pools + address internal constant IZUMI_WETH_USDC_POOL = + 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; + address internal constant IZUMI_WETH_USDB_C_POOL = + 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; + + // Test parameters + uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals + uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals + + // structs + struct IzumiV3SwapTestParams { + address from; + address to; + address tokenIn; + uint256 amountIn; + address tokenOut; + SwapDirection direction; + } + + struct MultiHopTestParams { + address tokenIn; + address tokenMid; + address tokenOut; + address pool1; + address pool2; + uint256 amountIn; + SwapDirection direction1; + SwapDirection direction2; + } + + error IzumiV3SwapUnexpected(); + error IzumiV3SwapCallbackUnknownSource(); + error IzumiV3SwapCallbackNotPositiveAmount(); + + // Setup function for Base tests + function setupBase() internal { + customRpcUrlForForking = "ETH_NODE_URI_BASE"; + customBlockNumberForForking = 29831758; + } + + function setUp() public override { + setupBase(); + super.setUp(); + + deal(address(USDC), address(USER_SENDER), 1_000 * 1e6); + } + + function _addDexFacet() internal override { + izumiV3Facet = new IzumiV3Facet(); + bytes4[] memory functionSelectors = new bytes4[](3); + functionSelectors[0] = izumiV3Facet.swapIzumiV3.selector; + functionSelectors[1] = izumiV3Facet.swapX2YCallback.selector; + functionSelectors[2] = izumiV3Facet.swapY2XCallback.selector; + addFacet( + address(ldaDiamond), + address(izumiV3Facet), + functionSelectors + ); + + izumiV3Facet = IzumiV3Facet(payable(address(ldaDiamond))); + } + + function test_CanSwap_FromDexAggregator() public override { + // Test USDC -> WETH + deal(USDC, address(coreRouteFacet), AMOUNT_USDC); vm.startPrank(USER_SENDER); + _testSwap( + IzumiV3SwapTestParams({ + from: address(coreRouteFacet), + to: USER_SENDER, + tokenIn: USDC, + amountIn: AMOUNT_USDC, + tokenOut: WETH, + direction: SwapDirection.Token1ToToken0 + }) + ); + vm.stopPrank(); + } - // Mock token0() call on the pool - vm.mockCall( - ALGEBRA_POOL_APECHAIN, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) + function test_CanSwap_MultiHop() public override { + _testMultiHopSwap( + MultiHopTestParams({ + tokenIn: USDC, + tokenMid: WETH, + tokenOut: USDB_C, + pool1: IZUMI_WETH_USDC_POOL, + pool2: IZUMI_WETH_USDB_C_POOL, + amountIn: AMOUNT_USDC, + direction1: SwapDirection.Token1ToToken0, + direction2: SwapDirection.Token0ToToken1 + }) ); + } - // Build route with address(0) as recipient - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: address(0), // Zero address recipient - pool: ALGEBRA_POOL_APECHAIN, - supportsFeeOnTransfer: true + function test_CanSwap() public override { + deal(address(USDC), USER_SENDER, AMOUNT_USDC); + + vm.startPrank(USER_SENDER); + IERC20(USDC).approve(address(coreRouteFacet), AMOUNT_USDC); + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDC_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: USER_RECEIVER }) ); bytes memory route = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, + USDC, uint8(1), // number of pools/splits FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix + uint16(swapData.length), // length prefix swapData ); - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + vm.expectEmit(true, true, true, false); + emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); + + coreRouteFacet.processRoute( + USDC, + AMOUNT_USDC, + WETH, + 0, + USER_RECEIVER, + route + ); + + vm.stopPrank(); + } + + function testRevert_IzumiV3SwapUnexpected() public { + deal(USDC, USER_SENDER, AMOUNT_USDC); + + vm.startPrank(USER_SENDER); + + // create invalid pool address + address invalidPool = address(0x999); + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: invalidPool, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + // create a route with an invalid pool + bytes memory invalidRoute = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + USDC, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + IERC20(USDC).approve(address(coreRouteFacet), AMOUNT_USDC); + + // mock the iZiSwap pool to return without updating lastCalledPool + vm.mockCall( + invalidPool, + abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), + abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool + ); + + vm.expectRevert(IzumiV3SwapUnexpected.selector); + + coreRouteFacet.processRoute( + USDC, + AMOUNT_USDC, + WETH, + 0, + USER_SENDER, + invalidRoute + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + function testRevert_UnexpectedCallbackSender() public { + deal(USDC, USER_SENDER, AMOUNT_USDC); + + // Set up the expected callback sender through the diamond + vm.store( + address(ldaDiamond), + keccak256("com.lifi.lda.callbackmanager"), + bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) + ); + + // Try to call callback from a different address than expected + address unexpectedCaller = address(0xdead); + vm.prank(unexpectedCaller); + vm.expectRevert( + abi.encodeWithSelector( + LibCallbackManager.UnexpectedCallbackSender.selector, + unexpectedCaller, + IZUMI_WETH_USDC_POOL + ) + ); + izumiV3Facet.swapY2XCallback(1, 1, abi.encode(USDC)); + } + + function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { + deal(USDC, USER_SENDER, AMOUNT_USDC); + + // Set the expected callback sender through the diamond storage + vm.store( + address(ldaDiamond), + keccak256("com.lifi.lda.callbackmanager"), + bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) + ); + + // try to call the callback with zero amount + vm.prank(IZUMI_WETH_USDC_POOL); + vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); + izumiV3Facet.swapY2XCallback( + 0, + 0, // zero amount should trigger the error + abi.encode(USDC) + ); + } + + function testRevert_FailsIfAmountInIsTooLarge() public { + deal(address(WETH), USER_SENDER, type(uint256).max); + + vm.startPrank(USER_SENDER); + IERC20(WETH).approve(address(coreRouteFacet), type(uint256).max); + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDC_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_RECEIVER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + WETH, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); - // Expect revert with InvalidCallData vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + WETH, + type(uint216).max, + USDC, + 0, + USER_RECEIVER, + route + ); + + vm.stopPrank(); + } + + function _testSwap(IzumiV3SwapTestParams memory params) internal { + // Fund the sender with tokens if not the contract itself + if (params.from != address(coreRouteFacet)) { + deal(params.tokenIn, params.from, params.amountIn); + } + + // Capture initial token balances + uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( + params.from + ); + uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( + params.to + ); + + // Build the route based on the command type + CommandType commandCode = params.from == address(coreRouteFacet) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20; + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDC_POOL, + direction: params.direction == SwapDirection.Token0ToToken1 + ? SwapDirection.Token0ToToken1 + : SwapDirection.Token1ToToken0, + recipient: params.to + }) + ); + + bytes memory route = abi.encodePacked( + uint8(commandCode), + params.tokenIn, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + // Approve tokens if necessary + if (params.from == USER_SENDER) { + vm.startPrank(USER_SENDER); + IERC20(params.tokenIn).approve( + address(coreRouteFacet), + params.amountIn + ); + } + + // Expect the Route event emission + address from = params.from == address(coreRouteFacet) + ? USER_SENDER + : params.from; + + vm.expectEmit(true, true, true, false); + emit Route( + from, + params.to, + params.tokenIn, + params.tokenOut, + params.amountIn, + 0, // No minimum amount enforced in test + 0 // Actual amount will be checked after the swap + ); + + // Execute the swap + uint256 amountOut = coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + 0, // No minimum amount for testing + params.to, + route + ); + + if (params.from == USER_SENDER) { + vm.stopPrank(); + } + + // Verify balances have changed correctly + uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); + + assertApproxEqAbs( + initialBalanceIn - finalBalanceIn, + params.amountIn, + 1, // 1 wei tolerance because of undrain protection for dex aggregator + "TokenIn amount mismatch" + ); + assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); + assertEq( + amountOut, + finalBalanceOut - initialBalanceOut, + "AmountOut mismatch" + ); + + emit log_named_uint("Amount In", params.amountIn); + emit log_named_uint("Amount Out", amountOut); + } + + function _testMultiHopSwap(MultiHopTestParams memory params) internal { + // Fund the sender with tokens + deal(params.tokenIn, USER_SENDER, params.amountIn); + + // Capture initial token balances + uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( + USER_SENDER + ); + uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( + USER_SENDER + ); + + // Build first swap data + bytes memory firstSwapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: params.pool1, + direction: params.direction1, + recipient: address(coreRouteFacet) + }) + ); + + bytes memory firstHop = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + params.tokenIn, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(firstSwapData.length), // length prefix + firstSwapData + ); + + // Build second swap data + bytes memory secondSwapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: params.pool2, + direction: params.direction2, + recipient: USER_SENDER + }) + ); + + bytes memory secondHop = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + params.tokenMid, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(secondSwapData.length), // length prefix + secondSwapData + ); + + // Combine into route + bytes memory route = bytes.concat(firstHop, secondHop); + + // Approve tokens + vm.startPrank(USER_SENDER); + IERC20(params.tokenIn).approve( + address(coreRouteFacet), + params.amountIn + ); + + // Execute the swap + uint256 amountOut = coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + 0, // No minimum amount for testing + USER_SENDER, + route + ); + vm.stopPrank(); + + // Verify balances have changed correctly + uint256 finalBalanceIn; + uint256 finalBalanceOut; + + finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); + finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); + + assertEq( + initialBalanceIn - finalBalanceIn, + params.amountIn, + "TokenIn amount mismatch" + ); + assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); + assertEq( + amountOut, + finalBalanceOut - initialBalanceOut, + "AmountOut mismatch" + ); + } + + function _buildIzumiV3Route( + CommandType commandCode, + address tokenIn, + uint8 direction, + address pool, + address recipient + ) internal pure returns (bytes memory) { + return + abi.encodePacked( + uint8(commandCode), + tokenIn, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + IzumiV3Facet.swapIzumiV3.selector, + pool, + uint8(direction), + recipient + ); + } + function _buildIzumiV3MultiHopRoute( + MultiHopTestParams memory params + ) internal view returns (bytes memory) { + // First hop: USER_ERC20 -> LDA + bytes memory firstHop = _buildIzumiV3Route( + CommandType.ProcessUserERC20, + params.tokenIn, + uint8(params.direction1), + params.pool1, + address(coreRouteFacet) + ); + + // Second hop: MY_ERC20 (LDA) -> pool2 + bytes memory secondHop = _buildIzumiV3Route( + CommandType.ProcessMyERC20, + params.tokenMid, + uint8(params.direction2), + params.pool2, + USER_SENDER // final recipient + ); + + // Combine the two hops + return bytes.concat(firstHop, secondHop); + } + + struct IzumiV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildIzumiV3SwapData( + IzumiV3SwapParams memory params + ) internal view returns (bytes memory) { + return + abi.encodePacked( + izumiV3Facet.swapIzumiV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); + } +} + +// ----------------------------------------------------------------------------- +// HyperswapV3 on HyperEVM +// ----------------------------------------------------------------------------- +contract LiFiDexAggregatorHyperswapV3UpgradeTest is + LiFiDexAggregatorUpgradeTest +{ + using SafeERC20 for IERC20; + + UniV3StyleFacet internal uniV3StyleFacet; + + /// @dev HyperswapV3 router on HyperEVM chain + IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = + IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); + /// @dev HyperswapV3 quoter on HyperEVM chain + IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = + IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); + + /// @dev a liquid USDT on HyperEVM + IERC20 internal constant USDT0 = + IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); + /// @dev WHYPE on HyperEVM + IERC20 internal constant WHYPE = + IERC20(0x5555555555555555555555555555555555555555); + + struct HyperswapV3Params { + CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 + address tokenIn; // Input token address + address recipient; // Address receiving the output tokens + address pool; // HyperswapV3 pool address + bool zeroForOne; // Direction of the swap + } + + function setUp() public override { + setupHyperEVM(); + super.setUp(); + + deal(address(USDT0), address(USER_SENDER), 1_000 * 1e6); + } + + function _addDexFacet() internal override { + uniV3StyleFacet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; + functionSelectors[1] = uniV3StyleFacet + .hyperswapV3SwapCallback + .selector; + addFacet( + address(ldaDiamond), + address(uniV3StyleFacet), + functionSelectors + ); + + uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + + deal(address(USDT0), USER_SENDER, amountIn); + + // user approves + vm.prank(USER_SENDER); + USDT0.approve(address(uniV3StyleFacet), amountIn); + + // fetch the real pool and quote + address pool = HYPERSWAP_FACTORY.getPool( + address(USDT0), + address(WHYPE), + 3000 + ); + + // Create the params struct for quoting + IHyperswapV3QuoterV2.QuoteExactInputSingleParams + memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: amountIn, + fee: 3000, + sqrtPriceLimitX96: 0 + }); + + // Get the quote using the struct + (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + params + ); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: pool, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDT0), + uint8(1), // 1 pool + FULL_SHARE, // FULL_SHARE + uint16(swapData.length), // length prefix + swapData + ); + + // expect the Route event + vm.expectEmit(true, true, true, true); + emit Route( + USER_SENDER, + USER_SENDER, + address(USDT0), + address(WHYPE), + amountIn, + quoted, + quoted + ); + + // execute + vm.prank(USER_SENDER); coreRouteFacet.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), + address(USDT0), + amountIn, + address(WHYPE), + quoted, + USER_SENDER, + route + ); + } + + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + + // Fund dex aggregator contract + deal(address(USDT0), address(ldaDiamond), amountIn); + + // fetch the real pool and quote + address pool = HYPERSWAP_FACTORY.getPool( + address(USDT0), + address(WHYPE), + 3000 + ); + + // Create the params struct for quoting + IHyperswapV3QuoterV2.QuoteExactInputSingleParams + memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection + fee: 3000, + sqrtPriceLimitX96: 0 + }); + + // Get the quote using the struct + (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + params + ); + + // Build route using our helper function + // bytes memory route = _buildHyperswapV3Route( + // HyperswapV3Params({ + // commandCode: CommandType.ProcessMyERC20, + // tokenIn: address(USDT0), + // recipient: USER_SENDER, + // pool: pool, + // zeroForOne: true // USDT0 < WHYPE + // }) + // ); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: pool, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(USDT0), + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + // expect the Route event + vm.expectEmit(true, true, true, true); + emit Route( + USER_SENDER, + USER_SENDER, + address(USDT0), + address(WHYPE), + amountIn - 1, // Account for slot undrain protection + quoted, + quoted + ); + + // execute + vm.prank(USER_SENDER); + coreRouteFacet.processRoute( + address(USDT0), + amountIn - 1, // Account for slot undrain protection + address(WHYPE), + quoted, + USER_SENDER, + route + ); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. + // HyperswapV3 does not support a "one-pool" second hop today, because + // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. HyperswapV3's swap() immediately reverts on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } + + // function _buildHyperswapV3Route( + // HyperswapV3Params memory params + // ) internal pure returns (bytes memory route) { + // route = abi.encodePacked( + // uint8(params.commandCode), + // params.tokenIn, + // uint8(1), // 1 pool + // FULL_SHARE, // 65535 - 100% share + // uint8(PoolType.UniV3), // POOL_TYPE_UNIV3 = 1 + // params.pool, + // uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false + // params.recipient + // ); + + // return route; + // } + + struct UniV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildUniV3SwapData( + UniV3SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + uniV3StyleFacet.swapUniV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); + } +} + +// ----------------------------------------------------------------------------- +// LaminarV3 on HyperEVM +// ----------------------------------------------------------------------------- +contract LiFiDexAggregatorLaminarV3UpgradeTest is + LiFiDexAggregatorUpgradeTest +{ + UniV3StyleFacet internal uniV3StyleFacet; + using SafeERC20 for IERC20; + + IERC20 internal constant WHYPE = + IERC20(0x5555555555555555555555555555555555555555); + IERC20 internal constant LHYPE = + IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); + + address internal constant WHYPE_LHYPE_POOL = + 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; + + function setUp() public override { + setupHyperEVM(); + super.setUp(); + } + + function _addDexFacet() internal override { + uniV3StyleFacet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; + functionSelectors[1] = uniV3StyleFacet.laminarV3SwapCallback.selector; + addFacet( + address(ldaDiamond), + address(uniV3StyleFacet), + functionSelectors + ); + + uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e18; + + // Fund the user with WHYPE + deal(address(WHYPE), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WHYPE.approve(address(uniV3StyleFacet), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: WHYPE_LHYPE_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(WHYPE), + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances + uint256 inBefore = WHYPE.balanceOf(USER_SENDER); + uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + + // Execute swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(WHYPE), + amountIn, + address(LHYPE), 0, USER_SENDER, route ); + // Verify + uint256 inAfter = WHYPE.balanceOf(USER_SENDER); + uint256 outAfter = LHYPE.balanceOf(USER_SENDER); + assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + vm.stopPrank(); - vm.clearMockedCalls(); + } + + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 1_000 * 1e18; + + // fund the aggregator directly + deal(address(WHYPE), address(uniV3StyleFacet), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: WHYPE_LHYPE_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(WHYPE), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + + // Withdraw 1 wei to avoid slot-undrain protection + coreRouteFacet.processRoute( + address(WHYPE), + amountIn - 1, + address(LHYPE), + 0, + USER_SENDER, + route + ); + + uint256 outAfter = LHYPE.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. + // Laminar V3 does not support a "one-pool" second hop today, because + // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. Laminar V3's swap() immediately reverts on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } + + struct UniV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildUniV3SwapData( + UniV3SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + uniV3StyleFacet.swapUniV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); } } From c7a3ca79a4e1328dc3a94643207a832767fdefa5 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 6 Aug 2025 22:43:36 +0200 Subject: [PATCH 011/220] changes --- ...{SyncSwapFacet.sol => SyncSwapV2Facet.sol} | 22 +- .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 1101 ++++++++++++++++- 2 files changed, 1093 insertions(+), 30 deletions(-) rename src/Periphery/Lda/Facets/{SyncSwapFacet.sol => SyncSwapV2Facet.sol} (81%) diff --git a/src/Periphery/Lda/Facets/SyncSwapFacet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol similarity index 81% rename from src/Periphery/Lda/Facets/SyncSwapFacet.sol rename to src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index 07f4cfab0..30402d255 100644 --- a/src/Periphery/Lda/Facets/SyncSwapFacet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -2,32 +2,34 @@ pragma solidity ^0.8.17; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; import { ISyncSwapPool } from "lifi/Interfaces/ISyncSwapPool.sol"; import { ISyncSwapVault } from "lifi/Interfaces/ISyncSwapVault.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -/// @title SyncSwap Facet +/// @title SyncSwapV2Facet /// @author LI.FI (https://li.fi) /// @notice Handles SyncSwap swaps with callback management /// @custom:version 1.0.0 -contract SyncSwapFacet { - using LibInputStream for uint256; +contract SyncSwapV2Facet { + using LibInputStream2 for uint256; using SafeERC20 for IERC20; - /// @notice Performs a swap through SyncSwap pools - /// @dev This function handles both X to Y and Y to X swaps through SyncSwap pools. - /// See [SyncSwap API documentation](https://docs.syncswap.xyz/api-documentation) for protocol details. - /// @param stream [pool, to, withdrawMode, isV1Pool, vault] + /// @notice Performs a swap through SyncSwapV2 pools + /// @dev This function handles both X to Y and Y to X swaps through SyncSwapV2 pools. + /// See [SyncSwapV2 API documentation](https://docs.syncswap.xyz/api-documentation) for protocol details. + /// @param swapData [pool, to, withdrawMode, isV1Pool, vault] /// @param from Where to take liquidity for swap /// @param tokenIn Input token /// @param amountIn Amount of tokenIn to take for swap - function swapSyncSwap( - uint256 stream, + function swapSyncSwapV2( + bytes memory swapData, address from, address tokenIn, uint256 amountIn ) external returns (uint256) { + uint256 stream = LibInputStream2.createStream(swapData); + address pool = stream.readAddress(); address to = stream.readAddress(); diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index 10698ec9b..cf8899d9b 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -21,6 +21,7 @@ import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; import { TestToken as ERC20 } from "../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; @@ -111,6 +112,26 @@ abstract contract LiFiDexAggregatorUpgradeTest is LdaDiamondTest, TestHelpers { customBlockNumberForForking = 4433562; } + function setupXDC() internal { + customRpcUrlForForking = "ETH_NODE_URI_XDC"; + customBlockNumberForForking = 89279495; + } + + function setupViction() internal { + customRpcUrlForForking = "ETH_NODE_URI_VICTION"; + customBlockNumberForForking = 94490946; + } + + function setupFlare() internal { + customRpcUrlForForking = "ETH_NODE_URI_FLARE"; + customBlockNumberForForking = 42652369; + } + + function setupLinea() internal { + customRpcUrlForForking = "ETH_NODE_URI_LINEA"; + customBlockNumberForForking = 20077881; + } + function setUp() public virtual override { fork(); LdaDiamondTest.setUp(); @@ -1166,7 +1187,7 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { vm.startPrank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), amountIn); + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), amountIn); // Build route for algebra swap with command code 2 (user funds) bytes memory swapData = _buildAlgebraSwapData( @@ -1320,7 +1341,7 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { ); // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); // Mock the algebra pool to not reset lastCalledPool vm.mockCall( @@ -1447,7 +1468,7 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { // Approve spending IERC20(address(state.tokenA)).approve( - address(coreRouteFacet), + address(ldaDiamond), state.amountIn ); @@ -1709,10 +1730,7 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { ); // 2. Approve tokens - IERC20(params.tokenIn).approve( - address(coreRouteFacet), - params.amountIn - ); + IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); // 3. Set up event expectations address fromAddress = params.from == address(coreRouteFacet) @@ -1821,7 +1839,7 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { ); // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); // Expect revert with InvalidCallData vm.expectRevert(InvalidCallData.selector); @@ -1874,7 +1892,7 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { // ); // // Approve tokens - // IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); // // Expect revert with InvalidCallData // vm.expectRevert(InvalidCallData.selector); @@ -1927,7 +1945,7 @@ contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { ); // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(coreRouteFacet), 1 * 1e18); + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); // Expect revert with InvalidCallData vm.expectRevert(InvalidCallData.selector); @@ -2062,7 +2080,7 @@ contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { deal(address(USDC), USER_SENDER, AMOUNT_USDC); vm.startPrank(USER_SENDER); - IERC20(USDC).approve(address(coreRouteFacet), AMOUNT_USDC); + IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ @@ -2122,7 +2140,7 @@ contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { swapData ); - IERC20(USDC).approve(address(coreRouteFacet), AMOUNT_USDC); + IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); // mock the iZiSwap pool to return without updating lastCalledPool vm.mockCall( @@ -2193,7 +2211,7 @@ contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { deal(address(WETH), USER_SENDER, type(uint256).max); vm.startPrank(USER_SENDER); - IERC20(WETH).approve(address(coreRouteFacet), type(uint256).max); + IERC20(WETH).approve(address(ldaDiamond), type(uint256).max); bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ @@ -2267,7 +2285,7 @@ contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { if (params.from == USER_SENDER) { vm.startPrank(USER_SENDER); IERC20(params.tokenIn).approve( - address(coreRouteFacet), + address(ldaDiamond), params.amountIn ); } @@ -2376,10 +2394,7 @@ contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { // Approve tokens vm.startPrank(USER_SENDER); - IERC20(params.tokenIn).approve( - address(coreRouteFacet), - params.amountIn - ); + IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); // Execute the swap uint256 amountOut = coreRouteFacet.processRoute( @@ -2538,7 +2553,7 @@ contract LiFiDexAggregatorHyperswapV3UpgradeTest is // user approves vm.prank(USER_SENDER); - USDT0.approve(address(uniV3StyleFacet), amountIn); + USDT0.approve(address(ldaDiamond), amountIn); // fetch the real pool and quote address pool = HYPERSWAP_FACTORY.getPool( @@ -2771,7 +2786,7 @@ contract LiFiDexAggregatorLaminarV3UpgradeTest is deal(address(WHYPE), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - WHYPE.approve(address(uniV3StyleFacet), amountIn); + WHYPE.approve(address(ldaDiamond), amountIn); bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ @@ -2883,3 +2898,1049 @@ contract LiFiDexAggregatorLaminarV3UpgradeTest is ); } } + +contract LiFiDexAggregatorXSwapV3UpgradeTest is LiFiDexAggregatorUpgradeTest { + using SafeERC20 for IERC20; + + UniV3StyleFacet internal uniV3StyleFacet; + + address internal constant USDC_E_WXDC_POOL = + 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; + + /// @dev our two tokens: USDC.e and wrapped XDC + IERC20 internal constant USDC_E = + IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); + IERC20 internal constant WXDC = + IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); + + function setUp() public override { + setupXDC(); + super.setUp(); + } + + function _addDexFacet() internal override { + uniV3StyleFacet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; + functionSelectors[1] = uniV3StyleFacet.xswapCallback.selector; + addFacet( + address(ldaDiamond), + address(uniV3StyleFacet), + functionSelectors + ); + + uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e6; + deal(address(USDC_E), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + USDC_E.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: USDC_E_WXDC_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDC_E), + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = USDC_E.balanceOf(USER_SENDER); + uint256 outBefore = WXDC.balanceOf(USER_SENDER); + + // Execute swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(USDC_E), + amountIn, + address(WXDC), + 0, + USER_SENDER, + route + ); + + // Verify balances after swap + uint256 inAfter = USDC_E.balanceOf(USER_SENDER); + uint256 outAfter = WXDC.balanceOf(USER_SENDER); + assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + + vm.stopPrank(); + } + + /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 5_000 * 1e6; + + // fund the aggregator + deal(address(USDC_E), address(uniV3StyleFacet), amountIn); + + vm.startPrank(USER_SENDER); + + // Account for slot-undrain protection + uint256 swapAmount = amountIn - 1; + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: USDC_E_WXDC_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(USDC_E), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 outBefore = WXDC.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(USDC_E), + swapAmount, + address(WXDC), + 0, + USER_SENDER, + route + ); + + // Verify balances after swap + uint256 outAfter = WXDC.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. + // XSwap V3 does not support a "one-pool" second hop today, because + // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. XSwap V3's swap() immediately reverts on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } + + struct UniV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildUniV3SwapData( + UniV3SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + uniV3StyleFacet.swapUniV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); + } +} + +// ----------------------------------------------------------------------------- +// RabbitSwap on Viction +// ----------------------------------------------------------------------------- +contract LiFiDexAggregatorRabbitSwapUpgradeTest is + LiFiDexAggregatorUpgradeTest +{ + using SafeERC20 for IERC20; + + UniV3StyleFacet internal uniV3StyleFacet; + + // Constants for RabbitSwap on Viction + IERC20 internal constant SOROS = + IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); + IERC20 internal constant C98 = + IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); + address internal constant SOROS_C98_POOL = + 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; + + function setUp() public override { + setupViction(); + super.setUp(); + } + + function _addDexFacet() internal override { + uniV3StyleFacet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; + functionSelectors[1] = uniV3StyleFacet + .rabbitSwapV3SwapCallback + .selector; + addFacet( + address(ldaDiamond), + address(uniV3StyleFacet), + functionSelectors + ); + + uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e18; + + // fund the user with SOROS + deal(address(SOROS), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + SOROS.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: SOROS_C98_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(SOROS), + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // record balances before swap + uint256 inBefore = SOROS.balanceOf(USER_SENDER); + uint256 outBefore = C98.balanceOf(USER_SENDER); + + // execute swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(SOROS), + amountIn, + address(C98), + 0, + USER_SENDER, + route + ); + + // verify balances after swap + uint256 inAfter = SOROS.balanceOf(USER_SENDER); + uint256 outAfter = C98.balanceOf(USER_SENDER); + assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive C98"); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 1_000 * 1e18; + + // fund the aggregator directly + deal(address(SOROS), address(uniV3StyleFacet), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: SOROS_C98_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(SOROS), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + uint256 outBefore = C98.balanceOf(USER_SENDER); + + // withdraw 1 wei less to avoid slot-undrain protection + coreRouteFacet.processRoute( + address(SOROS), + amountIn - 1, + address(C98), + 0, + USER_SENDER, + route + ); + + uint256 outAfter = C98.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive C98"); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. + // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, + // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. UniV3-style pools immediately revert on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } + + function testRevert_RabbitSwapInvalidPool() public { + uint256 amountIn = 1_000 * 1e18; + deal(address(SOROS), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + SOROS.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: address(0), + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(SOROS), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(SOROS), + amountIn, + address(C98), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + } + + function testRevert_RabbitSwapInvalidRecipient() public { + uint256 amountIn = 1_000 * 1e18; + deal(address(SOROS), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + SOROS.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: SOROS_C98_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: address(0) + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(SOROS), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(SOROS), + amountIn, + address(C98), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + } + + struct UniV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildUniV3SwapData( + UniV3SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + uniV3StyleFacet.swapUniV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); + } +} + +// ---------------------------------------------- +// EnosysDexV3 on Flare +// ---------------------------------------------- +contract LiFiDexAggregatorEnosysDexV3UpgradeTest is + LiFiDexAggregatorUpgradeTest +{ + using SafeERC20 for IERC20; + + UniV3StyleFacet internal uniV3StyleFacet; + + /// @dev HLN token on Flare + IERC20 internal constant HLN = + IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); + + /// @dev USDT0 token on Flare + IERC20 internal constant USDT0 = + IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); + + /// @dev The single EnosysDexV3 pool for HLN–USDT0 + address internal constant ENOSYS_V3_POOL = + 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; + + function setUp() public override { + setupFlare(); + super.setUp(); + } + + function _addDexFacet() internal override { + uniV3StyleFacet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; + functionSelectors[1] = uniV3StyleFacet + .enosysdexV3SwapCallback + .selector; + addFacet( + address(ldaDiamond), + address(uniV3StyleFacet), + functionSelectors + ); + + uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + /// @notice Single‐pool swap: USER sends HLN → receives USDT0 + function test_CanSwap() public override { + // Mint 1 000 HLN to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(HLN), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + HLN.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: ENOSYS_V3_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(HLN), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = HLN.balanceOf(USER_SENDER); + uint256 outBefore = USDT0.balanceOf(USER_SENDER); + + // Execute the swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(HLN), + amountIn, + address(USDT0), + 0, + USER_SENDER, + route + ); + + // Verify that HLN was spent and some USDT0 was received + uint256 inAfter = HLN.balanceOf(USER_SENDER); + uint256 outAfter = USDT0.balanceOf(USER_SENDER); + + assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + + vm.stopPrank(); + } + + /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 + function test_CanSwap_FromDexAggregator() public override { + // Fund the aggregator with 1 000 HLN + uint256 amountIn = 1_000 * 1e18; + deal(address(HLN), address(coreRouteFacet), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: ENOSYS_V3_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), // aggregator's funds + address(HLN), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Subtract 1 to protect against slot‐undrain + uint256 swapAmount = amountIn - 1; + uint256 outBefore = USDT0.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(HLN), + swapAmount, + address(USDT0), + 0, + USER_SENDER, + route + ); + + // Verify that some USDT0 was received + uint256 outAfter = USDT0.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. + // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, + // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. UniV3-style pools immediately revert on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } + + struct UniV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildUniV3SwapData( + UniV3SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + uniV3StyleFacet.swapUniV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); + } +} + +// ---------------------------------------------- +// SyncSwapV2 on Linea +// ---------------------------------------------- +contract LiFiDexAggregatorSyncSwapV2UpgradeTest is + LiFiDexAggregatorUpgradeTest +{ + using SafeERC20 for IERC20; + + SyncSwapV2Facet internal syncSwapV2Facet; + + IERC20 internal constant USDC = + IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); + IERC20 internal constant WETH = + IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); + address internal constant USDC_WETH_POOL_V1 = + address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); + address internal constant SYNC_SWAP_VAULT = + address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); + + address internal constant USDC_WETH_POOL_V2 = + address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); + + IERC20 internal constant USDT = + IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); + address internal constant USDC_USDT_POOL_V1 = + address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); + + function setUp() public override { + setupLinea(); + super.setUp(); + } + + function _addDexFacet() internal override { + syncSwapV2Facet = new SyncSwapV2Facet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = syncSwapV2Facet.swapSyncSwapV2.selector; + addFacet( + address(ldaDiamond), + address(syncSwapV2Facet), + functionSelectors + ); + + syncSwapV2Facet = SyncSwapV2Facet(payable(address(ldaDiamond))); + } + + /// @notice Single‐pool swap: USER sends WETH → receives USDC + function test_CanSwap() public override { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = WETH.balanceOf(USER_SENDER); + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + // Execute the swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that WETH was spent and some USDC_C was received + uint256 inAfter = WETH.balanceOf(USER_SENDER); + uint256 outAfter = USDC.balanceOf(USER_SENDER); + + assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_PoolV2() public { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V2, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 0, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = WETH.balanceOf(USER_SENDER); + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + // Execute the swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that WETH was spent and some USDC_C was received + uint256 inAfter = WETH.balanceOf(USER_SENDER); + uint256 outAfter = USDC.balanceOf(USER_SENDER); + + assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + // Fund the aggregator with 1 000 WETH + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), // aggregator's funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Subtract 1 to protect against slot‐undrain + uint256 swapAmount = amountIn - 1; + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(WETH), + swapAmount, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that some USDC was received + uint256 outAfter = USDC.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator_PoolV2() public { + // Fund the aggregator with 1 000 WETH + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V2, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 0, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), // aggregator's funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Subtract 1 to protect against slot‐undrain + uint256 swapAmount = amountIn - 1; + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(WETH), + swapAmount, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that some USDC was received + uint256 outAfter = USDC.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + uint256 amountIn = 1_000e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); + uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); + + // + // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) + // + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: SYNC_SWAP_VAULT, + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeHop1 = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // + // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 + // + bytes memory swapDataHop2 = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_USDT_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeHop2 = abi.encodePacked( + uint8(CommandType.ProcessOnePool), + address(USDC), + uint16(swapDataHop2.length), // length prefix + swapDataHop2 + ); + + bytes memory route = bytes.concat(routeHop1, routeHop2); + + uint256 amountOut = coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDT), + 0, + USER_SENDER, + route + ); + + uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); + uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); + + assertEq( + initialBalanceIn - afterBalanceIn, + amountIn, + "WETH spent mismatch" + ); + assertEq( + amountOut, + afterBalanceOut - initialBalanceOut, + "USDT amountOut mismatch" + ); + vm.stopPrank(); + } + + function testRevert_V1PoolMissingVaultAddress() public { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: address(0) + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + } + + function testRevert_InvalidPoolOrRecipient() public { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: address(0), + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeWithInvalidPool = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + routeWithInvalidPool + ); + + bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(0), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeWithInvalidRecipient = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapDataWithInvalidRecipient.length), // length prefix + swapDataWithInvalidRecipient + ); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + routeWithInvalidRecipient + ); + + vm.stopPrank(); + } + + function testRevert_InvalidWithdrawMode() public { + vm.startPrank(USER_SENDER); + + bytes + memory swapDataWithInvalidWithdrawMode = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 3, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapDataWithInvalidWithdrawMode.length), // length prefix + swapDataWithInvalidWithdrawMode + ); + + // Expect revert with InvalidCallData because withdrawMode is invalid + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + 1, + address(USDC), + 0, + USER_SENDER, + routeWithInvalidWithdrawMode + ); + + vm.stopPrank(); + } + + struct SyncSwapV2SwapParams { + address pool; + address to; + uint8 withdrawMode; + uint8 isV1Pool; + address vault; + } + + function _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + syncSwapV2Facet.swapSyncSwapV2.selector, + params.pool, + params.to, + params.withdrawMode, + params.isV1Pool, + params.vault + ); + } +} From bd8320da5b925d8e2e6b11f1198864e33910b346 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 6 Aug 2025 23:58:38 +0200 Subject: [PATCH 012/220] changes --- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 2 +- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 3534 +---------------- .../Lda/BaseUniV3StyleDexFacet.t.sol | 36 + .../Periphery/Lda/Facets/EnosysDexV3.t.sol | 132 + .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 200 + .../Lda/Facets/SyncSwapV2Facet.t.sol | 493 +++ 6 files changed, 1041 insertions(+), 3356 deletions(-) create mode 100644 test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index f08f1d64d..230fa358c 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -46,7 +46,7 @@ contract UniV3StyleFacet { address from, address tokenIn, uint256 amountIn - ) external returns (uint256 amountOut) { + ) external { uint256 stream = LibInputStream2.createStream(swapData); address pool = stream.readAddress(); bool direction = stream.readUint8() > 0; diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 7f8985d29..e6a0c1e34 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -1,3358 +1,182 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -// import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -// import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -// import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -// import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; -// import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; -// import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -// import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -// import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; -// import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; -// import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -// import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -// import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; -// import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; -// import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -// import { TestBase } from "../../utils/TestBase.sol"; -// import { TestToken as ERC20 } from "../../utils/TestToken.sol"; -// import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; -// import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; - -// // Command codes for route processing -// enum CommandType { -// None, // 0 - not used -// ProcessMyERC20, // 1 - processMyERC20 -// ProcessUserERC20, // 2 - processUserERC20 -// ProcessNative, // 3 - processNative -// ProcessOnePool, // 4 - processOnePool -// ProcessInsideBento, // 5 - processInsideBento -// ApplyPermit // 6 - applyPermit -// } - -// // Pool type identifiers -// enum PoolType { -// UniV2, // 0 -// UniV3, // 1 -// WrapNative, // 2 -// BentoBridge, // 3 -// Trident, // 4 -// Curve, // 5 -// VelodromeV2, // 6 -// Algebra, // 7 -// iZiSwap, // 8 -// SyncSwapV2 // 9 -// } - -// // Direction constants -// enum SwapDirection { -// Token1ToToken0, // 0 -// Token0ToToken1 // 1 -// } - -// // Callback constants -// enum CallbackStatus { -// Disabled, // 0 -// Enabled // 1 -// } - -// // Other constants -// uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps - -// contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { -// event HookCalled( -// address sender, -// uint256 amount0, -// uint256 amount1, -// bytes data -// ); - -// function hook( -// address sender, -// uint256 amount0, -// uint256 amount1, -// bytes calldata data -// ) external { -// emit HookCalled(sender, amount0, amount1, data); -// } -// } -// /** -// * @title BaseDexFacetTest -// * @notice Base test contract with common functionality and abstractions for DEX-specific tests -// */ -// abstract contract BaseDexFacetTest is LdaDiamondTest { -// using SafeERC20 for IERC20; - -// // Common variables -// LiFiDEXAggregator internal liFiDEXAggregator; -// address[] internal privileged; - -// // Common events and errors -// event Route( -// address indexed from, -// address to, -// address indexed tokenIn, -// address indexed tokenOut, -// uint256 amountIn, -// uint256 amountOutMin, -// uint256 amountOut -// ); -// event HookCalled( -// address sender, -// uint256 amount0, -// uint256 amount1, -// bytes data -// ); - -// error WrongPoolReserves(); -// error PoolDoesNotExist(); - -// // helper function to initialize the aggregator -// function _initializeDexAggregator(address owner) internal { -// privileged = new address[](1); -// privileged[0] = owner; - -// liFiDEXAggregator = new LiFiDEXAggregator( -// address(0xCAFE), -// privileged, -// owner -// ); -// vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); -// } - -// // Setup function for Apechain tests -// function setupApechain() internal { -// customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; -// customBlockNumberForForking = 12912470; -// fork(); - -// _initializeDexAggregator(address(USER_DIAMOND_OWNER)); -// } - -// function setupHyperEVM() internal { -// customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; -// customBlockNumberForForking = 4433562; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - - -// // ============================ Abstract DEX Tests ============================ -// /** -// * @notice Abstract test for basic token swapping functionality -// * Each DEX implementation should override this -// */ -// function test_CanSwap() public virtual { -// // Each DEX implementation must override this -// // solhint-disable-next-line gas-custom-errors -// revert("test_CanSwap: Not implemented"); -// } - -// /** -// * @notice Abstract test for swapping tokens from the DEX aggregator -// * Each DEX implementation should override this -// */ -// function test_CanSwap_FromDexAggregator() public virtual { -// // Each DEX implementation must override this -// // solhint-disable-next-line gas-custom-errors -// revert("test_CanSwap_FromDexAggregator: Not implemented"); -// } - -// /** -// * @notice Abstract test for multi-hop swapping -// * Each DEX implementation should override this -// */ -// function test_CanSwap_MultiHop() public virtual { -// // Each DEX implementation must override this -// // solhint-disable-next-line gas-custom-errors -// revert("test_CanSwap_MultiHop: Not implemented"); -// } -// } - -// /** -// * @title VelodromeV2 tests -// * @notice Tests specific to Velodrome V2 pool type -// */ -// contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorTest { -// // ==================== Velodrome V2 specific variables ==================== -// IVelodromeV2Router internal constant VELODROME_V2_ROUTER = -// IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router -// address internal constant VELODROME_V2_FACTORY_REGISTRY = -// 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; -// IERC20 internal constant STG_TOKEN = -// IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); -// IERC20 internal constant USDC_E_TOKEN = -// IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); - -// MockVelodromeV2FlashLoanCallbackReceiver -// internal mockFlashloanCallbackReceiver; - -// // Velodrome V2 structs -// struct VelodromeV2SwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// bool stable; -// SwapDirection direction; -// bool callback; -// } - -// struct MultiHopTestParams { -// address tokenIn; -// address tokenMid; -// address tokenOut; -// address pool1; -// address pool2; -// uint256[] amounts1; -// uint256[] amounts2; -// uint256 pool1Fee; -// uint256 pool2Fee; -// } - -// struct ReserveState { -// uint256 reserve0Pool1; -// uint256 reserve1Pool1; -// uint256 reserve0Pool2; -// uint256 reserve1Pool2; -// } - -// // Setup function for Optimism tests -// function setupOptimism() internal { -// customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; -// customBlockNumberForForking = 133999121; -// initTestBase(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// function setUp() public override { -// setupOptimism(); -// } - -// // // ============================ Velodrome V2 Tests ============================ - -// // no stable swap -// function test_CanSwap() public override { -// vm.startPrank(USER_SENDER); - -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(USER_SENDER), -// to: address(USER_SENDER), -// tokenIn: ADDRESS_USDC, -// amountIn: 1_000 * 1e6, -// tokenOut: address(STG_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_NoStable_Reverse() public { -// // first perform the forward swap. -// test_CanSwap(); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(STG_TOKEN), -// amountIn: 500 * 1e18, -// tokenOut: ADDRESS_USDC, -// stable: false, -// direction: SwapDirection.Token1ToToken0, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_Stable() public { -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: ADDRESS_USDC, -// amountIn: 1_000 * 1e6, -// tokenOut: address(USDC_E_TOKEN), -// stable: true, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_Stable_Reverse() public { -// // first perform the forward stable swap. -// test_CanSwap_Stable(); - -// vm.startPrank(USER_SENDER); - -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(USDC_E_TOKEN), -// amountIn: 500 * 1e6, -// tokenOut: ADDRESS_USDC, -// stable: false, -// direction: SwapDirection.Token1ToToken0, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // fund dex aggregator contract so that the contract holds USDC -// deal(ADDRESS_USDC, address(liFiDEXAggregator), 100_000 * 1e6); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(liFiDEXAggregator), -// to: address(USER_SENDER), -// tokenIn: ADDRESS_USDC, -// amountIn: IERC20(ADDRESS_USDC).balanceOf( -// address(liFiDEXAggregator) -// ) - 1, // adjust for slot undrain protection: subtract 1 token so that the aggregator's balance isn't completely drained, matching the contract's safeguard -// tokenOut: address(USDC_E_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_FlashloanCallback() public { -// mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(USER_SENDER), -// to: address(mockFlashloanCallbackReceiver), -// tokenIn: ADDRESS_USDC, -// amountIn: 1_000 * 1e6, -// tokenOut: address(USDC_E_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: true -// }) -// ); -// vm.stopPrank(); -// } - -// // Override the abstract test with VelodromeV2 implementation -// function test_CanSwap_MultiHop() public override { -// vm.startPrank(USER_SENDER); - -// // Setup routes and get amounts -// MultiHopTestParams memory params = _setupRoutes( -// ADDRESS_USDC, -// address(STG_TOKEN), -// address(USDC_E_TOKEN), -// false, -// false -// ); - -// // Get initial reserves BEFORE the swap -// ReserveState memory initialReserves; -// ( -// initialReserves.reserve0Pool1, -// initialReserves.reserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// initialReserves.reserve0Pool2, -// initialReserves.reserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( -// USER_SENDER -// ); - -// // Build route and execute swap -// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); - -// // Approve and execute -// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// params.tokenIn, -// params.tokenOut, -// 1000 * 1e6, -// params.amounts2[1], -// params.amounts2[1] -// ); - -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// 1000 * 1e6, -// params.tokenOut, -// params.amounts2[1], -// USER_SENDER, -// route -// ); - -// _verifyUserBalances(params, initialBalance1, initialBalance2); -// _verifyReserves(params, initialReserves); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop_WithStable() public { -// vm.startPrank(USER_SENDER); - -// // Setup routes and get amounts for stable->volatile path -// MultiHopTestParams memory params = _setupRoutes( -// ADDRESS_USDC, -// address(USDC_E_TOKEN), -// address(STG_TOKEN), -// true, // stable pool for first hop -// false // volatile pool for second hop -// ); - -// // Get initial reserves BEFORE the swap -// ReserveState memory initialReserves; -// ( -// initialReserves.reserve0Pool1, -// initialReserves.reserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// initialReserves.reserve0Pool2, -// initialReserves.reserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// // Record initial balances -// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( -// USER_SENDER -// ); - -// // Build route and execute swap -// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - -// // Approve and execute -// IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// params.tokenIn, -// params.tokenOut, -// 1000 * 1e6, -// params.amounts2[1], -// params.amounts2[1] -// ); - -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// 1000 * 1e6, -// params.tokenOut, -// params.amounts2[1], -// USER_SENDER, -// route -// ); - -// _verifyUserBalances(params, initialBalance1, initialBalance2); -// _verifyReserves(params, initialReserves); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidPoolOrRecipient() public { -// vm.startPrank(USER_SENDER); - -// // Get a valid pool address first for comparison -// address validPool = VELODROME_V2_ROUTER.poolFor( -// ADDRESS_USDC, -// address(STG_TOKEN), -// false, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// // Test case 1: Zero pool address -// bytes memory routeWithZeroPool = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// ADDRESS_USDC, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// address(0), -// uint8(SwapDirection.Token1ToToken0), -// USER_SENDER, -// uint8(CallbackStatus.Disabled) -// ); - -// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// ADDRESS_USDC, -// 1000 * 1e6, -// address(STG_TOKEN), -// 0, -// USER_SENDER, -// routeWithZeroPool -// ); - -// // Test case 2: Zero recipient address -// bytes memory routeWithZeroRecipient = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// ADDRESS_USDC, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// validPool, -// uint8(SwapDirection.Token1ToToken0), -// address(0), -// uint8(CallbackStatus.Disabled) -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// ADDRESS_USDC, -// 1000 * 1e6, -// address(STG_TOKEN), -// 0, -// USER_SENDER, -// routeWithZeroRecipient -// ); - -// vm.stopPrank(); -// } - -// function testRevert_WrongPoolReserves() public { -// vm.startPrank(USER_SENDER); - -// // Setup multi-hop route: USDC -> STG -> USDC.e -// MultiHopTestParams memory params = _setupRoutes( -// ADDRESS_USDC, -// address(STG_TOKEN), -// address(USDC_E_TOKEN), -// false, -// false -// ); - -// // Build multi-hop route -// bytes memory firstHop = _buildFirstHop( -// params.tokenIn, -// params.pool1, -// params.pool2, -// 1 // direction -// ); - -// bytes memory secondHop = _buildSecondHop( -// params.tokenMid, -// params.pool2, -// USER_SENDER, -// 0 // direction -// ); - -// bytes memory route = bytes.concat(firstHop, secondHop); - -// deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); - -// IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); - -// // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves -// vm.mockCall( -// params.pool2, -// abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), -// abi.encode(0, 0, block.timestamp) -// ); - -// vm.expectRevert(WrongPoolReserves.selector); - -// liFiDEXAggregator.processRoute( -// ADDRESS_USDC, -// 1000 * 1e6, -// address(USDC_E_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// // ============================ Velodrome V2 Helper Functions ============================ - -// /** -// * @dev Helper function to test a VelodromeV2 swap. -// * Uses a struct to group parameters and reduce stack depth. -// */ -// function _testSwap(VelodromeV2SwapTestParams memory params) internal { -// // get expected output amounts from the router. -// IVelodromeV2Router.Route[] -// memory routes = new IVelodromeV2Router.Route[](1); -// routes[0] = IVelodromeV2Router.Route({ -// from: params.tokenIn, -// to: params.tokenOut, -// stable: params.stable, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( -// params.amountIn, -// routes -// ); -// emit log_named_uint("Expected amount out", amounts[1]); - -// // Retrieve the pool address. -// address pool = VELODROME_V2_ROUTER.poolFor( -// params.tokenIn, -// params.tokenOut, -// params.stable, -// VELODROME_V2_FACTORY_REGISTRY -// ); -// emit log_named_uint("Pool address:", uint256(uint160(pool))); - -// // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. -// CommandType commandCode = params.from == address(liFiDEXAggregator) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; - -// // build the route. -// bytes memory route = abi.encodePacked( -// uint8(commandCode), -// params.tokenIn, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// pool, -// params.direction, -// params.to, -// params.callback -// ? uint8(CallbackStatus.Enabled) -// : uint8(CallbackStatus.Disabled) -// ); - -// // approve the aggregator to spend tokenIn. -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); - -// // capture initial token balances. -// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); -// emit log_named_uint("Initial tokenIn balance", initialTokenIn); - -// address from = params.from == address(liFiDEXAggregator) -// ? USER_SENDER -// : params.from; -// if (params.callback == true) { -// vm.expectEmit(true, false, false, false); -// emit HookCalled( -// address(liFiDEXAggregator), -// 0, -// 0, -// abi.encode(params.tokenIn) -// ); -// } -// vm.expectEmit(true, true, true, true); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// amounts[1], -// amounts[1] -// ); - -// // execute the swap -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// amounts[1], -// params.to, -// route -// ); - -// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); -// emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); -// emit log_named_uint( -// "TokenOut received", -// finalTokenOut - initialTokenOut -// ); - -// assertApproxEqAbs( -// initialTokenIn - finalTokenIn, -// params.amountIn, -// 1, // 1 wei tolerance -// "TokenIn amount mismatch" -// ); -// assertEq( -// finalTokenOut - initialTokenOut, -// amounts[1], -// "TokenOut amount mismatch" -// ); -// } - -// // Helper function to set up routes and get amounts -// function _setupRoutes( -// address tokenIn, -// address tokenMid, -// address tokenOut, -// bool isStableFirst, -// bool isStableSecond -// ) private view returns (MultiHopTestParams memory params) { -// params.tokenIn = tokenIn; -// params.tokenMid = tokenMid; -// params.tokenOut = tokenOut; - -// // Setup first hop route -// IVelodromeV2Router.Route[] -// memory routes1 = new IVelodromeV2Router.Route[](1); -// routes1[0] = IVelodromeV2Router.Route({ -// from: tokenIn, -// to: tokenMid, -// stable: isStableFirst, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( -// 1000 * 1e6, -// routes1 -// ); - -// // Setup second hop route -// IVelodromeV2Router.Route[] -// memory routes2 = new IVelodromeV2Router.Route[](1); -// routes2[0] = IVelodromeV2Router.Route({ -// from: tokenMid, -// to: tokenOut, -// stable: isStableSecond, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( -// params.amounts1[1], -// routes2 -// ); - -// // Get pool addresses -// params.pool1 = VELODROME_V2_ROUTER.poolFor( -// tokenIn, -// tokenMid, -// isStableFirst, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// params.pool2 = VELODROME_V2_ROUTER.poolFor( -// tokenMid, -// tokenOut, -// isStableSecond, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// // Get pool fees info -// params.pool1Fee = IVelodromeV2PoolFactory( -// VELODROME_V2_FACTORY_REGISTRY -// ).getFee(params.pool1, isStableFirst); -// params.pool2Fee = IVelodromeV2PoolFactory( -// VELODROME_V2_FACTORY_REGISTRY -// ).getFee(params.pool2, isStableSecond); - -// return params; -// } - -// // function to build first hop of the route -// function _buildFirstHop( -// address tokenIn, -// address pool1, -// address pool2, -// uint8 direction -// ) private pure returns (bytes memory) { -// return -// abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// tokenIn, -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.VelodromeV2), -// pool1, -// direction, -// pool2, -// uint8(CallbackStatus.Disabled) -// ); -// } - -// // function to build second hop of the route -// function _buildSecondHop( -// address tokenMid, -// address pool2, -// address recipient, -// uint8 direction -// ) private pure returns (bytes memory) { -// return -// abi.encodePacked( -// uint8(CommandType.ProcessOnePool), -// tokenMid, -// uint8(PoolType.VelodromeV2), -// pool2, -// direction, -// recipient, -// uint8(CallbackStatus.Disabled) -// ); -// } - -// // route building function -// function _buildMultiHopRoute( -// MultiHopTestParams memory params, -// address recipient, -// uint8 firstHopDirection, -// uint8 secondHopDirection -// ) private pure returns (bytes memory) { -// bytes memory firstHop = _buildFirstHop( -// params.tokenIn, -// params.pool1, -// params.pool2, -// firstHopDirection -// ); - -// bytes memory secondHop = _buildSecondHop( -// params.tokenMid, -// params.pool2, -// recipient, -// secondHopDirection -// ); - -// return bytes.concat(firstHop, secondHop); -// } - -// function _verifyUserBalances( -// MultiHopTestParams memory params, -// uint256 initialBalance1, -// uint256 initialBalance2 -// ) private { -// // Verify token balances -// uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// assertApproxEqAbs( -// initialBalance1 - finalBalance1, -// 1000 * 1e6, -// 1, // 1 wei tolerance -// "Token1 spent amount mismatch" -// ); -// assertEq( -// finalBalance2 - initialBalance2, -// params.amounts2[1], -// "Token2 received amount mismatch" -// ); -// } - -// function _verifyReserves( -// MultiHopTestParams memory params, -// ReserveState memory initialReserves -// ) private { -// // Get reserves after swap -// ( -// uint256 finalReserve0Pool1, -// uint256 finalReserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// uint256 finalReserve0Pool2, -// uint256 finalReserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); -// address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); - -// // Calculate exact expected changes -// uint256 amountInAfterFees = 1000 * -// 1e6 - -// ((1000 * 1e6 * params.pool1Fee) / 10000); - -// // Assert exact reserve changes for Pool1 -// if (token0Pool1 == params.tokenIn) { -// // tokenIn is token0, so reserve0 should increase and reserve1 should decrease -// assertEq( -// finalReserve0Pool1 - initialReserves.reserve0Pool1, -// amountInAfterFees, -// "Pool1 reserve0 (tokenIn) change incorrect" -// ); -// assertEq( -// initialReserves.reserve1Pool1 - finalReserve1Pool1, -// params.amounts1[1], -// "Pool1 reserve1 (tokenMid) change incorrect" -// ); -// } else { -// // tokenIn is token1, so reserve1 should increase and reserve0 should decrease -// assertEq( -// finalReserve1Pool1 - initialReserves.reserve1Pool1, -// amountInAfterFees, -// "Pool1 reserve1 (tokenIn) change incorrect" -// ); -// assertEq( -// initialReserves.reserve0Pool1 - finalReserve0Pool1, -// params.amounts1[1], -// "Pool1 reserve0 (tokenMid) change incorrect" -// ); -// } - -// // Assert exact reserve changes for Pool2 -// if (token0Pool2 == params.tokenMid) { -// // tokenMid is token0, so reserve0 should increase and reserve1 should decrease -// assertEq( -// finalReserve0Pool2 - initialReserves.reserve0Pool2, -// params.amounts1[1] - -// ((params.amounts1[1] * params.pool2Fee) / 10000), -// "Pool2 reserve0 (tokenMid) change incorrect" -// ); -// assertEq( -// initialReserves.reserve1Pool2 - finalReserve1Pool2, -// params.amounts2[1], -// "Pool2 reserve1 (tokenOut) change incorrect" -// ); -// } else { -// // tokenMid is token1, so reserve1 should increase and reserve0 should decrease -// assertEq( -// finalReserve1Pool2 - initialReserves.reserve1Pool2, -// params.amounts1[1] - -// ((params.amounts1[1] * params.pool2Fee) / 10000), -// "Pool2 reserve1 (tokenMid) change incorrect" -// ); -// assertEq( -// initialReserves.reserve0Pool2 - finalReserve0Pool2, -// params.amounts2[1], -// "Pool2 reserve0 (tokenOut) change incorrect" -// ); -// } -// } -// } - -// contract AlgebraLiquidityAdderHelper { -// address public immutable TOKEN_0; -// address public immutable TOKEN_1; - -// constructor(address _token0, address _token1) { -// TOKEN_0 = _token0; -// TOKEN_1 = _token1; -// } - -// function addLiquidity( -// address pool, -// int24 bottomTick, -// int24 topTick, -// uint128 amount -// ) -// external -// returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) -// { -// // Get balances before -// uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Call mint -// (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( -// address(this), -// address(this), -// bottomTick, -// topTick, -// amount, -// abi.encode(TOKEN_0, TOKEN_1) -// ); - -// // Get balances after to account for fees -// uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Calculate actual amounts transferred accounting for fees -// amount0 = balance0Before - balance0After; -// amount1 = balance1Before - balance1After; - -// return (amount0, amount1, liquidityActual); -// } - -// function algebraMintCallback( -// uint256 amount0Owed, -// uint256 amount1Owed, -// bytes calldata -// ) external { -// // Check token balances -// uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Transfer what we can, limited by actual balance -// if (amount0Owed > 0) { -// uint256 amount0ToSend = amount0Owed > balance0 -// ? balance0 -// : amount0Owed; -// uint256 balance0Before = IERC20(TOKEN_0).balanceOf( -// address(msg.sender) -// ); -// IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); -// uint256 balance0After = IERC20(TOKEN_0).balanceOf( -// address(msg.sender) -// ); -// // solhint-disable-next-line gas-custom-errors -// require(balance0After > balance0Before, "Transfer failed"); -// } - -// if (amount1Owed > 0) { -// uint256 amount1ToSend = amount1Owed > balance1 -// ? balance1 -// : amount1Owed; -// uint256 balance1Before = IERC20(TOKEN_1).balanceOf( -// address(msg.sender) -// ); -// IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); -// uint256 balance1After = IERC20(TOKEN_1).balanceOf( -// address(msg.sender) -// ); -// // solhint-disable-next-line gas-custom-errors -// require(balance1After > balance1Before, "Transfer failed"); -// } -// } -// } - -// /** -// * @title Algebra tests -// * @notice Tests specific to Algebra pool type -// */ -// contract LiFiDexAggregatorAlgebraTest is LiFiDexAggregatorTest { -// address private constant APE_ETH_TOKEN = -// 0xcF800F4948D16F23333508191B1B1591daF70438; -// address private constant WETH_TOKEN = -// 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; -// address private constant ALGEBRA_FACTORY_APECHAIN = -// 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; -// address private constant ALGEBRA_QUOTER_V2_APECHAIN = -// 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; -// address private constant ALGEBRA_POOL_APECHAIN = -// 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; -// address private constant APE_ETH_HOLDER_APECHAIN = -// address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); - -// address private constant IMPOSSIBLE_POOL_ADDRESS = -// 0x0000000000000000000000000000000000000001; - -// struct AlgebraSwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// SwapDirection direction; -// bool supportsFeeOnTransfer; -// } - -// error AlgebraSwapUnexpected(); - -// function setUp() public override { -// setupApechain(); -// } - -// // Override the abstract test with Algebra implementation -// function test_CanSwap_FromDexAggregator() public override { -// // Fund LDA from whale address -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(address(liFiDEXAggregator), 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: address(liFiDEXAggregator), -// to: address(USER_SENDER), -// tokenIn: APE_ETH_TOKEN, -// amountIn: IERC20(APE_ETH_TOKEN).balanceOf( -// address(liFiDEXAggregator) -// ) - 1, -// tokenOut: address(WETH_TOKEN), -// direction: SwapDirection.Token0ToToken1, -// supportsFeeOnTransfer: true -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FeeOnTransferToken() public { -// setupApechain(); - -// uint256 amountIn = 534451326669177; -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); - -// vm.startPrank(APE_ETH_HOLDER_APECHAIN); - -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), amountIn); - -// // Build route for algebra swap with command code 2 (user funds) -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: APE_ETH_HOLDER_APECHAIN, -// pool: ALGEBRA_POOL_APECHAIN, -// supportsFeeOnTransfer: true -// }) -// ); - -// // Track initial balance -// uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( -// APE_ETH_HOLDER_APECHAIN -// ); - -// // Execute the swap -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// amountIn, -// WETH_TOKEN, -// 0, // minOut = 0 for this test -// APE_ETH_HOLDER_APECHAIN, -// route -// ); - -// // Verify balances -// uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( -// APE_ETH_HOLDER_APECHAIN -// ); -// assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); - -// vm.stopPrank(); -// } - -// function test_CanSwap() public override { -// vm.startPrank(APE_ETH_HOLDER_APECHAIN); - -// // Transfer tokens from whale to USER_SENDER -// uint256 amountToTransfer = 100 * 1e18; -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); - -// vm.stopPrank(); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: APE_ETH_TOKEN, -// amountIn: 10 * 1e18, -// tokenOut: address(WETH_TOKEN), -// direction: SwapDirection.Token0ToToken1, -// supportsFeeOnTransfer: true -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_Reverse() public { -// test_CanSwap(); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(WETH_TOKEN), -// amountIn: 5 * 1e18, -// tokenOut: APE_ETH_TOKEN, -// direction: SwapDirection.Token1ToToken0, -// supportsFeeOnTransfer: false -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { -// MultiHopTestState memory state; -// state.isFeeOnTransfer = true; - -// // Setup tokens and pools -// state = _setupTokensAndPools(state); - -// // Execute and verify swap -// _executeAndVerifyMultiHopSwap(state); -// } - -// function test_CanSwap_MultiHop() public override { -// MultiHopTestState memory state; -// state.isFeeOnTransfer = false; - -// // Setup tokens and pools -// state = _setupTokensAndPools(state); - -// // Execute and verify swap -// _executeAndVerifyMultiHopSwap(state); -// } - -// // Test that the proper error is thrown when algebra swap fails -// function testRevert_SwapUnexpected() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Create invalid pool address -// address invalidPool = address(0x999); - -// // Mock token0() call on invalid pool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Create a route with an invalid pool -// bytes memory invalidRoute = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: invalidPool, -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Mock the algebra pool to not reset lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSelector( -// IAlgebraPool.swapSupportingFeeOnInputTokens.selector -// ), -// abi.encode(0, 0) -// ); - -// // Expect the AlgebraSwapUnexpected error -// vm.expectRevert(AlgebraSwapUnexpected.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// invalidRoute -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// // Helper function to setup tokens and pools -// function _setupTokensAndPools( -// MultiHopTestState memory state -// ) private returns (MultiHopTestState memory) { -// // Create tokens -// ERC20 tokenA = new ERC20( -// "Token A", -// state.isFeeOnTransfer ? "FTA" : "TA", -// 18 -// ); -// IERC20 tokenB; -// ERC20 tokenC = new ERC20( -// "Token C", -// state.isFeeOnTransfer ? "FTC" : "TC", -// 18 -// ); - -// if (state.isFeeOnTransfer) { -// tokenB = IERC20( -// address( -// new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) -// ) -// ); -// } else { -// tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); -// } - -// state.tokenA = IERC20(address(tokenA)); -// state.tokenB = tokenB; -// state.tokenC = IERC20(address(tokenC)); - -// // Label addresses -// vm.label(address(state.tokenA), "Token A"); -// vm.label(address(state.tokenB), "Token B"); -// vm.label(address(state.tokenC), "Token C"); - -// // Mint initial token supplies -// tokenA.mint(address(this), 1_000_000 * 1e18); -// if (!state.isFeeOnTransfer) { -// ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); -// } else { -// MockFeeOnTransferToken(address(tokenB)).mint( -// address(this), -// 1_000_000 * 1e18 -// ); -// } -// tokenC.mint(address(this), 1_000_000 * 1e18); - -// // Create pools -// state.pool1 = _createAlgebraPool( -// address(state.tokenA), -// address(state.tokenB) -// ); -// state.pool2 = _createAlgebraPool( -// address(state.tokenB), -// address(state.tokenC) -// ); - -// vm.label(state.pool1, "Pool 1"); -// vm.label(state.pool2, "Pool 2"); - -// // Add liquidity -// _addLiquidityToPool( -// state.pool1, -// address(state.tokenA), -// address(state.tokenB) -// ); -// _addLiquidityToPool( -// state.pool2, -// address(state.tokenB), -// address(state.tokenC) -// ); - -// state.amountToTransfer = 100 * 1e18; -// state.amountIn = 50 * 1e18; - -// // Transfer tokens to USER_SENDER -// IERC20(address(state.tokenA)).transfer( -// USER_SENDER, -// state.amountToTransfer -// ); - -// return state; -// } - -// // Helper function to execute and verify the swap -// function _executeAndVerifyMultiHopSwap( -// MultiHopTestState memory state -// ) private { -// vm.startPrank(USER_SENDER); - -// uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( -// USER_SENDER -// ); - -// // Approve spending -// IERC20(address(state.tokenA)).approve( -// address(liFiDEXAggregator), -// state.amountIn -// ); - -// // Build route -// bytes memory route = _buildMultiHopRouteForTest(state); - -// // Execute swap -// liFiDEXAggregator.processRoute( -// address(state.tokenA), -// state.amountIn, -// address(state.tokenC), -// 0, // No minimum amount out for testing -// USER_SENDER, -// route -// ); - -// // Verify results -// _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); - -// vm.stopPrank(); -// } - -// // Helper function to build the multi-hop route for test -// function _buildMultiHopRouteForTest( -// MultiHopTestState memory state -// ) private view returns (bytes memory) { -// bytes memory firstHop = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: address(state.tokenA), -// recipient: address(liFiDEXAggregator), -// pool: state.pool1, -// supportsFeeOnTransfer: false -// }) -// ); - -// bytes memory secondHop = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessMyERC20, -// tokenIn: address(state.tokenB), -// recipient: USER_SENDER, -// pool: state.pool2, -// supportsFeeOnTransfer: state.isFeeOnTransfer -// }) -// ); - -// return bytes.concat(firstHop, secondHop); -// } - -// // Helper function to verify multi-hop results -// function _verifyMultiHopResults( -// MultiHopTestState memory state, -// uint256 initialBalanceA, -// uint256 initialBalanceC -// ) private { -// uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( -// USER_SENDER -// ); -// uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( -// USER_SENDER -// ); - -// assertApproxEqAbs( -// initialBalanceA - finalBalanceA, -// state.amountIn, -// 1, // 1 wei tolerance -// "TokenA spent amount mismatch" -// ); -// assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); - -// emit log_named_uint( -// state.isFeeOnTransfer -// ? "Output amount with fee tokens" -// : "Output amount with regular tokens", -// finalBalanceC - initialBalanceC -// ); -// } - -// // Helper function to create an Algebra pool -// function _createAlgebraPool( -// address tokenA, -// address tokenB -// ) internal returns (address pool) { -// // Call the actual Algebra factory to create a pool -// pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( -// tokenA, -// tokenB -// ); -// return pool; -// } - -// // Helper function to add liquidity to a pool -// function _addLiquidityToPool( -// address pool, -// address token0, -// address token1 -// ) internal { -// // For fee-on-transfer tokens, we need to send more to account for the fee -// // We'll use a small amount and send extra to cover fees -// uint256 initialAmount0 = 1e17; // 0.1 token -// uint256 initialAmount1 = 1e17; // 0.1 token - -// // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) -// uint256 transferAmount0 = (initialAmount0 * 110) / 100; -// uint256 transferAmount1 = (initialAmount1 * 110) / 100; - -// // Initialize with 1:1 price ratio (Q64.96 format) -// uint160 initialPrice = uint160(1 << 96); -// IAlgebraPool(pool).initialize(initialPrice); - -// // Create AlgebraLiquidityAdderHelper with safe transfer logic -// AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( -// token0, -// token1 -// ); - -// // Transfer tokens with extra amounts to account for fees -// IERC20(token0).transfer( -// address(algebraLiquidityAdderHelper), -// transferAmount0 -// ); -// IERC20(token1).transfer( -// address(algebraLiquidityAdderHelper), -// transferAmount1 -// ); - -// // Get actual balances to use for liquidity, accounting for any fees -// uint256 actualBalance0 = IERC20(token0).balanceOf( -// address(algebraLiquidityAdderHelper) -// ); -// uint256 actualBalance1 = IERC20(token1).balanceOf( -// address(algebraLiquidityAdderHelper) -// ); - -// // Use the smaller of the two balances for liquidity amount -// uint128 liquidityAmount = uint128( -// actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 -// ); - -// // Add liquidity using the actual token amounts we have -// algebraLiquidityAdderHelper.addLiquidity( -// pool, -// -887220, -// 887220, -// liquidityAmount / 2 // Use half of available liquidity to ensure success -// ); -// } - -// struct MultiHopTestState { -// IERC20 tokenA; -// IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken -// IERC20 tokenC; -// address pool1; -// address pool2; -// uint256 amountIn; -// uint256 amountToTransfer; -// bool isFeeOnTransfer; -// } - -// struct AlgebraRouteParams { -// CommandType commandCode; // 1 for contract funds, 2 for user funds -// address tokenIn; // Input token address -// address recipient; // Address receiving the output tokens -// address pool; // Algebra pool address -// bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens -// } - -// // Helper function to build route for Apechain Algebra swap -// function _buildAlgebraRoute( -// AlgebraRouteParams memory params -// ) internal view returns (bytes memory route) { -// address token0 = IAlgebraPool(params.pool).token0(); -// bool zeroForOne = (params.tokenIn == token0); -// SwapDirection direction = zeroForOne -// ? SwapDirection.Token0ToToken1 -// : SwapDirection.Token1ToToken0; - -// route = abi.encodePacked( -// params.commandCode, -// params.tokenIn, -// uint8(1), // one pool -// FULL_SHARE, // 100% share -// uint8(PoolType.Algebra), -// params.pool, -// uint8(direction), -// params.recipient, -// params.supportsFeeOnTransfer ? uint8(1) : uint8(0) -// ); - -// return route; -// } - -// // Helper function to test an Algebra swap -// function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { -// // Find or create a pool -// address pool = _getPool(params.tokenIn, params.tokenOut); - -// vm.label(pool, "AlgebraPool"); - -// // Get token0 from pool and label tokens accordingly -// address token0 = IAlgebraPool(pool).token0(); -// if (params.tokenIn == token0) { -// vm.label( -// params.tokenIn, -// string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") -// ); -// vm.label( -// params.tokenOut, -// string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") -// ); -// } else { -// vm.label( -// params.tokenIn, -// string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") -// ); -// vm.label( -// params.tokenOut, -// string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") -// ); -// } - -// // Record initial balances -// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - -// // Get expected output from QuoterV2 -// // NOTE: There may be a small discrepancy between the quoted amount and the actual output -// // because the Quoter uses the regular swap() function for simulation while the actual -// // execution may use swapSupportingFeeOnInputTokens() for fee-on-transfer tokens. -// // The Quoter cannot accurately predict transfer fees taken by the token contract itself, -// // resulting in minor "dust" differences that are normal and expected when dealing with -// // non-standard token implementations. -// uint256 expectedOutput = _getQuoteExactInput( -// params.tokenIn, -// params.tokenOut, -// params.amountIn -// ); - -// // Build the route -// CommandType commandCode = params.from == address(liFiDEXAggregator) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: commandCode, -// tokenIn: params.tokenIn, -// recipient: params.to, -// pool: pool, -// supportsFeeOnTransfer: params.supportsFeeOnTransfer -// }) -// ); - -// // Approve tokens -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); - -// // Execute the swap -// address from = params.from == address(liFiDEXAggregator) -// ? USER_SENDER -// : params.from; - -// vm.expectEmit(true, true, true, false); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// expectedOutput, -// expectedOutput -// ); - -// uint256 minOut = (expectedOutput * 995) / 1000; // 0.5% slippage - -// liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// minOut, -// params.to, -// route -// ); - -// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - -// assertApproxEqAbs( -// initialTokenIn - finalTokenIn, -// params.amountIn, -// 1, // 1 wei tolerance -// "TokenIn amount mismatch" -// ); -// assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); -// } - -// function _getPool( -// address tokenA, -// address tokenB -// ) private view returns (address pool) { -// pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( -// tokenA, -// tokenB -// ); -// if (pool == address(0)) revert PoolDoesNotExist(); -// return pool; -// } - -// function _getQuoteExactInput( -// address tokenIn, -// address tokenOut, -// uint256 amountIn -// ) private returns (uint256 amountOut) { -// (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) -// .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); -// return amountOut; -// } - -// function testRevert_AlgebraSwap_ZeroAddressPool() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on address(0) -// vm.mockCall( -// address(0), -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with address(0) as pool -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: address(0), // Zero address pool -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS -// vm.mockCall( -// IMPOSSIBLE_POOL_ADDRESS, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with IMPOSSIBLE_POOL_ADDRESS as pool -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// function testRevert_AlgebraSwap_ZeroAddressRecipient() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on the pool -// vm.mockCall( -// ALGEBRA_POOL_APECHAIN, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with address(0) as recipient -// bytes memory route = _buildAlgebraRoute( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: address(0), // Zero address recipient -// pool: ALGEBRA_POOL_APECHAIN, -// supportsFeeOnTransfer: true -// }) -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// liFiDEXAggregator.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } -// } - -// /** -// * @title LiFiDexAggregatorIzumiV3Test -// * @notice Tests specific to iZiSwap V3 pool type -// */ -// contract LiFiDexAggregatorIzumiV3Test is LiFiDexAggregatorTest { -// // ==================== iZiSwap V3 specific variables ==================== -// // Base constants -// address internal constant USDC = -// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; -// address internal constant WETH = -// 0x4200000000000000000000000000000000000006; -// address internal constant USDB_C = -// 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; - -// // iZiSwap pools -// address internal constant IZUMI_WETH_USDC_POOL = -// 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; -// address internal constant IZUMI_WETH_USDB_C_POOL = -// 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; - -// // Test parameters -// uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals -// uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals - -// // structs -// struct IzumiV3SwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// SwapDirection direction; -// } - -// struct MultiHopTestParams { -// address tokenIn; -// address tokenMid; -// address tokenOut; -// address pool1; -// address pool2; -// uint256 amountIn; -// SwapDirection direction1; -// SwapDirection direction2; -// } - -// error IzumiV3SwapUnexpected(); -// error IzumiV3SwapCallbackUnknownSource(); -// error IzumiV3SwapCallbackNotPositiveAmount(); - -// function setUp() public override { -// super.setUp(); - -// string memory baseRpc = vm.envString("ETH_NODE_URI_BASE"); -// vm.createSelectFork(baseRpc, 29831758); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); - -// // Setup labels -// vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); -// vm.label(USDC, "USDC"); -// vm.label(WETH, "WETH"); -// vm.label(USDB_C, "USDB-C"); -// vm.label(IZUMI_WETH_USDC_POOL, "WETH-USDC Pool"); -// vm.label(IZUMI_WETH_USDB_C_POOL, "WETH-USDB-C Pool"); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // Test USDC -> WETH -// deal(USDC, address(liFiDEXAggregator), AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// IzumiV3SwapTestParams({ -// from: address(liFiDEXAggregator), -// to: USER_SENDER, -// tokenIn: USDC, -// amountIn: AMOUNT_USDC, -// tokenOut: WETH, -// direction: SwapDirection.Token1ToToken0 -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// _testMultiHopSwap( -// MultiHopTestParams({ -// tokenIn: USDC, -// tokenMid: WETH, -// tokenOut: USDB_C, -// pool1: IZUMI_WETH_USDC_POOL, -// pool2: IZUMI_WETH_USDB_C_POOL, -// amountIn: AMOUNT_USDC, -// direction1: SwapDirection.Token1ToToken0, -// direction2: SwapDirection.Token0ToToken1 -// }) -// ); -// } - -// function test_CanSwap() public override { -// deal(address(USDC), USER_SENDER, AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); -// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - -// // fix the swap data encoding -// bytes memory swapData = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// USDC, -// uint8(SwapDirection.Token1ToToken0), -// IZUMI_WETH_USDC_POOL, -// USER_RECEIVER -// ); - -// vm.expectEmit(true, true, true, false); -// emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); - -// liFiDEXAggregator.processRoute( -// USDC, -// AMOUNT_USDC, -// WETH, -// 0, -// USER_RECEIVER, -// swapData -// ); - -// vm.stopPrank(); -// } - -// function testRevert_IzumiV3SwapUnexpected() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); - -// // create invalid pool address -// address invalidPool = address(0x999); - -// // create a route with an invalid pool -// bytes memory invalidRoute = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// USDC, -// uint8(SwapDirection.Token1ToToken0), -// invalidPool, -// USER_SENDER -// ); - -// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - -// // mock the iZiSwap pool to return without updating lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), -// abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool -// ); - -// vm.expectRevert(IzumiV3SwapUnexpected.selector); - -// liFiDEXAggregator.processRoute( -// USDC, -// AMOUNT_USDC, -// WETH, -// 0, -// USER_SENDER, -// invalidRoute -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// function testRevert_IzumiV3SwapCallbackUnknownSource() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// // create invalid pool address -// address invalidPool = address(0x999); - -// vm.prank(USER_SENDER); -// IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - -// // mock the pool to call the callback directly without setting lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), -// abi.encode(0, 0) -// ); - -// // try to call the callback directly from the pool without setting lastCalledPool -// vm.prank(invalidPool); -// vm.expectRevert(IzumiV3SwapCallbackUnknownSource.selector); -// liFiDEXAggregator.swapY2XCallback(0, AMOUNT_USDC, abi.encode(USDC)); - -// vm.clearMockedCalls(); -// } - -// function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// // set lastCalledPool to the pool address to pass the unknown source check -// vm.store( -// address(liFiDEXAggregator), -// bytes32(uint256(3)), // slot for lastCalledPool -// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) -// ); - -// // try to call the callback with zero amount -// vm.prank(IZUMI_WETH_USDC_POOL); -// vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); -// liFiDEXAggregator.swapY2XCallback( -// 0, -// 0, // zero amount should trigger the error -// abi.encode(USDC) -// ); -// } - -// function testRevert_FailsIfAmountInIsTooLarge() public { -// deal(address(WETH), USER_SENDER, type(uint256).max); - -// vm.startPrank(USER_SENDER); -// IERC20(WETH).approve(address(liFiDEXAggregator), type(uint256).max); - -// // fix the swap data encoding -// bytes memory swapData = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// WETH, -// uint8(SwapDirection.Token0ToToken1), -// IZUMI_WETH_USDC_POOL, -// USER_RECEIVER -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// WETH, -// type(uint216).max, -// USDC, -// 0, -// USER_RECEIVER, -// swapData -// ); - -// vm.stopPrank(); -// } - -// function _testSwap(IzumiV3SwapTestParams memory params) internal { -// // Fund the sender with tokens if not the contract itself -// if (params.from != address(liFiDEXAggregator)) { -// deal(params.tokenIn, params.from, params.amountIn); -// } - -// // Capture initial token balances -// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( -// params.from -// ); -// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( -// params.to -// ); - -// // Build the route based on the command type -// CommandType commandCode = params.from == address(liFiDEXAggregator) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; - -// // Construct the route -// bytes memory route = _buildIzumiV3Route( -// commandCode, -// params.tokenIn, -// uint8(params.direction == SwapDirection.Token0ToToken1 ? 1 : 0), -// IZUMI_WETH_USDC_POOL, -// params.to -// ); - -// // Approve tokens if necessary -// if (params.from == USER_SENDER) { -// vm.startPrank(USER_SENDER); -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); -// } - -// // Expect the Route event emission -// address from = params.from == address(liFiDEXAggregator) -// ? USER_SENDER -// : params.from; - -// vm.expectEmit(true, true, true, false); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// 0, // No minimum amount enforced in test -// 0 // Actual amount will be checked after the swap -// ); - -// // Execute the swap -// uint256 amountOut = liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// 0, // No minimum amount for testing -// params.to, -// route -// ); - -// if (params.from == USER_SENDER) { -// vm.stopPrank(); -// } - -// // Verify balances have changed correctly -// uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); - -// assertApproxEqAbs( -// initialBalanceIn - finalBalanceIn, -// params.amountIn, -// 1, // 1 wei tolerance because of undrain protection for dex aggregator -// "TokenIn amount mismatch" -// ); -// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); -// assertEq( -// amountOut, -// finalBalanceOut - initialBalanceOut, -// "AmountOut mismatch" -// ); - -// emit log_named_uint("Amount In", params.amountIn); -// emit log_named_uint("Amount Out", amountOut); -// } - -// function _testMultiHopSwap(MultiHopTestParams memory params) internal { -// // Fund the sender with tokens -// deal(params.tokenIn, USER_SENDER, params.amountIn); - -// // Capture initial token balances -// uint256 initialBalanceIn; -// uint256 initialBalanceOut; - -// initialBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// initialBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// // Build multi-hop route -// bytes memory route = _buildIzumiV3MultiHopRoute(params); - -// // Approve tokens -// vm.startPrank(USER_SENDER); -// IERC20(params.tokenIn).approve( -// address(liFiDEXAggregator), -// params.amountIn -// ); - -// // Execute the swap -// uint256 amountOut = liFiDEXAggregator.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// 0, // No minimum amount for testing -// USER_SENDER, -// route -// ); -// vm.stopPrank(); - -// // Verify balances have changed correctly -// uint256 finalBalanceIn; -// uint256 finalBalanceOut; - -// finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// assertEq( -// initialBalanceIn - finalBalanceIn, -// params.amountIn, -// "TokenIn amount mismatch" -// ); -// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); -// assertEq( -// amountOut, -// finalBalanceOut - initialBalanceOut, -// "AmountOut mismatch" -// ); -// } - -// function _buildIzumiV3Route( -// CommandType commandCode, -// address tokenIn, -// uint8 direction, -// address pool, -// address recipient -// ) internal pure returns (bytes memory) { -// return -// abi.encodePacked( -// uint8(commandCode), -// tokenIn, -// uint8(1), // number of pools (1) -// FULL_SHARE, // 100% share -// uint8(PoolType.iZiSwap), // pool type -// pool, -// uint8(direction), -// recipient -// ); -// } - -// function _buildIzumiV3MultiHopRoute( -// MultiHopTestParams memory params -// ) internal view returns (bytes memory) { -// // First hop: USER_ERC20 -> LDA -// bytes memory firstHop = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// params.tokenIn, -// uint8(params.direction1), -// params.pool1, -// address(liFiDEXAggregator) -// ); - -// // Second hop: MY_ERC20 (LDA) -> pool2 -// bytes memory secondHop = _buildIzumiV3Route( -// CommandType.ProcessMyERC20, -// params.tokenMid, -// uint8(params.direction2), -// params.pool2, -// USER_SENDER // final recipient -// ); - -// // Combine the two hops -// return bytes.concat(firstHop, secondHop); -// } -// } - -// ----------------------------------------------------------------------------- -// HyperswapV3 on HyperEVM -// ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorHyperswapV3Test is LiFiDexAggregatorUpgradeTest { -// using SafeERC20 for IERC20; - -// /// @dev HyperswapV3 router on HyperEVM chain -// IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = -// IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); -// /// @dev HyperswapV3 quoter on HyperEVM chain -// IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = -// IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); - -// /// @dev a liquid USDT on HyperEVM -// IERC20 internal constant USDT0 = -// IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); -// /// @dev WHYPE on HyperEVM -// IERC20 internal constant WHYPE = -// IERC20(0x5555555555555555555555555555555555555555); - -// struct HyperswapV3Params { -// CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 -// address tokenIn; // Input token address -// address recipient; // Address receiving the output tokens -// address pool; // HyperswapV3 pool address -// bool zeroForOne; // Direction of the swap -// } - -// function setUp() public override { -// setupHyperEVM(); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - -// deal(address(USDT0), USER_SENDER, amountIn); - -// // user approves -// vm.prank(USER_SENDER); -// USDT0.approve(address(liFiDEXAggregator), amountIn); - -// // fetch the real pool and quote -// address pool = HYPERSWAP_FACTORY.getPool( -// address(USDT0), -// address(WHYPE), -// 3000 -// ); - -// // Create the params struct for quoting -// IHyperswapV3QuoterV2.QuoteExactInputSingleParams -// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ -// tokenIn: address(USDT0), -// tokenOut: address(WHYPE), -// amountIn: amountIn, -// fee: 3000, -// sqrtPriceLimitX96: 0 -// }); - -// // Get the quote using the struct -// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( -// params -// ); - -// // build the "off-chain" route -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(USDT0), -// uint8(1), // 1 pool -// uint16(65535), // FULL_SHARE -// UniV3Facet.swapUniV3.selector, // UNIV3 selector -// pool, -// uint8(0), // zeroForOne = true if USDT0 < WHYPE -// address(USER_SENDER) -// ); - -// // expect the Route event -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// address(USDT0), -// address(WHYPE), -// amountIn, -// quoted, -// quoted -// ); - -// // execute -// vm.prank(USER_SENDER); -// liFiDEXAggregator.processRoute( -// address(USDT0), -// amountIn, -// address(WHYPE), -// quoted, -// USER_SENDER, -// route -// ); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - -// // Fund dex aggregator contract -// deal(address(USDT0), address(liFiDEXAggregator), amountIn); - -// // fetch the real pool and quote -// address pool = HYPERSWAP_FACTORY.getPool( -// address(USDT0), -// address(WHYPE), -// 3000 -// ); - -// // Create the params struct for quoting -// IHyperswapV3QuoterV2.QuoteExactInputSingleParams -// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ -// tokenIn: address(USDT0), -// tokenOut: address(WHYPE), -// amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection -// fee: 3000, -// sqrtPriceLimitX96: 0 -// }); - -// // Get the quote using the struct -// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( -// params -// ); - -// // Build route using our helper function -// bytes memory route = _buildHyperswapV3Route( -// HyperswapV3Params({ -// commandCode: CommandType.ProcessMyERC20, -// tokenIn: address(USDT0), -// recipient: USER_SENDER, -// pool: pool, -// zeroForOne: true // USDT0 < WHYPE -// }) -// ); - -// // expect the Route event -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// address(USDT0), -// address(WHYPE), -// amountIn - 1, // Account for slot undrain protection -// quoted, -// quoted -// ); - -// // execute -// vm.prank(USER_SENDER); -// liFiDEXAggregator.processRoute( -// address(USDT0), -// amountIn - 1, // Account for slot undrain protection -// address(WHYPE), -// quoted, -// USER_SENDER, -// route -// ); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. -// // HyperswapV3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. HyperswapV3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// function _buildHyperswapV3Route( -// HyperswapV3Params memory params -// ) internal pure returns (bytes memory route) { -// route = abi.encodePacked( -// uint8(params.commandCode), -// params.tokenIn, -// uint8(1), // 1 pool -// FULL_SHARE, // 65535 - 100% share -// UniV3Facet.swapUniV3.selector, // UNIV3 selector -// params.pool, -// uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false -// params.recipient -// ); - -// return route; -// } -// } - -// // ----------------------------------------------------------------------------- -// // LaminarV3 on HyperEVM -// // ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorLaminarV3Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// IERC20 internal constant WHYPE = -// IERC20(0x5555555555555555555555555555555555555555); -// IERC20 internal constant LHYPE = -// IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); - -// address internal constant WHYPE_LHYPE_POOL = -// 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; - -// function setUp() public override { -// setupHyperEVM(); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // Fund the user with WHYPE -// deal(address(WHYPE), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WHYPE.approve(address(liFiDEXAggregator), amountIn); - -// // Build a single-pool UniV3 route -// bool zeroForOne = address(WHYPE) > address(LHYPE); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(WHYPE), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), -// WHYPE_LHYPE_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// // Record balances -// uint256 inBefore = WHYPE.balanceOf(USER_SENDER); -// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - -// // Execute swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(WHYPE), -// amountIn, -// address(LHYPE), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify -// uint256 inAfter = WHYPE.balanceOf(USER_SENDER); -// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the aggregator directly -// deal(address(WHYPE), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); - -// bool zeroForOne = address(WHYPE) > address(LHYPE); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(WHYPE), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// WHYPE_LHYPE_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - -// // Withdraw 1 wei to avoid slot-undrain protection -// liFiDEXAggregator.processRoute( -// address(WHYPE), -// amountIn - 1, -// address(LHYPE), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. -// // Laminar V3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. Laminar V3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } -// } - -// contract LiFiDexAggregatorXSwapV3Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// address internal constant USDC_E_WXDC_POOL = -// 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; - -// /// @dev our two tokens: USDC.e and wrapped XDC -// IERC20 internal constant USDC_E = -// IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); -// IERC20 internal constant WXDC = -// IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); - -// function setUp() public override { -// customRpcUrlForForking = "ETH_NODE_URI_XDC"; -// customBlockNumberForForking = 89279495; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e6; -// deal(address(USDC_E), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// USDC_E.approve(address(liFiDEXAggregator), amountIn); - -// // Build a one-pool V3 route -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(USDC_E), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), -// USDC_E_WXDC_POOL, -// uint8(1), // zeroForOne (USDC.e > WXDC) -// USER_SENDER -// ); - -// // Record balances before swap -// uint256 inBefore = USDC_E.balanceOf(USER_SENDER); -// uint256 outBefore = WXDC.balanceOf(USER_SENDER); - -// // Execute swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(USDC_E), -// amountIn, -// address(WXDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify balances after swap -// uint256 inAfter = USDC_E.balanceOf(USER_SENDER); -// uint256 outAfter = WXDC.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - -// vm.stopPrank(); -// } - -// /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 5_000 * 1e6; - -// // fund the aggregator -// deal(address(USDC_E), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); - -// // Account for slot-undrain protection -// uint256 swapAmount = amountIn - 1; - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(USDC_E), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// USDC_E_WXDC_POOL, -// uint8(1), // zeroForOne (USDC.e > WXDC) -// USER_SENDER -// ); - -// // Record balances before swap -// uint256 outBefore = WXDC.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(USDC_E), -// swapAmount, -// address(WXDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify balances after swap -// uint256 outAfter = WXDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. -// // XSwap V3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. XSwap V3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } -// } - -// // ----------------------------------------------------------------------------- -// // RabbitSwap on Viction -// // ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorRabbitSwapTest is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// // Constants for RabbitSwap on Viction -// IERC20 internal constant SOROS = -// IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); -// IERC20 internal constant C98 = -// IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); -// address internal constant SOROS_C98_POOL = -// 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; - -// function setUp() public override { -// // setup for Viction network -// customRpcUrlForForking = "ETH_NODE_URI_VICTION"; -// customBlockNumberForForking = 94490946; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the user with SOROS -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(liFiDEXAggregator), amountIn); - -// // build a single-pool UniV3-style route -// bool zeroForOne = address(SOROS) > address(C98); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), // RabbitSwap uses UniV3 pool type -// SOROS_C98_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// // record balances before swap -// uint256 inBefore = SOROS.balanceOf(USER_SENDER); -// uint256 outBefore = C98.balanceOf(USER_SENDER); - -// // execute swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// // verify balances after swap -// uint256 inAfter = SOROS.balanceOf(USER_SENDER); -// uint256 outAfter = C98.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive C98"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the aggregator directly -// deal(address(SOROS), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); - -// bool zeroForOne = address(SOROS) > address(C98); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// SOROS_C98_POOL, -// uint8(zeroForOne ? 0 : 1), -// address(USER_SENDER) -// ); - -// uint256 outBefore = C98.balanceOf(USER_SENDER); - -// // withdraw 1 wei less to avoid slot-undrain protection -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn - 1, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 outAfter = C98.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive C98"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. -// // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, -// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. UniV3-style pools immediately revert on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// function testRevert_RabbitSwapInvalidPool() public { -// uint256 amountIn = 1_000 * 1e18; -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(liFiDEXAggregator), amountIn); - -// // build route with invalid pool address -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// address(0), // invalid pool address -// uint8(0), -// USER_SENDER -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } - -// function testRevert_RabbitSwapInvalidRecipient() public { -// uint256 amountIn = 1_000 * 1e18; -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(liFiDEXAggregator), amountIn); - -// // build route with invalid recipient -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint8(PoolType.UniV3), -// SOROS_C98_POOL, -// uint8(0), -// address(0) // invalid recipient -// ); - -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } -// } - -// // ---------------------------------------------- -// // EnosysDexV3 on Flare -// // ---------------------------------------------- -// contract LiFiDexAggregatorEnosysDexV3Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// /// @dev HLN token on Flare -// IERC20 internal constant HLN = -// IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); - -// /// @dev USDT0 token on Flare -// IERC20 internal constant USDT0 = -// IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); - -// /// @dev The single EnosysDexV3 pool for HLN–USDT0 -// address internal constant ENOSYS_V3_POOL = -// 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; - -// /// @notice Set up a fork of Flare at block 42652369 and initialize the aggregator -// function setUp() public override { -// customRpcUrlForForking = "ETH_NODE_URI_FLARE"; -// customBlockNumberForForking = 42652369; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// /// @notice Single‐pool swap: USER sends HLN → receives USDT0 -// function test_CanSwap() public override { -// // Mint 1 000 HLN to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(HLN), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// HLN.approve(address(liFiDEXAggregator), amountIn); - -// bool zeroForOne = address(HLN) > address(USDT0); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(HLN), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), // V3‐style pool -// ENOSYS_V3_POOL, // pool address -// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1, 1 = token1→token0 -// address(USER_SENDER) // recipient -// ); - -// // Record balances before swap -// uint256 inBefore = HLN.balanceOf(USER_SENDER); -// uint256 outBefore = USDT0.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(HLN), -// amountIn, -// address(USDT0), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that HLN was spent and some USDT0 was received -// uint256 inAfter = HLN.balanceOf(USER_SENDER); -// uint256 outAfter = USDT0.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - -// vm.stopPrank(); -// } - -// /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 -// function test_CanSwap_FromDexAggregator() public override { -// // Fund the aggregator with 1 000 HLN -// uint256 amountIn = 1_000 * 1e18; -// deal(address(HLN), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); -// bool zeroForOne = address(HLN) > address(USDT0); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(HLN), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.UniV3), // V3‐style pool -// ENOSYS_V3_POOL, // pool address -// uint8(zeroForOne ? 0 : 1), // 0 = token0→token1 -// address(USER_SENDER) // recipient -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDT0.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(HLN), -// swapAmount, -// address(USDT0), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDT0 was received -// uint256 outAfter = USDT0.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. -// // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, -// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. UniV3-style pools immediately revert on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } -// } - -// // ---------------------------------------------- -// // SyncSwapV2 on Linea -// // ---------------------------------------------- -// contract LiFiDexAggregatorSyncSwapV2Test is LiFiDexAggregatorTest { -// using SafeERC20 for IERC20; - -// IERC20 internal constant USDC = -// IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); -// IERC20 internal constant WETH = -// IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); -// address internal constant USDC_WETH_POOL_V1 = -// address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); -// address internal constant SYNC_SWAP_VAULT = -// address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); - -// address internal constant USDC_WETH_POOL_V2 = -// address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); - -// IERC20 internal constant USDT = -// IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); -// address internal constant USDC_USDT_POOL_V1 = -// address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); - -// /// @notice Set up a fork of Linea at block 20077881 and initialize the aggregator -// function setUp() public override { -// customRpcUrlForForking = "ETH_NODE_URI_LINEA"; -// customBlockNumberForForking = 20077881; -// fork(); - -// _initializeDexAggregator(USER_DIAMOND_OWNER); -// } - -// /// @notice Single‐pool swap: USER sends WETH → receives USDC -// function test_CanSwap() public override { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Record balances before swap -// uint256 inBefore = WETH.balanceOf(USER_SENDER); -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that WETH was spent and some USDC_C was received -// uint256 inAfter = WETH.balanceOf(USER_SENDER); -// uint256 outAfter = USDC.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_PoolV2() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V2, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(0) // isV1Pool -// ); - -// // Record balances before swap -// uint256 inBefore = WETH.balanceOf(USER_SENDER); -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that WETH was spent and some USDC_C was received -// uint256 inAfter = WETH.balanceOf(USER_SENDER); -// uint256 outAfter = USDC.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // Fund the aggregator with 1 000 WETH -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(WETH), -// swapAmount, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDC was received -// uint256 outAfter = USDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator_PoolV2() public { -// // Fund the aggregator with 1 000 WETH -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), address(liFiDEXAggregator), amountIn); - -// vm.startPrank(USER_SENDER); -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V2, // pool address -// address(USER_SENDER), // recipient -// uint8(2) // withdrawMode -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// liFiDEXAggregator.processRoute( -// address(WETH), -// swapAmount, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDC was received -// uint256 outAfter = USDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// uint256 amountIn = 1_000e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); -// uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); - -// // -// // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) -// // -// bytes memory hop1 = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(WETH), -// uint8(1), // one pool -// FULL_SHARE, // 100% of the WETH -// uint8(PoolType.SyncSwapV2), -// USDC_WETH_POOL_V1, // the V1 pool -// SYNC_SWAP_VAULT, // “to” = the vault address -// uint8(2), // withdrawMode = 2 -// uint8(1), // isV1Pool = true -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // -// // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 -// // -// bytes memory hop2 = abi.encodePacked( -// uint8(CommandType.ProcessOnePool), -// address(USDC), -// uint8(PoolType.SyncSwapV2), -// USDC_USDT_POOL_V1, // V1 USDC⟶USDT pool -// address(USER_SENDER), // send the USDT home -// uint8(2), // withdrawMode = 2 -// uint8(1), // isV1Pool = true -// SYNC_SWAP_VAULT // vault -// ); - -// bytes memory route = bytes.concat(hop1, hop2); - -// uint256 amountOut = liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDT), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); -// uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); - -// assertEq( -// initialBalanceIn - afterBalanceIn, -// amountIn, -// "WETH spent mismatch" -// ); -// assertEq( -// amountOut, -// afterBalanceOut - initialBalanceOut, -// "USDT amountOut mismatch" -// ); -// vm.stopPrank(); -// } - -// function testRevert_V1PoolMissingVaultAddress() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(0) // vault (invalid address) -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidPoolOrRecipient() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(liFiDEXAggregator), amountIn); - -// bytes memory routeWithInvalidPool = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// address(0), // pool address (invalid address) -// address(USER_SENDER), // recipient -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidPool -// ); - -// bytes memory routeWithInvalidRecipient = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address -// address(0), // recipient (invalid address) -// uint8(2), // withdrawMode -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidRecipient -// ); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidWithdrawMode() public { -// vm.startPrank(USER_SENDER); - -// bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint8(PoolType.SyncSwapV2), // SyncSwapV2 -// USDC_WETH_POOL_V1, // pool address (invalid address) -// address(USER_SENDER), // recipient -// uint8(3), // withdrawMode (invalid) -// uint8(1), // isV1Pool -// address(SYNC_SWAP_VAULT) // vault -// ); - -// // Expect revert with InvalidCallData because withdrawMode is invalid -// vm.expectRevert(InvalidCallData.selector); -// liFiDEXAggregator.processRoute( -// address(WETH), -// 1, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidWithdrawMode -// ); - -// vm.stopPrank(); -// } -// } +import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; +import { TestHelpers } from "../../utils/TestHelpers.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; + +/** + * @title BaseDexFacetTest + * @notice Base test contract with common functionality and abstractions for DEX-specific tests + */ +abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { + using SafeERC20 for IERC20; + + struct ForkConfig { + string rpcEnvName; + uint256 blockNumber; + } + + // Command codes for route processing + enum CommandType { + None, // 0 - not used + ProcessMyERC20, // 1 - processMyERC20 + ProcessUserERC20, // 2 - processUserERC20 + ProcessNative, // 3 - processNative + ProcessOnePool, // 4 - processOnePool + ProcessInsideBento, // 5 - processInsideBento + ApplyPermit // 6 - applyPermit + } + + // Direction constants + enum SwapDirection { + Token1ToToken0, // 0 + Token0ToToken1 // 1 + } + + // Callback constants + enum CallbackStatus { + Disabled, // 0 + Enabled // 1 + } + + CoreRouteFacet internal coreRouteFacet; + ForkConfig internal forkConfig; + + // Other constants + uint16 internal constant FULL_SHARE = 65535; // 100% share for single pool swaps + + // Common events and errors + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + error WrongPoolReserves(); + error PoolDoesNotExist(); + + function _addDexFacet() internal virtual; + + // Setup function for Apechain tests + function setupApechain() internal { + customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; + customBlockNumberForForking = 12912470; + } + + function setupHyperEVM() internal { + customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; + customBlockNumberForForking = 4433562; + } + + function setupXDC() internal { + customRpcUrlForForking = "ETH_NODE_URI_XDC"; + customBlockNumberForForking = 89279495; + } + + function setupViction() internal { + customRpcUrlForForking = "ETH_NODE_URI_VICTION"; + customBlockNumberForForking = 94490946; + } + + function setupFlare() internal { + customRpcUrlForForking = "ETH_NODE_URI_FLARE"; + customBlockNumberForForking = 42652369; + } + + function setupLinea() internal { + customRpcUrlForForking = "ETH_NODE_URI_LINEA"; + customBlockNumberForForking = 20077881; + } + + function setUp() public virtual override { + // forkConfig should be set in the child contract via _setupForkConfig() + _setupForkConfig(); + // TODO if rpcEnvName is not set, revert + // TODO if blockNumber is not set, revert + customRpcUrlForForking = forkConfig.rpcEnvName; + customBlockNumberForForking = forkConfig.blockNumber; + + fork(); + LdaDiamondTest.setUp(); + _addCoreRouteFacet(); + _addDexFacet(); + } + + function _addCoreRouteFacet() internal { + coreRouteFacet = new CoreRouteFacet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = CoreRouteFacet.processRoute.selector; + addFacet( + address(ldaDiamond), + address(coreRouteFacet), + functionSelectors + ); + + coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); + } + + function _setupForkConfig() internal virtual; + + // function test_ContractIsSetUpCorrectly() public { + // assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); + // assertEq( + // liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), + // true + // ); + // assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); + // } + + // function testRevert_FailsIfOwnerIsZeroAddress() public { + // vm.expectRevert(InvalidConfig.selector); + + // liFiDEXAggregator = new LiFiDEXAggregator( + // address(0xCAFE), + // privileged, + // address(0) + // ); + // } + + // ============================ Abstract DEX Tests ============================ + /** + * @notice Abstract test for basic token swapping functionality + * Each DEX implementation should override this + */ + function test_CanSwap() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap: Not implemented"); + } + + /** + * @notice Abstract test for swapping tokens from the DEX aggregator + * Each DEX implementation should override this + */ + function test_CanSwap_FromDexAggregator() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_FromDexAggregator: Not implemented"); + } + + /** + * @notice Abstract test for multi-hop swapping + * Each DEX implementation should override this + */ + function test_CanSwap_MultiHop() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_MultiHop: Not implemented"); + } +} diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol new file mode 100644 index 000000000..1b1c69071 --- /dev/null +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; + +abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { + UniV3StyleFacet internal uniV3Facet; + + function test_CanSwap_MultiHop() public virtual override { + // SKIPPED: UniV3 forke dex multi-hop unsupported due to AS (amount specified) requirement. + // UniV3 forke dex does not support a "one-pool" second hop today, + // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. UniV3-style pools immediately revert on + // require(amountSpecified != 0, 'AS'), so you can't chain two uniV3 pools + // in a single processRoute invocation. + } + + struct UniV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildUniV3SwapData( + UniV3SwapParams memory params + ) internal view returns (bytes memory) { + return + abi.encodePacked( + uniV3Facet.swapUniV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol new file mode 100644 index 000000000..444534cce --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; + +contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { + /// @dev HLN token on Flare + IERC20 internal constant HLN = + IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); + + /// @dev USDT0 token on Flare + IERC20 internal constant USDT0 = + IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); + + /// @dev The single EnosysDexV3 pool for HLN–USDT0 + address internal constant ENOSYS_V3_POOL = + 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_FLARE", + blockNumber: 42652369 + }); + } + + function _addDexFacet() internal override { + uniV3Facet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3Facet.swapUniV3.selector; + functionSelectors[1] = uniV3Facet.enosysdexV3SwapCallback.selector; + addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); + + uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + /// @notice Single‐pool swap: USER sends HLN → receives USDT0 + function test_CanSwap() public override { + // Mint 1 000 HLN to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(HLN), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + HLN.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: ENOSYS_V3_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(HLN), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = HLN.balanceOf(USER_SENDER); + uint256 outBefore = USDT0.balanceOf(USER_SENDER); + + // Execute the swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(HLN), + amountIn, + address(USDT0), + 0, + USER_SENDER, + route + ); + + // Verify that HLN was spent and some USDT0 was received + uint256 inAfter = HLN.balanceOf(USER_SENDER); + uint256 outAfter = USDT0.balanceOf(USER_SENDER); + + assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + + vm.stopPrank(); + } + + /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 + function test_CanSwap_FromDexAggregator() public override { + // Fund the aggregator with 1 000 HLN + uint256 amountIn = 1_000 * 1e18; + deal(address(HLN), address(coreRouteFacet), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: ENOSYS_V3_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), // aggregator's funds + address(HLN), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Subtract 1 to protect against slot‐undrain + uint256 swapAmount = amountIn - 1; + uint256 outBefore = USDT0.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(HLN), + swapAmount, + address(USDT0), + 0, + USER_SENDER, + route + ); + + // Verify that some USDT0 was received + uint256 outAfter = USDT0.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + + vm.stopPrank(); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol new file mode 100644 index 000000000..4702c9080 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; + +contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { + // Constants for RabbitSwap on Viction + IERC20 internal constant SOROS = + IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); + IERC20 internal constant C98 = + IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); + address internal constant SOROS_C98_POOL = + 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_VICTION", + blockNumber: 94490946 + }); + } + + function _addDexFacet() internal override { + uniV3Facet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3Facet.swapUniV3.selector; + functionSelectors[1] = uniV3Facet.rabbitSwapV3SwapCallback.selector; + addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); + + uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e18; + + // fund the user with SOROS + deal(address(SOROS), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + SOROS.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: SOROS_C98_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(SOROS), + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // record balances before swap + uint256 inBefore = SOROS.balanceOf(USER_SENDER); + uint256 outBefore = C98.balanceOf(USER_SENDER); + + // execute swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(SOROS), + amountIn, + address(C98), + 0, + USER_SENDER, + route + ); + + // verify balances after swap + uint256 inAfter = SOROS.balanceOf(USER_SENDER); + uint256 outAfter = C98.balanceOf(USER_SENDER); + assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive C98"); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 1_000 * 1e18; + + // fund the aggregator directly + deal(address(SOROS), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: SOROS_C98_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(SOROS), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + uint256 outBefore = C98.balanceOf(USER_SENDER); + + // withdraw 1 wei less to avoid slot-undrain protection + coreRouteFacet.processRoute( + address(SOROS), + amountIn - 1, + address(C98), + 0, + USER_SENDER, + route + ); + + uint256 outAfter = C98.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive C98"); + + vm.stopPrank(); + } + + function testRevert_RabbitSwapInvalidPool() public { + uint256 amountIn = 1_000 * 1e18; + deal(address(SOROS), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + SOROS.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: address(0), + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(SOROS), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(SOROS), + amountIn, + address(C98), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + } + + function testRevert_RabbitSwapInvalidRecipient() public { + uint256 amountIn = 1_000 * 1e18; + deal(address(SOROS), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + SOROS.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: SOROS_C98_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: address(0) + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(SOROS), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(SOROS), + amountIn, + address(C98), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol new file mode 100644 index 000000000..b9065e428 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +contract SyncSwapV2FacetTest is BaseDexFacetTest { + SyncSwapV2Facet internal syncSwapV2Facet; + + IERC20 internal constant USDC = + IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); + IERC20 internal constant WETH = + IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); + address internal constant USDC_WETH_POOL_V1 = + address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); + address internal constant SYNC_SWAP_VAULT = + address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); + + address internal constant USDC_WETH_POOL_V2 = + address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); + + IERC20 internal constant USDT = + IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); + address internal constant USDC_USDT_POOL_V1 = + address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_LINEA", + blockNumber: 20077881 + }); + } + + function _addDexFacet() internal override { + syncSwapV2Facet = new SyncSwapV2Facet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = syncSwapV2Facet.swapSyncSwapV2.selector; + addFacet( + address(ldaDiamond), + address(syncSwapV2Facet), + functionSelectors + ); + + syncSwapV2Facet = SyncSwapV2Facet(payable(address(ldaDiamond))); + } + + /// @notice Single‐pool swap: USER sends WETH → receives USDC + function test_CanSwap() public override { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = WETH.balanceOf(USER_SENDER); + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + // Execute the swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that WETH was spent and some USDC_C was received + uint256 inAfter = WETH.balanceOf(USER_SENDER); + uint256 outAfter = USDC.balanceOf(USER_SENDER); + + assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_PoolV2() public { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V2, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 0, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = WETH.balanceOf(USER_SENDER); + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + // Execute the swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that WETH was spent and some USDC_C was received + uint256 inAfter = WETH.balanceOf(USER_SENDER); + uint256 outAfter = USDC.balanceOf(USER_SENDER); + + assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + // Fund the aggregator with 1 000 WETH + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), // aggregator's funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Subtract 1 to protect against slot‐undrain + uint256 swapAmount = amountIn - 1; + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(WETH), + swapAmount, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that some USDC was received + uint256 outAfter = USDC.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator_PoolV2() public { + // Fund the aggregator with 1 000 WETH + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V2, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 0, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), // aggregator's funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Subtract 1 to protect against slot‐undrain + uint256 swapAmount = amountIn - 1; + uint256 outBefore = USDC.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(WETH), + swapAmount, + address(USDC), + 0, + USER_SENDER, + route + ); + + // Verify that some USDC was received + uint256 outAfter = USDC.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive USDC"); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + uint256 amountIn = 1_000e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); + uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); + + // + // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) + // + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: SYNC_SWAP_VAULT, + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeHop1 = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // + // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 + // + bytes memory swapDataHop2 = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_USDT_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeHop2 = abi.encodePacked( + uint8(CommandType.ProcessOnePool), + address(USDC), + uint16(swapDataHop2.length), // length prefix + swapDataHop2 + ); + + bytes memory route = bytes.concat(routeHop1, routeHop2); + + uint256 amountOut = coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDT), + 0, + USER_SENDER, + route + ); + + uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); + uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); + + assertEq( + initialBalanceIn - afterBalanceIn, + amountIn, + "WETH spent mismatch" + ); + assertEq( + amountOut, + afterBalanceOut - initialBalanceOut, + "USDT amountOut mismatch" + ); + vm.stopPrank(); + } + + function testRevert_V1PoolMissingVaultAddress() public { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: address(0) + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + } + + function testRevert_InvalidPoolOrRecipient() public { + // Transfer 1 000 WETH from whale to USER_SENDER + uint256 amountIn = 1_000 * 1e18; + deal(address(WETH), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WETH.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: address(0), + to: address(USER_SENDER), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeWithInvalidPool = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + routeWithInvalidPool + ); + + bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(0), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeWithInvalidRecipient = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapDataWithInvalidRecipient.length), // length prefix + swapDataWithInvalidRecipient + ); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + amountIn, + address(USDC), + 0, + USER_SENDER, + routeWithInvalidRecipient + ); + + vm.stopPrank(); + } + + function testRevert_InvalidWithdrawMode() public { + vm.startPrank(USER_SENDER); + + bytes + memory swapDataWithInvalidWithdrawMode = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: USDC_WETH_POOL_V1, + to: address(USER_SENDER), + withdrawMode: 3, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); + + bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), // user funds + address(WETH), // tokenIn + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapDataWithInvalidWithdrawMode.length), // length prefix + swapDataWithInvalidWithdrawMode + ); + + // Expect revert with InvalidCallData because withdrawMode is invalid + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(WETH), + 1, + address(USDC), + 0, + USER_SENDER, + routeWithInvalidWithdrawMode + ); + + vm.stopPrank(); + } + + struct SyncSwapV2SwapParams { + address pool; + address to; + uint8 withdrawMode; + uint8 isV1Pool; + address vault; + } + + function _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + syncSwapV2Facet.swapSyncSwapV2.selector, + params.pool, + params.to, + params.withdrawMode, + params.isV1Pool, + params.vault + ); + } +} From 04765f21fc9d0008f8b1aa6f8833b560ca0b003f Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 7 Aug 2025 20:50:25 +0200 Subject: [PATCH 013/220] added tests to facets --- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 961 ++++++++++++++++++ .../Lda/Facets/HyperswapV3Facet.t.sol | 210 ++++ .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 525 ++++++++++ .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 125 +++ .../Lda/Facets/VelodromeV2Facet.t.sol | 838 +++++++++++++++ .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 138 +++ 6 files changed, 2797 insertions(+) create mode 100644 test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol new file mode 100644 index 000000000..ff5c46a18 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -0,0 +1,961 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; +import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; +import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; +import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; +import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; +import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; + +contract AlgebraLiquidityAdderHelper { + address public immutable TOKEN_0; + address public immutable TOKEN_1; + + constructor(address _token0, address _token1) { + TOKEN_0 = _token0; + TOKEN_1 = _token1; + } + + function addLiquidity( + address pool, + int24 bottomTick, + int24 topTick, + uint128 amount + ) + external + returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) + { + // Get balances before + uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); + + // Call mint + (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( + address(this), + address(this), + bottomTick, + topTick, + amount, + abi.encode(TOKEN_0, TOKEN_1) + ); + + // Get balances after to account for fees + uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); + + // Calculate actual amounts transferred accounting for fees + amount0 = balance0Before - balance0After; + amount1 = balance1Before - balance1After; + + return (amount0, amount1, liquidityActual); + } + + function algebraMintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata + ) external { + // Check token balances + uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); + + // Transfer what we can, limited by actual balance + if (amount0Owed > 0) { + uint256 amount0ToSend = amount0Owed > balance0 + ? balance0 + : amount0Owed; + uint256 balance0Before = IERC20(TOKEN_0).balanceOf( + address(msg.sender) + ); + IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); + uint256 balance0After = IERC20(TOKEN_0).balanceOf( + address(msg.sender) + ); + // solhint-disable-next-line gas-custom-errors + require(balance0After > balance0Before, "Transfer failed"); + } + + if (amount1Owed > 0) { + uint256 amount1ToSend = amount1Owed > balance1 + ? balance1 + : amount1Owed; + uint256 balance1Before = IERC20(TOKEN_1).balanceOf( + address(msg.sender) + ); + IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); + uint256 balance1After = IERC20(TOKEN_1).balanceOf( + address(msg.sender) + ); + // solhint-disable-next-line gas-custom-errors + require(balance1After > balance1Before, "Transfer failed"); + } + } +} + +contract AlgebraFacetTest is BaseDexFacetTest { + AlgebraFacet internal algebraFacet; + + address private constant APE_ETH_TOKEN = + 0xcF800F4948D16F23333508191B1B1591daF70438; + address private constant WETH_TOKEN = + 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; + address private constant ALGEBRA_FACTORY_APECHAIN = + 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; + address private constant ALGEBRA_QUOTER_V2_APECHAIN = + 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; + address private constant ALGEBRA_POOL_APECHAIN = + 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; + address private constant APE_ETH_HOLDER_APECHAIN = + address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); + + address private constant IMPOSSIBLE_POOL_ADDRESS = + 0x0000000000000000000000000000000000000001; + + struct AlgebraSwapTestParams { + address from; + address to; + address tokenIn; + uint256 amountIn; + address tokenOut; + SwapDirection direction; + bool supportsFeeOnTransfer; + } + + error AlgebraSwapUnexpected(); + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_APECHAIN", + blockNumber: 12912470 + }); + } + + function _addDexFacet() internal override { + algebraFacet = new AlgebraFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = algebraFacet.swapAlgebra.selector; + functionSelectors[1] = algebraFacet.algebraSwapCallback.selector; + addFacet( + address(ldaDiamond), + address(algebraFacet), + functionSelectors + ); + + algebraFacet = AlgebraFacet(payable(address(ldaDiamond))); + } + + // Override the abstract test with Algebra implementation + function test_CanSwap_FromDexAggregator() public override { + // Fund LDA from whale address + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(address(coreRouteFacet), 1 * 1e18); + + vm.startPrank(USER_SENDER); + + _testAlgebraSwap( + AlgebraSwapTestParams({ + from: address(coreRouteFacet), + to: address(USER_SENDER), + tokenIn: APE_ETH_TOKEN, + amountIn: IERC20(APE_ETH_TOKEN).balanceOf( + address(coreRouteFacet) + ) - 1, + tokenOut: address(WETH_TOKEN), + direction: SwapDirection.Token0ToToken1, + supportsFeeOnTransfer: true + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_FeeOnTransferToken() public { + setupApechain(); + + uint256 amountIn = 534451326669177; + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); + + vm.startPrank(APE_ETH_HOLDER_APECHAIN); + + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), amountIn); + + // Build route for algebra swap with command code 2 (user funds) + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: APE_ETH_HOLDER_APECHAIN, + pool: ALGEBRA_POOL_APECHAIN, + supportsFeeOnTransfer: true + }) + ); + + // 2. Build the final route with the command and length-prefixed swapData + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Track initial balance + uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( + APE_ETH_HOLDER_APECHAIN + ); + + // Execute the swap + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + amountIn, + WETH_TOKEN, + 0, // minOut = 0 for this test + APE_ETH_HOLDER_APECHAIN, + route + ); + + // Verify balances + uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( + APE_ETH_HOLDER_APECHAIN + ); + assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); + + vm.stopPrank(); + } + + function test_CanSwap() public override { + vm.startPrank(APE_ETH_HOLDER_APECHAIN); + + // Transfer tokens from whale to USER_SENDER + uint256 amountToTransfer = 100 * 1e18; + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); + + vm.stopPrank(); + + vm.startPrank(USER_SENDER); + + _testAlgebraSwap( + AlgebraSwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: APE_ETH_TOKEN, + amountIn: 10 * 1e18, + tokenOut: address(WETH_TOKEN), + direction: SwapDirection.Token0ToToken1, + supportsFeeOnTransfer: true + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_Reverse() public { + test_CanSwap(); + + vm.startPrank(USER_SENDER); + + _testAlgebraSwap( + AlgebraSwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(WETH_TOKEN), + amountIn: 5 * 1e18, + tokenOut: APE_ETH_TOKEN, + direction: SwapDirection.Token1ToToken0, + supportsFeeOnTransfer: false + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { + MultiHopTestState memory state; + state.isFeeOnTransfer = true; + + // Setup tokens and pools + state = _setupTokensAndPools(state); + + // Execute and verify swap + _executeAndVerifyMultiHopSwap(state); + } + + function test_CanSwap_MultiHop() public override { + MultiHopTestState memory state; + state.isFeeOnTransfer = false; + + // Setup tokens and pools + state = _setupTokensAndPools(state); + + // Execute and verify swap + _executeAndVerifyMultiHopSwap(state); + } + + // Test that the proper error is thrown when algebra swap fails + function testRevert_SwapUnexpected() public { + // Transfer tokens from whale to user + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Create invalid pool address + address invalidPool = address(0x999); + + // Mock token0() call on invalid pool + vm.mockCall( + invalidPool, + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(APE_ETH_TOKEN) + ); + + // Create a route with an invalid pool + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: USER_SENDER, + pool: invalidPool, + supportsFeeOnTransfer: true + }) + ); + + bytes memory invalidRoute = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Approve tokens + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + + // Mock the algebra pool to not reset lastCalledPool + vm.mockCall( + invalidPool, + abi.encodeWithSelector( + IAlgebraPool.swapSupportingFeeOnInputTokens.selector + ), + abi.encode(0, 0) + ); + + // Expect the AlgebraSwapUnexpected error + vm.expectRevert(AlgebraSwapUnexpected.selector); + + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + 1 * 1e18, + address(WETH_TOKEN), + 0, + USER_SENDER, + invalidRoute + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + // Helper function to setup tokens and pools + function _setupTokensAndPools( + MultiHopTestState memory state + ) private returns (MultiHopTestState memory) { + // Create tokens + ERC20 tokenA = new ERC20( + "Token A", + state.isFeeOnTransfer ? "FTA" : "TA", + 18 + ); + IERC20 tokenB; + ERC20 tokenC = new ERC20( + "Token C", + state.isFeeOnTransfer ? "FTC" : "TC", + 18 + ); + + if (state.isFeeOnTransfer) { + tokenB = IERC20( + address( + new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) + ) + ); + } else { + tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); + } + + state.tokenA = IERC20(address(tokenA)); + state.tokenB = tokenB; + state.tokenC = IERC20(address(tokenC)); + + // Label addresses + vm.label(address(state.tokenA), "Token A"); + vm.label(address(state.tokenB), "Token B"); + vm.label(address(state.tokenC), "Token C"); + + // Mint initial token supplies + tokenA.mint(address(this), 1_000_000 * 1e18); + if (!state.isFeeOnTransfer) { + ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); + } else { + MockFeeOnTransferToken(address(tokenB)).mint( + address(this), + 1_000_000 * 1e18 + ); + } + tokenC.mint(address(this), 1_000_000 * 1e18); + + // Create pools + state.pool1 = _createAlgebraPool( + address(state.tokenA), + address(state.tokenB) + ); + state.pool2 = _createAlgebraPool( + address(state.tokenB), + address(state.tokenC) + ); + + vm.label(state.pool1, "Pool 1"); + vm.label(state.pool2, "Pool 2"); + + // Add liquidity + _addLiquidityToPool( + state.pool1, + address(state.tokenA), + address(state.tokenB) + ); + _addLiquidityToPool( + state.pool2, + address(state.tokenB), + address(state.tokenC) + ); + + state.amountToTransfer = 100 * 1e18; + state.amountIn = 50 * 1e18; + + // Transfer tokens to USER_SENDER + IERC20(address(state.tokenA)).transfer( + USER_SENDER, + state.amountToTransfer + ); + + return state; + } + + // Helper function to execute and verify the swap + function _executeAndVerifyMultiHopSwap( + MultiHopTestState memory state + ) private { + vm.startPrank(USER_SENDER); + + uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( + USER_SENDER + ); + uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( + USER_SENDER + ); + + // Approve spending + IERC20(address(state.tokenA)).approve( + address(ldaDiamond), + state.amountIn + ); + + // Build route + bytes memory route = _buildMultiHopRouteForTest(state); + + // Execute swap + coreRouteFacet.processRoute( + address(state.tokenA), + state.amountIn, + address(state.tokenC), + 0, // No minimum amount out for testing + USER_SENDER, + route + ); + + // Verify results + _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); + + vm.stopPrank(); + } + + // Helper function to build the multi-hop route for test + function _buildMultiHopRouteForTest( + MultiHopTestState memory state + ) private view returns (bytes memory) { + // 1. Get the specific data payload for each hop + bytes memory firstHopData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: address(state.tokenA), + recipient: address(ldaDiamond), // Hop 1 sends to the contract itself + pool: state.pool1, + supportsFeeOnTransfer: false + }) + ); + + bytes memory secondHopData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessMyERC20, + tokenIn: address(state.tokenB), + recipient: USER_SENDER, // Hop 2 sends to the final user + pool: state.pool2, + supportsFeeOnTransfer: state.isFeeOnTransfer + }) + ); + + // 2. Assemble the first full command with its length prefix + bytes memory firstCommand = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + state.tokenA, + uint8(1), + FULL_SHARE, + uint16(firstHopData.length), + firstHopData + ); + + // 3. Assemble the second full command with its length prefix + bytes memory secondCommand = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + state.tokenB, + uint8(1), // num splits for the second hop + FULL_SHARE, // full share for the second hop + uint16(secondHopData.length), + secondHopData + ); + + // 4. Concatenate the commands to create the final route + return bytes.concat(firstCommand, secondCommand); + } + + // Helper function to verify multi-hop results + function _verifyMultiHopResults( + MultiHopTestState memory state, + uint256 initialBalanceA, + uint256 initialBalanceC + ) private { + uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( + USER_SENDER + ); + uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( + USER_SENDER + ); + + assertApproxEqAbs( + initialBalanceA - finalBalanceA, + state.amountIn, + 1, // 1 wei tolerance + "TokenA spent amount mismatch" + ); + assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); + + emit log_named_uint( + state.isFeeOnTransfer + ? "Output amount with fee tokens" + : "Output amount with regular tokens", + finalBalanceC - initialBalanceC + ); + } + + // Helper function to create an Algebra pool + function _createAlgebraPool( + address tokenA, + address tokenB + ) internal returns (address pool) { + // Call the actual Algebra factory to create a pool + pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( + tokenA, + tokenB + ); + return pool; + } + + // Helper function to add liquidity to a pool + function _addLiquidityToPool( + address pool, + address token0, + address token1 + ) internal { + // For fee-on-transfer tokens, we need to send more to account for the fee + // We'll use a small amount and send extra to cover fees + uint256 initialAmount0 = 1e17; // 0.1 token + uint256 initialAmount1 = 1e17; // 0.1 token + + // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) + uint256 transferAmount0 = (initialAmount0 * 110) / 100; + uint256 transferAmount1 = (initialAmount1 * 110) / 100; + + // Initialize with 1:1 price ratio (Q64.96 format) + uint160 initialPrice = uint160(1 << 96); + IAlgebraPool(pool).initialize(initialPrice); + + // Create AlgebraLiquidityAdderHelper with safe transfer logic + AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( + token0, + token1 + ); + + // Transfer tokens with extra amounts to account for fees + IERC20(token0).transfer( + address(algebraLiquidityAdderHelper), + transferAmount0 + ); + IERC20(token1).transfer( + address(algebraLiquidityAdderHelper), + transferAmount1 + ); + + // Get actual balances to use for liquidity, accounting for any fees + uint256 actualBalance0 = IERC20(token0).balanceOf( + address(algebraLiquidityAdderHelper) + ); + uint256 actualBalance1 = IERC20(token1).balanceOf( + address(algebraLiquidityAdderHelper) + ); + + // Use the smaller of the two balances for liquidity amount + uint128 liquidityAmount = uint128( + actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 + ); + + // Add liquidity using the actual token amounts we have + algebraLiquidityAdderHelper.addLiquidity( + pool, + -887220, + 887220, + liquidityAmount / 2 // Use half of available liquidity to ensure success + ); + } + + struct MultiHopTestState { + IERC20 tokenA; + IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken + IERC20 tokenC; + address pool1; + address pool2; + uint256 amountIn; + uint256 amountToTransfer; + bool isFeeOnTransfer; + } + + struct AlgebraRouteParams { + CommandType commandCode; // 1 for contract funds, 2 for user funds + address tokenIn; // Input token address + address recipient; // Address receiving the output tokens + address pool; // Algebra pool address + bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens + } + + // Helper function to build route for Apechain Algebra swap + function _buildAlgebraSwapData( + AlgebraRouteParams memory params + ) private view returns (bytes memory) { + address token0 = IAlgebraPool(params.pool).token0(); + bool zeroForOne = (params.tokenIn == token0); + SwapDirection direction = zeroForOne + ? SwapDirection.Token0ToToken1 + : SwapDirection.Token1ToToken0; + + // This data blob is what the AlgebraFacet will receive and parse + return + abi.encodePacked( + AlgebraFacet.swapAlgebra.selector, + params.pool, + uint8(direction), + params.recipient, + params.supportsFeeOnTransfer ? uint8(1) : uint8(0) + ); + } + + // Helper function to test an Algebra swap + function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { + // Find or create a pool + address pool = _getPool(params.tokenIn, params.tokenOut); + vm.label(pool, "AlgebraPool"); + + // Get token0 from pool for labeling + address token0 = IAlgebraPool(pool).token0(); + if (params.tokenIn == token0) { + vm.label( + params.tokenIn, + string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") + ); + vm.label( + params.tokenOut, + string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") + ); + } else { + vm.label( + params.tokenIn, + string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") + ); + vm.label( + params.tokenOut, + string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") + ); + } + + // Record initial balances + uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + + // Get expected output from QuoterV2 + uint256 expectedOutput = _getQuoteExactInput( + params.tokenIn, + params.tokenOut, + params.amountIn + ); + + // 1. Pack the specific data for this swap + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper + tokenIn: params.tokenIn, + recipient: params.to, + pool: pool, + supportsFeeOnTransfer: params.supportsFeeOnTransfer + }) + ); + + // 2. Approve tokens + IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); + + // 3. Set up event expectations + address fromAddress = params.from == address(coreRouteFacet) + ? USER_SENDER + : params.from; + + vm.expectEmit(true, true, true, false); + emit Route( + fromAddress, + params.to, + params.tokenIn, + params.tokenOut, + params.amountIn, + expectedOutput, + expectedOutput + ); + + // 4. Build the route inline and execute the swap to save stack space + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + (expectedOutput * 995) / 1000, // minOut calculated inline + params.to, + abi.encodePacked( + uint8( + params.from == address(coreRouteFacet) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 + ), + params.tokenIn, + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ) + ); + + // 5. Verify final balances + uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + + assertApproxEqAbs( + initialTokenIn - finalTokenIn, + params.amountIn, + 1, + "TokenIn amount mismatch" + ); + assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); + } + + function _getPool( + address tokenA, + address tokenB + ) private view returns (address pool) { + pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( + tokenA, + tokenB + ); + if (pool == address(0)) revert PoolDoesNotExist(); + return pool; + } + + function _getQuoteExactInput( + address tokenIn, + address tokenOut, + uint256 amountIn + ) private returns (uint256 amountOut) { + (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) + .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); + return amountOut; + } + + function testRevert_AlgebraSwap_ZeroAddressPool() public { + // Transfer tokens from whale to user + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Mock token0() call on address(0) + vm.mockCall( + address(0), + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(APE_ETH_TOKEN) + ); + + // Build route with address(0) as pool + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: USER_SENDER, + pool: address(0), // Zero address pool + supportsFeeOnTransfer: true + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Approve tokens + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + 1 * 1e18, + address(WETH_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { + // // Transfer tokens from whale to user + // vm.prank(APE_ETH_HOLDER_APECHAIN); + // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + // vm.startPrank(USER_SENDER); + + // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS + // vm.mockCall( + // IMPOSSIBLE_POOL_ADDRESS, + // abi.encodeWithSelector(IAlgebraPool.token0.selector), + // abi.encode(APE_ETH_TOKEN) + // ); + + // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool + // bytes memory swapData = _buildAlgebraSwapData( + // AlgebraRouteParams({ + // commandCode: CommandType.ProcessUserERC20, + // tokenIn: APE_ETH_TOKEN, + // recipient: USER_SENDER, + // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address + // supportsFeeOnTransfer: true + // }) + // ); + + // bytes memory route = abi.encodePacked( + // uint8(CommandType.ProcessUserERC20), + // APE_ETH_TOKEN, + // uint8(1), // number of pools/splits + // FULL_SHARE, // 100% share + // uint16(swapData.length), // <--- Add the length prefix + // swapData + // ); + + // // Approve tokens + // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + + // // Expect revert with InvalidCallData + // vm.expectRevert(InvalidCallData.selector); + + // coreRouteFacet.processRoute( + // APE_ETH_TOKEN, + // 1 * 1e18, + // address(WETH_TOKEN), + // 0, + // USER_SENDER, + // route + // ); + + // vm.stopPrank(); + // vm.clearMockedCalls(); + // } + + function testRevert_AlgebraSwap_ZeroAddressRecipient() public { + // Transfer tokens from whale to user + vm.prank(APE_ETH_HOLDER_APECHAIN); + IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Mock token0() call on the pool + vm.mockCall( + ALGEBRA_POOL_APECHAIN, + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(APE_ETH_TOKEN) + ); + + // Build route with address(0) as recipient + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: APE_ETH_TOKEN, + recipient: address(0), // Zero address recipient + pool: ALGEBRA_POOL_APECHAIN, + supportsFeeOnTransfer: true + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + APE_ETH_TOKEN, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // <--- Add the length prefix + swapData + ); + + // Approve tokens + IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + + // Expect revert with InvalidCallData + vm.expectRevert(InvalidCallData.selector); + + coreRouteFacet.processRoute( + APE_ETH_TOKEN, + 1 * 1e18, + address(WETH_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol new file mode 100644 index 000000000..7dbcac330 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; +import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; + +contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { + /// @dev HyperswapV3 router on HyperEVM chain + IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = + IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); + /// @dev HyperswapV3 quoter on HyperEVM chain + IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = + IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); + + /// @dev a liquid USDT on HyperEVM + IERC20 internal constant USDT0 = + IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); + /// @dev WHYPE on HyperEVM + IERC20 internal constant WHYPE = + IERC20(0x5555555555555555555555555555555555555555); + + struct HyperswapV3Params { + CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 + address tokenIn; // Input token address + address recipient; // Address receiving the output tokens + address pool; // HyperswapV3 pool address + bool zeroForOne; // Direction of the swap + } + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_HYPEREVM", + blockNumber: 4433562 + }); + } + + function _addDexFacet() internal override { + uniV3Facet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3Facet.swapUniV3.selector; + functionSelectors[1] = uniV3Facet.hyperswapV3SwapCallback.selector; + addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); + + uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + + deal(address(USDT0), USER_SENDER, amountIn); + + // user approves + vm.prank(USER_SENDER); + USDT0.approve(address(ldaDiamond), amountIn); + + // fetch the real pool and quote + address pool = HYPERSWAP_FACTORY.getPool( + address(USDT0), + address(WHYPE), + 3000 + ); + + // Create the params struct for quoting + IHyperswapV3QuoterV2.QuoteExactInputSingleParams + memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: amountIn, + fee: 3000, + sqrtPriceLimitX96: 0 + }); + + // Get the quote using the struct + (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + params + ); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: pool, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDT0), + uint8(1), // 1 pool + FULL_SHARE, // FULL_SHARE + uint16(swapData.length), // length prefix + swapData + ); + + // expect the Route event + vm.expectEmit(true, true, true, true); + emit Route( + USER_SENDER, + USER_SENDER, + address(USDT0), + address(WHYPE), + amountIn, + quoted, + quoted + ); + + // execute + vm.prank(USER_SENDER); + coreRouteFacet.processRoute( + address(USDT0), + amountIn, + address(WHYPE), + quoted, + USER_SENDER, + route + ); + } + + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + + // Fund dex aggregator contract + deal(address(USDT0), address(ldaDiamond), amountIn); + + // fetch the real pool and quote + address pool = HYPERSWAP_FACTORY.getPool( + address(USDT0), + address(WHYPE), + 3000 + ); + + // Create the params struct for quoting + IHyperswapV3QuoterV2.QuoteExactInputSingleParams + memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection + fee: 3000, + sqrtPriceLimitX96: 0 + }); + + // Get the quote using the struct + (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + params + ); + + // Build route using our helper function + // bytes memory route = _buildHyperswapV3Route( + // HyperswapV3Params({ + // commandCode: CommandType.ProcessMyERC20, + // tokenIn: address(USDT0), + // recipient: USER_SENDER, + // pool: pool, + // zeroForOne: true // USDT0 < WHYPE + // }) + // ); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: pool, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(USDT0), + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + // expect the Route event + vm.expectEmit(true, true, true, true); + emit Route( + USER_SENDER, + USER_SENDER, + address(USDT0), + address(WHYPE), + amountIn - 1, // Account for slot undrain protection + quoted, + quoted + ); + + // execute + vm.prank(USER_SENDER); + coreRouteFacet.processRoute( + address(USDT0), + amountIn - 1, // Account for slot undrain protection + address(WHYPE), + quoted, + USER_SENDER, + route + ); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. + // HyperswapV3 does not support a "one-pool" second hop today, because + // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. HyperswapV3's swap() immediately reverts on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } +} diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol new file mode 100644 index 000000000..691e169b0 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -0,0 +1,525 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; + +contract IzumiV3FacetTest is BaseDexFacetTest { + IzumiV3Facet internal izumiV3Facet; + + // ==================== iZiSwap V3 specific variables ==================== + // Base constants + address internal constant USDC = + 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; + address internal constant WETH = + 0x4200000000000000000000000000000000000006; + address internal constant USDB_C = + 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; + + // iZiSwap pools + address internal constant IZUMI_WETH_USDC_POOL = + 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; + address internal constant IZUMI_WETH_USDB_C_POOL = + 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; + + // Test parameters + uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals + uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals + + // structs + struct IzumiV3SwapTestParams { + address from; + address to; + address tokenIn; + uint256 amountIn; + address tokenOut; + SwapDirection direction; + } + + struct MultiHopTestParams { + address tokenIn; + address tokenMid; + address tokenOut; + address pool1; + address pool2; + uint256 amountIn; + SwapDirection direction1; + SwapDirection direction2; + } + + error IzumiV3SwapUnexpected(); + error IzumiV3SwapCallbackUnknownSource(); + error IzumiV3SwapCallbackNotPositiveAmount(); + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_BASE", + blockNumber: 29831758 + }); + } + + function _addDexFacet() internal override { + izumiV3Facet = new IzumiV3Facet(); + bytes4[] memory functionSelectors = new bytes4[](3); + functionSelectors[0] = izumiV3Facet.swapIzumiV3.selector; + functionSelectors[1] = izumiV3Facet.swapX2YCallback.selector; + functionSelectors[2] = izumiV3Facet.swapY2XCallback.selector; + addFacet( + address(ldaDiamond), + address(izumiV3Facet), + functionSelectors + ); + + izumiV3Facet = IzumiV3Facet(payable(address(ldaDiamond))); + } + + function test_CanSwap_FromDexAggregator() public override { + // Test USDC -> WETH + deal(USDC, address(coreRouteFacet), AMOUNT_USDC); + + vm.startPrank(USER_SENDER); + _testSwap( + IzumiV3SwapTestParams({ + from: address(coreRouteFacet), + to: USER_SENDER, + tokenIn: USDC, + amountIn: AMOUNT_USDC, + tokenOut: WETH, + direction: SwapDirection.Token1ToToken0 + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + _testMultiHopSwap( + MultiHopTestParams({ + tokenIn: USDC, + tokenMid: WETH, + tokenOut: USDB_C, + pool1: IZUMI_WETH_USDC_POOL, + pool2: IZUMI_WETH_USDB_C_POOL, + amountIn: AMOUNT_USDC, + direction1: SwapDirection.Token1ToToken0, + direction2: SwapDirection.Token0ToToken1 + }) + ); + } + + function test_CanSwap() public override { + deal(address(USDC), USER_SENDER, AMOUNT_USDC); + + vm.startPrank(USER_SENDER); + IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDC_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: USER_RECEIVER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + USDC, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + vm.expectEmit(true, true, true, false); + emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); + + coreRouteFacet.processRoute( + USDC, + AMOUNT_USDC, + WETH, + 0, + USER_RECEIVER, + route + ); + + vm.stopPrank(); + } + + function testRevert_IzumiV3SwapUnexpected() public { + deal(USDC, USER_SENDER, AMOUNT_USDC); + + vm.startPrank(USER_SENDER); + + // create invalid pool address + address invalidPool = address(0x999); + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: invalidPool, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + // create a route with an invalid pool + bytes memory invalidRoute = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + USDC, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); + + // mock the iZiSwap pool to return without updating lastCalledPool + vm.mockCall( + invalidPool, + abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), + abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool + ); + + vm.expectRevert(IzumiV3SwapUnexpected.selector); + + coreRouteFacet.processRoute( + USDC, + AMOUNT_USDC, + WETH, + 0, + USER_SENDER, + invalidRoute + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + function testRevert_UnexpectedCallbackSender() public { + deal(USDC, USER_SENDER, AMOUNT_USDC); + + // Set up the expected callback sender through the diamond + vm.store( + address(ldaDiamond), + keccak256("com.lifi.lda.callbackmanager"), + bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) + ); + + // Try to call callback from a different address than expected + address unexpectedCaller = address(0xdead); + vm.prank(unexpectedCaller); + vm.expectRevert( + abi.encodeWithSelector( + LibCallbackManager.UnexpectedCallbackSender.selector, + unexpectedCaller, + IZUMI_WETH_USDC_POOL + ) + ); + izumiV3Facet.swapY2XCallback(1, 1, abi.encode(USDC)); + } + + function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { + deal(USDC, USER_SENDER, AMOUNT_USDC); + + // Set the expected callback sender through the diamond storage + vm.store( + address(ldaDiamond), + keccak256("com.lifi.lda.callbackmanager"), + bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) + ); + + // try to call the callback with zero amount + vm.prank(IZUMI_WETH_USDC_POOL); + vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); + izumiV3Facet.swapY2XCallback( + 0, + 0, // zero amount should trigger the error + abi.encode(USDC) + ); + } + + function testRevert_FailsIfAmountInIsTooLarge() public { + deal(address(WETH), USER_SENDER, type(uint256).max); + + vm.startPrank(USER_SENDER); + IERC20(WETH).approve(address(ldaDiamond), type(uint256).max); + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDC_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_RECEIVER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + WETH, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + WETH, + type(uint216).max, + USDC, + 0, + USER_RECEIVER, + route + ); + + vm.stopPrank(); + } + + function _testSwap(IzumiV3SwapTestParams memory params) internal { + // Fund the sender with tokens if not the contract itself + if (params.from != address(coreRouteFacet)) { + deal(params.tokenIn, params.from, params.amountIn); + } + + // Capture initial token balances + uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( + params.from + ); + uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( + params.to + ); + + // Build the route based on the command type + CommandType commandCode = params.from == address(coreRouteFacet) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20; + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDC_POOL, + direction: params.direction == SwapDirection.Token0ToToken1 + ? SwapDirection.Token0ToToken1 + : SwapDirection.Token1ToToken0, + recipient: params.to + }) + ); + + bytes memory route = abi.encodePacked( + uint8(commandCode), + params.tokenIn, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + uint16(swapData.length), // length prefix + swapData + ); + + // Approve tokens if necessary + if (params.from == USER_SENDER) { + vm.startPrank(USER_SENDER); + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + } + + // Expect the Route event emission + address from = params.from == address(coreRouteFacet) + ? USER_SENDER + : params.from; + + vm.expectEmit(true, true, true, false); + emit Route( + from, + params.to, + params.tokenIn, + params.tokenOut, + params.amountIn, + 0, // No minimum amount enforced in test + 0 // Actual amount will be checked after the swap + ); + + // Execute the swap + uint256 amountOut = coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + 0, // No minimum amount for testing + params.to, + route + ); + + if (params.from == USER_SENDER) { + vm.stopPrank(); + } + + // Verify balances have changed correctly + uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); + + assertApproxEqAbs( + initialBalanceIn - finalBalanceIn, + params.amountIn, + 1, // 1 wei tolerance because of undrain protection for dex aggregator + "TokenIn amount mismatch" + ); + assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); + assertEq( + amountOut, + finalBalanceOut - initialBalanceOut, + "AmountOut mismatch" + ); + + emit log_named_uint("Amount In", params.amountIn); + emit log_named_uint("Amount Out", amountOut); + } + + function _testMultiHopSwap(MultiHopTestParams memory params) internal { + // Fund the sender with tokens + deal(params.tokenIn, USER_SENDER, params.amountIn); + + // Capture initial token balances + uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( + USER_SENDER + ); + uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( + USER_SENDER + ); + + // Build first swap data + bytes memory firstSwapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: params.pool1, + direction: params.direction1, + recipient: address(coreRouteFacet) + }) + ); + + bytes memory firstHop = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + params.tokenIn, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(firstSwapData.length), // length prefix + firstSwapData + ); + + // Build second swap data + bytes memory secondSwapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: params.pool2, + direction: params.direction2, + recipient: USER_SENDER + }) + ); + + bytes memory secondHop = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + params.tokenMid, + uint8(1), // number of pools/splits + FULL_SHARE, // 100% share + uint16(secondSwapData.length), // length prefix + secondSwapData + ); + + // Combine into route + bytes memory route = bytes.concat(firstHop, secondHop); + + // Approve tokens + vm.startPrank(USER_SENDER); + IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); + + // Execute the swap + uint256 amountOut = coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + 0, // No minimum amount for testing + USER_SENDER, + route + ); + vm.stopPrank(); + + // Verify balances have changed correctly + uint256 finalBalanceIn; + uint256 finalBalanceOut; + + finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); + finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); + + assertEq( + initialBalanceIn - finalBalanceIn, + params.amountIn, + "TokenIn amount mismatch" + ); + assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); + assertEq( + amountOut, + finalBalanceOut - initialBalanceOut, + "AmountOut mismatch" + ); + } + + function _buildIzumiV3Route( + CommandType commandCode, + address tokenIn, + uint8 direction, + address pool, + address recipient + ) internal pure returns (bytes memory) { + return + abi.encodePacked( + uint8(commandCode), + tokenIn, + uint8(1), // number of pools (1) + FULL_SHARE, // 100% share + IzumiV3Facet.swapIzumiV3.selector, + pool, + uint8(direction), + recipient + ); + } + + function _buildIzumiV3MultiHopRoute( + MultiHopTestParams memory params + ) internal view returns (bytes memory) { + // First hop: USER_ERC20 -> LDA + bytes memory firstHop = _buildIzumiV3Route( + CommandType.ProcessUserERC20, + params.tokenIn, + uint8(params.direction1), + params.pool1, + address(coreRouteFacet) + ); + + // Second hop: MY_ERC20 (LDA) -> pool2 + bytes memory secondHop = _buildIzumiV3Route( + CommandType.ProcessMyERC20, + params.tokenMid, + uint8(params.direction2), + params.pool2, + USER_SENDER // final recipient + ); + + // Combine the two hops + return bytes.concat(firstHop, secondHop); + } + + struct IzumiV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _buildIzumiV3SwapData( + IzumiV3SwapParams memory params + ) internal view returns (bytes memory) { + return + abi.encodePacked( + izumiV3Facet.swapIzumiV3.selector, + params.pool, + uint8(params.direction), + params.recipient + ); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol new file mode 100644 index 000000000..83fc1ebfb --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; + +contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { + IERC20 internal constant WHYPE = + IERC20(0x5555555555555555555555555555555555555555); + IERC20 internal constant LHYPE = + IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); + + address internal constant WHYPE_LHYPE_POOL = + 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_HYPEREVM", + blockNumber: 4433562 + }); + } + + function _addDexFacet() internal override { + uniV3Facet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3Facet.swapUniV3.selector; + functionSelectors[1] = uniV3Facet.laminarV3SwapCallback.selector; + addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); + + uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e18; + + // Fund the user with WHYPE + deal(address(WHYPE), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + WHYPE.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: WHYPE_LHYPE_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(WHYPE), + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances + uint256 inBefore = WHYPE.balanceOf(USER_SENDER); + uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + + // Execute swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(WHYPE), + amountIn, + address(LHYPE), + 0, + USER_SENDER, + route + ); + + // Verify + uint256 inAfter = WHYPE.balanceOf(USER_SENDER); + uint256 outAfter = LHYPE.balanceOf(USER_SENDER); + assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 1_000 * 1e18; + + // fund the aggregator directly + deal(address(WHYPE), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: WHYPE_LHYPE_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(WHYPE), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + + // Withdraw 1 wei to avoid slot-undrain protection + coreRouteFacet.processRoute( + address(WHYPE), + amountIn - 1, + address(LHYPE), + 0, + USER_SENDER, + route + ); + + uint256 outAfter = LHYPE.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + + vm.stopPrank(); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol new file mode 100644 index 000000000..88c60af96 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -0,0 +1,838 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; +import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; +import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; +import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; + +contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + function hook( + address sender, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external { + emit HookCalled(sender, amount0, amount1, data); + } +} + +contract VelodromeV2FacetTest is BaseDexFacetTest { + VelodromeV2Facet internal velodromeV2Facet; + + // ==================== Velodrome V2 specific variables ==================== + IVelodromeV2Router internal constant VELODROME_V2_ROUTER = + IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router + address internal constant VELODROME_V2_FACTORY_REGISTRY = + 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; + IERC20 internal constant USDC_TOKEN = + IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); + IERC20 internal constant STG_TOKEN = + IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); + IERC20 internal constant USDC_E_TOKEN = + IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); + + MockVelodromeV2FlashLoanCallbackReceiver + internal mockFlashloanCallbackReceiver; + + // Velodrome V2 structs + struct VelodromeV2SwapTestParams { + address from; + address to; + address tokenIn; + uint256 amountIn; + address tokenOut; + bool stable; + SwapDirection direction; + bool callback; + } + + struct MultiHopTestParams { + address tokenIn; + address tokenMid; + address tokenOut; + address pool1; + address pool2; + uint256[] amounts1; + uint256[] amounts2; + uint256 pool1Fee; + uint256 pool2Fee; + } + + struct ReserveState { + uint256 reserve0Pool1; + uint256 reserve1Pool1; + uint256 reserve0Pool2; + uint256 reserve1Pool2; + } + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_OPTIMISM", + blockNumber: 133999121 + }); + } + + function _addDexFacet() internal override { + velodromeV2Facet = new VelodromeV2Facet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; + addFacet( + address(ldaDiamond), + address(velodromeV2Facet), + functionSelectors + ); + + velodromeV2Facet = VelodromeV2Facet(payable(address(ldaDiamond))); + } + + // ============================ Velodrome V2 Tests ============================ + + // no stable swap + function test_CanSwap() public override { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + + _testSwap( + VelodromeV2SwapTestParams({ + from: address(USER_SENDER), + to: address(USER_SENDER), + tokenIn: address(USDC_TOKEN), + amountIn: 1_000 * 1e6, + tokenOut: address(STG_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callback: false + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_NoStable_Reverse() public { + // first perform the forward swap. + test_CanSwap(); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(STG_TOKEN), + amountIn: 500 * 1e18, + tokenOut: address(USDC_TOKEN), + stable: false, + direction: SwapDirection.Token1ToToken0, + callback: false + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_Stable() public { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(USDC_TOKEN), + amountIn: 1_000 * 1e6, + tokenOut: address(USDC_E_TOKEN), + stable: true, + direction: SwapDirection.Token0ToToken1, + callback: false + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_Stable_Reverse() public { + // first perform the forward stable swap. + test_CanSwap_Stable(); + + vm.startPrank(USER_SENDER); + + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(USDC_E_TOKEN), + amountIn: 500 * 1e6, + tokenOut: address(USDC_TOKEN), + stable: false, + direction: SwapDirection.Token1ToToken0, + callback: false + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + // fund dex aggregator contract so that the contract holds USDC + deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: address(ldaDiamond), + to: address(USER_SENDER), + tokenIn: address(USDC_TOKEN), + amountIn: IERC20(address(USDC_TOKEN)).balanceOf( + address(ldaDiamond) + ) - 1, // adjust for slot undrain protection: subtract 1 token so that the + // aggregator's balance isn't completely drained, matching the contract's safeguard + tokenOut: address(USDC_E_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callback: false + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_FlashloanCallback() public { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: address(USER_SENDER), + to: address(mockFlashloanCallbackReceiver), + tokenIn: address(USDC_TOKEN), + amountIn: 1_000 * 1e6, + tokenOut: address(USDC_E_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callback: true + }) + ); + vm.stopPrank(); + } + + // Override the abstract test with VelodromeV2 implementation + function test_CanSwap_MultiHop() public override { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + + // Setup routes and get amounts + MultiHopTestParams memory params = _setupRoutes( + address(USDC_TOKEN), + address(STG_TOKEN), + address(USDC_E_TOKEN), + false, + false + ); + + // Get initial reserves BEFORE the swap + ReserveState memory initialReserves; + ( + initialReserves.reserve0Pool1, + initialReserves.reserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + initialReserves.reserve0Pool2, + initialReserves.reserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( + USER_SENDER + ); + uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( + USER_SENDER + ); + + // Build route and execute swap + bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); + + // Approve and execute + IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); + + vm.expectEmit(true, true, true, true); + emit Route( + USER_SENDER, + USER_SENDER, + params.tokenIn, + params.tokenOut, + 1000 * 1e6, + params.amounts2[1], + params.amounts2[1] + ); + + coreRouteFacet.processRoute( + params.tokenIn, + 1000 * 1e6, + params.tokenOut, + params.amounts2[1], + USER_SENDER, + route + ); + + _verifyUserBalances(params, initialBalance1, initialBalance2); + _verifyReserves(params, initialReserves); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop_WithStable() public { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + + // Setup routes and get amounts for stable->volatile path + MultiHopTestParams memory params = _setupRoutes( + address(USDC_TOKEN), + address(USDC_E_TOKEN), + address(STG_TOKEN), + true, // stable pool for first hop + false // volatile pool for second hop + ); + + // Get initial reserves BEFORE the swap + ReserveState memory initialReserves; + ( + initialReserves.reserve0Pool1, + initialReserves.reserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + initialReserves.reserve0Pool2, + initialReserves.reserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + // Record initial balances + uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( + USER_SENDER + ); + uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( + USER_SENDER + ); + + // Build route and execute swap + bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + + // Approve and execute + IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); + + // vm.expectEmit(true, true, true, true); + // emit Route( + // USER_SENDER, + // USER_SENDER, + // params.tokenIn, + // params.tokenOut, + // 1000 * 1e6, + // params.amounts2[1], + // params.amounts2[1] + // ); + + coreRouteFacet.processRoute( + params.tokenIn, + 1000 * 1e6, + params.tokenOut, + params.amounts2[1], + USER_SENDER, + route + ); + + _verifyUserBalances(params, initialBalance1, initialBalance2); + _verifyReserves(params, initialReserves); + + vm.stopPrank(); + } + + function testRevert_InvalidPoolOrRecipient() public { + vm.startPrank(USER_SENDER); + + // Get a valid pool address first for comparison + address validPool = VELODROME_V2_ROUTER.poolFor( + address(USDC_TOKEN), + address(STG_TOKEN), + false, + VELODROME_V2_FACTORY_REGISTRY + ); + + // --- Test case 1: Zero pool address --- + // 1. Create the specific swap data blob + bytes memory swapDataZeroPool = abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + address(0), // Invalid pool + uint8(SwapDirection.Token1ToToken0), + USER_SENDER, + uint8(CallbackStatus.Disabled) + ); + + // 2. Create the full route with the length-prefixed swap data + bytes memory routeWithZeroPool = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDC_TOKEN), + uint8(1), + FULL_SHARE, + uint16(swapDataZeroPool.length), // Length prefix + swapDataZeroPool + ); + + IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(USDC_TOKEN), + 1000 * 1e6, + address(STG_TOKEN), + 0, + USER_SENDER, + routeWithZeroPool + ); + + // --- Test case 2: Zero recipient address --- + bytes memory swapDataZeroRecipient = abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + validPool, + uint8(SwapDirection.Token1ToToken0), + address(0), // Invalid recipient + uint8(CallbackStatus.Disabled) + ); + + bytes memory routeWithZeroRecipient = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDC_TOKEN), + uint8(1), + FULL_SHARE, + uint16(swapDataZeroRecipient.length), // Length prefix + swapDataZeroRecipient + ); + + vm.expectRevert(InvalidCallData.selector); + coreRouteFacet.processRoute( + address(USDC_TOKEN), + 1000 * 1e6, + address(STG_TOKEN), + 0, + USER_SENDER, + routeWithZeroRecipient + ); + + vm.stopPrank(); + } + + function testRevert_WrongPoolReserves() public { + vm.startPrank(USER_SENDER); + + // Setup multi-hop route: USDC -> STG -> USDC.e + MultiHopTestParams memory params = _setupRoutes( + address(USDC_TOKEN), + address(STG_TOKEN), + address(USDC_E_TOKEN), + false, + false + ); + + // Build multi-hop route + bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + + deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); + + IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + + // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves + vm.mockCall( + params.pool2, + abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), + abi.encode(0, 0, block.timestamp) + ); + + vm.expectRevert(WrongPoolReserves.selector); + + coreRouteFacet.processRoute( + address(USDC_TOKEN), + 1000 * 1e6, + address(USDC_E_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + // ============================ Velodrome V2 Helper Functions ============================ + + /** + * @dev Helper function to test a VelodromeV2 swap. + * Uses a struct to group parameters and reduce stack depth. + */ + function _testSwap(VelodromeV2SwapTestParams memory params) internal { + // get expected output amounts from the router. + IVelodromeV2Router.Route[] + memory routes = new IVelodromeV2Router.Route[](1); + routes[0] = IVelodromeV2Router.Route({ + from: params.tokenIn, + to: params.tokenOut, + stable: params.stable, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( + params.amountIn, + routes + ); + emit log_named_uint("Expected amount out", amounts[1]); + + // Retrieve the pool address. + address pool = VELODROME_V2_ROUTER.poolFor( + params.tokenIn, + params.tokenOut, + params.stable, + VELODROME_V2_FACTORY_REGISTRY + ); + emit log_named_uint("Pool address:", uint256(uint160(pool))); + + // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. + CommandType commandCode = params.from == address(ldaDiamond) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20; + + // 1. Pack the data for the specific swap FIRST + bytes memory swapData = abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + pool, + params.direction, + params.to, + params.callback + ? uint8(CallbackStatus.Enabled) + : uint8(CallbackStatus.Disabled) + ); + // build the route. + bytes memory route = abi.encodePacked( + uint8(commandCode), + params.tokenIn, + uint8(1), // num splits + FULL_SHARE, + uint16(swapData.length), // <--- Add length prefix + swapData + ); + + // approve the aggregator to spend tokenIn. + IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); + + // capture initial token balances. + uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + emit log_named_uint("Initial tokenIn balance", initialTokenIn); + + address from = params.from == address(ldaDiamond) + ? USER_SENDER + : params.from; + if (params.callback == true) { + vm.expectEmit(true, false, false, false); + emit HookCalled( + address(ldaDiamond), + 0, + 0, + abi.encode(params.tokenIn) + ); + } + vm.expectEmit(true, true, true, true); + emit Route( + from, + params.to, + params.tokenIn, + params.tokenOut, + params.amountIn, + amounts[1], + amounts[1] + ); + + // execute the swap + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + amounts[1], + params.to, + route + ); + + uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); + emit log_named_uint( + "TokenOut received", + finalTokenOut - initialTokenOut + ); + + assertApproxEqAbs( + initialTokenIn - finalTokenIn, + params.amountIn, + 1, // 1 wei tolerance + "TokenIn amount mismatch" + ); + assertEq( + finalTokenOut - initialTokenOut, + amounts[1], + "TokenOut amount mismatch" + ); + } + + // Helper function to set up routes and get amounts + function _setupRoutes( + address tokenIn, + address tokenMid, + address tokenOut, + bool isStableFirst, + bool isStableSecond + ) private view returns (MultiHopTestParams memory params) { + params.tokenIn = tokenIn; + params.tokenMid = tokenMid; + params.tokenOut = tokenOut; + + // Setup first hop route + IVelodromeV2Router.Route[] + memory routes1 = new IVelodromeV2Router.Route[](1); + routes1[0] = IVelodromeV2Router.Route({ + from: tokenIn, + to: tokenMid, + stable: isStableFirst, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( + 1000 * 1e6, + routes1 + ); + + // Setup second hop route + IVelodromeV2Router.Route[] + memory routes2 = new IVelodromeV2Router.Route[](1); + routes2[0] = IVelodromeV2Router.Route({ + from: tokenMid, + to: tokenOut, + stable: isStableSecond, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( + params.amounts1[1], + routes2 + ); + + // Get pool addresses + params.pool1 = VELODROME_V2_ROUTER.poolFor( + tokenIn, + tokenMid, + isStableFirst, + VELODROME_V2_FACTORY_REGISTRY + ); + + params.pool2 = VELODROME_V2_ROUTER.poolFor( + tokenMid, + tokenOut, + isStableSecond, + VELODROME_V2_FACTORY_REGISTRY + ); + + // Get pool fees info + params.pool1Fee = IVelodromeV2PoolFactory( + VELODROME_V2_FACTORY_REGISTRY + ).getFee(params.pool1, isStableFirst); + params.pool2Fee = IVelodromeV2PoolFactory( + VELODROME_V2_FACTORY_REGISTRY + ).getFee(params.pool2, isStableSecond); + + return params; + } + + // function to build first hop of the route + function _buildFirstHop( + address pool1, + address pool2, // The recipient of the first hop is the next pool + uint8 direction + ) private pure returns (bytes memory) { + return + abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + pool1, + direction, + pool2, // Send intermediate tokens to the next pool for the second hop + uint8(CallbackStatus.Disabled) + ); + } + + // function to build second hop of the route + function _buildSecondHop( + address pool2, + address recipient, + uint8 direction + ) private pure returns (bytes memory) { + return + abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + pool2, + direction, + recipient, // Final recipient + uint8(CallbackStatus.Disabled) + ); + } + + // route building function + function _buildMultiHopRoute( + MultiHopTestParams memory params, + address recipient, + uint8 firstHopDirection, + uint8 secondHopDirection + ) private pure returns (bytes memory) { + // 1. Get the specific data for each hop + bytes memory firstHopData = _buildFirstHop( + params.pool1, + params.pool2, + firstHopDirection + ); + + bytes memory secondHopData = _buildSecondHop( + params.pool2, + recipient, + secondHopDirection + ); + + // 2. Assemble the first command + bytes memory firstCommand = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + params.tokenIn, + uint8(1), // num splits + FULL_SHARE, + uint16(firstHopData.length), // <--- Add length prefix + firstHopData + ); + + // 3. Assemble the second command + // The second hop takes tokens already held by the diamond, so we use ProcessOnePool + bytes memory secondCommand = abi.encodePacked( + uint8(CommandType.ProcessOnePool), + params.tokenMid, + uint16(secondHopData.length), // <--- Add length prefix + secondHopData + ); + + // 4. Concatenate the commands to create the final route + return bytes.concat(firstCommand, secondCommand); + } + + function _verifyUserBalances( + MultiHopTestParams memory params, + uint256 initialBalance1, + uint256 initialBalance2 + ) private { + // Verify token balances + uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); + uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); + + assertApproxEqAbs( + initialBalance1 - finalBalance1, + 1000 * 1e6, + 1, // 1 wei tolerance + "Token1 spent amount mismatch" + ); + assertEq( + finalBalance2 - initialBalance2, + params.amounts2[1], + "Token2 received amount mismatch" + ); + } + + function _verifyReserves( + MultiHopTestParams memory params, + ReserveState memory initialReserves + ) private { + // Get reserves after swap + ( + uint256 finalReserve0Pool1, + uint256 finalReserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + uint256 finalReserve0Pool2, + uint256 finalReserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); + address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); + + // Calculate exact expected changes + uint256 amountInAfterFees = 1000 * + 1e6 - + ((1000 * 1e6 * params.pool1Fee) / 10000); + + // Assert exact reserve changes for Pool1 + if (token0Pool1 == params.tokenIn) { + // tokenIn is token0, so reserve0 should increase and reserve1 should decrease + assertEq( + finalReserve0Pool1 - initialReserves.reserve0Pool1, + amountInAfterFees, + "Pool1 reserve0 (tokenIn) change incorrect" + ); + assertEq( + initialReserves.reserve1Pool1 - finalReserve1Pool1, + params.amounts1[1], + "Pool1 reserve1 (tokenMid) change incorrect" + ); + } else { + // tokenIn is token1, so reserve1 should increase and reserve0 should decrease + assertEq( + finalReserve1Pool1 - initialReserves.reserve1Pool1, + amountInAfterFees, + "Pool1 reserve1 (tokenIn) change incorrect" + ); + assertEq( + initialReserves.reserve0Pool1 - finalReserve0Pool1, + params.amounts1[1], + "Pool1 reserve0 (tokenMid) change incorrect" + ); + } + + // Assert exact reserve changes for Pool2 + if (token0Pool2 == params.tokenMid) { + // tokenMid is token0, so reserve0 should increase and reserve1 should decrease + assertEq( + finalReserve0Pool2 - initialReserves.reserve0Pool2, + params.amounts1[1] - + ((params.amounts1[1] * params.pool2Fee) / 10000), + "Pool2 reserve0 (tokenMid) change incorrect" + ); + assertEq( + initialReserves.reserve1Pool2 - finalReserve1Pool2, + params.amounts2[1], + "Pool2 reserve1 (tokenOut) change incorrect" + ); + } else { + // tokenMid is token1, so reserve1 should increase and reserve0 should decrease + assertEq( + finalReserve1Pool2 - initialReserves.reserve1Pool2, + params.amounts1[1] - + ((params.amounts1[1] * params.pool2Fee) / 10000), + "Pool2 reserve1 (tokenMid) change incorrect" + ); + assertEq( + initialReserves.reserve0Pool2 - finalReserve0Pool2, + params.amounts2[1], + "Pool2 reserve0 (tokenOut) change incorrect" + ); + } + } +} diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol new file mode 100644 index 000000000..75968fa8e --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; + +contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { + address internal constant USDC_E_WXDC_POOL = + 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; + + /// @dev our two tokens: USDC.e and wrapped XDC + IERC20 internal constant USDC_E = + IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); + IERC20 internal constant WXDC = + IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_XDC", + blockNumber: 89279495 + }); + } + + function _addDexFacet() internal override { + uniV3Facet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3Facet.swapUniV3.selector; + functionSelectors[1] = uniV3Facet.xswapCallback.selector; + addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); + + uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + } + + function test_CanSwap() public override { + uint256 amountIn = 1_000 * 1e6; + deal(address(USDC_E), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + USDC_E.approve(address(ldaDiamond), amountIn); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: USDC_E_WXDC_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDC_E), + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 inBefore = USDC_E.balanceOf(USER_SENDER); + uint256 outBefore = WXDC.balanceOf(USER_SENDER); + + // Execute swap (minOut = 0 for test) + coreRouteFacet.processRoute( + address(USDC_E), + amountIn, + address(WXDC), + 0, + USER_SENDER, + route + ); + + // Verify balances after swap + uint256 inAfter = USDC_E.balanceOf(USER_SENDER); + uint256 outAfter = WXDC.balanceOf(USER_SENDER); + assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + + vm.stopPrank(); + } + + /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC + function test_CanSwap_FromDexAggregator() public override { + uint256 amountIn = 5_000 * 1e6; + + // fund the aggregator + deal(address(USDC_E), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + // Account for slot-undrain protection + uint256 swapAmount = amountIn - 1; + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: USDC_E_WXDC_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = abi.encodePacked( + uint8(CommandType.ProcessMyERC20), + address(USDC_E), + uint8(1), + FULL_SHARE, + uint16(swapData.length), // length prefix + swapData + ); + + // Record balances before swap + uint256 outBefore = WXDC.balanceOf(USER_SENDER); + + coreRouteFacet.processRoute( + address(USDC_E), + swapAmount, + address(WXDC), + 0, + USER_SENDER, + route + ); + + // Verify balances after swap + uint256 outAfter = WXDC.balanceOf(USER_SENDER); + assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. + // XSwap V3 does not support a "one-pool" second hop today, because + // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. XSwap V3's swap() immediately reverts on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools + // in a single processRoute invocation. + } +} From 3a629c552935a5d1e473d588c35d8dbaf2f79c19 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 8 Aug 2025 13:03:28 +0200 Subject: [PATCH 014/220] tests changes --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 117 +++++++--- .../Lda/BaseUniV3StyleDexFacet.t.sol | 61 ++++- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 20 +- .../Periphery/Lda/Facets/EnosysDexV3.t.sol | 132 ----------- .../Lda/Facets/EnosysDexV3Facet.t.sol | 61 +++++ .../Lda/Facets/HyperswapV3Facet.t.sol | 211 ++++++------------ .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 18 +- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 116 ++-------- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 154 ++++--------- .../Lda/Facets/SyncSwapV2Facet.t.sol | 18 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 18 +- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 119 ++-------- 12 files changed, 409 insertions(+), 636 deletions(-) delete mode 100644 test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index e6a0c1e34..31e88f702 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -68,38 +68,23 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { error WrongPoolReserves(); error PoolDoesNotExist(); - function _addDexFacet() internal virtual; + function _addDexFacet() internal virtual { + ( + address facetAddress, + bytes4[] memory functionSelectors + ) = _createFacetAndSelectors(); - // Setup function for Apechain tests - function setupApechain() internal { - customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; - customBlockNumberForForking = 12912470; - } - - function setupHyperEVM() internal { - customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; - customBlockNumberForForking = 4433562; - } + addFacet(address(ldaDiamond), facetAddress, functionSelectors); - function setupXDC() internal { - customRpcUrlForForking = "ETH_NODE_URI_XDC"; - customBlockNumberForForking = 89279495; + _setFacetInstance(payable(address(ldaDiamond))); } - function setupViction() internal { - customRpcUrlForForking = "ETH_NODE_URI_VICTION"; - customBlockNumberForForking = 94490946; - } - - function setupFlare() internal { - customRpcUrlForForking = "ETH_NODE_URI_FLARE"; - customBlockNumberForForking = 42652369; - } - - function setupLinea() internal { - customRpcUrlForForking = "ETH_NODE_URI_LINEA"; - customBlockNumberForForking = 20077881; - } + // Each facet test must implement these + function _createFacetAndSelectors() + internal + virtual + returns (address, bytes4[] memory); + function _setFacetInstance(address payable facetAddress) internal virtual; function setUp() public virtual override { // forkConfig should be set in the child contract via _setupForkConfig() @@ -179,4 +164,80 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { // solhint-disable-next-line gas-custom-errors revert("test_CanSwap_MultiHop: Not implemented"); } + + struct SwapTestParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + address sender; + address recipient; + bool isAggregatorFunds; // true for ProcessMyERC20, false for ProcessUserERC20 + } + + // Add this struct for route building + struct RouteParams { + CommandType commandType; + address tokenIn; + uint8 numPools; // defaults to 1 + uint16 share; // defaults to FULL_SHARE + bytes swapData; + } + + // Helper to build common route parts + function _buildBaseRoute( + SwapTestParams memory params, + bytes memory swapData + ) internal pure returns (bytes memory) { + return + abi.encodePacked( + uint8( + params.isAggregatorFunds + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 + ), + params.tokenIn, + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), + swapData + ); + } + + // Helper to handle common swap setup and verification + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route + ) internal { + if (!params.isAggregatorFunds) { + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + } + + uint256 inBefore; + if (params.isAggregatorFunds) { + inBefore = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + } else { + inBefore = IERC20(params.tokenIn).balanceOf(params.sender); + } + uint256 outBefore = IERC20(params.tokenOut).balanceOf( + params.recipient + ); + + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + 0, // minOut = 0 for tests + params.recipient, + route + ); + + uint256 inAfter = IERC20(params.tokenIn).balanceOf(params.sender); + uint256 outAfter = IERC20(params.tokenOut).balanceOf(params.recipient); + + assertEq(inBefore - inAfter, params.amountIn, "Token spent mismatch"); + assertGt(outAfter - outBefore, 0, "Should receive tokens"); + } } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 1b1c69071..10608514a 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -7,6 +7,33 @@ import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { UniV3StyleFacet internal uniV3Facet; + struct UniV3SwapParams { + address pool; + SwapDirection direction; + address recipient; + } + + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { + uniV3Facet = new UniV3StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV3Facet.swapUniV3.selector; + functionSelectors[1] = _getCallbackSelector(); // Each implementation provides its specific callback + return (address(uniV3Facet), functionSelectors); + } + + function _setFacetInstance( + address payable facetAddress + ) internal override { + uniV3Facet = UniV3StyleFacet(facetAddress); + } + + // Each UniV3-style DEX must implement this to provide its specific callback selector + function _getCallbackSelector() internal virtual returns (bytes4); + function test_CanSwap_MultiHop() public virtual override { // SKIPPED: UniV3 forke dex multi-hop unsupported due to AS (amount specified) requirement. // UniV3 forke dex does not support a "one-pool" second hop today, @@ -16,12 +43,6 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { // in a single processRoute invocation. } - struct UniV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - function _buildUniV3SwapData( UniV3SwapParams memory params ) internal view returns (bytes memory) { @@ -33,4 +54,32 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { params.recipient ); } + + function _executeUniV3StyleSwap( + SwapTestParams memory params, + address pool, + SwapDirection direction + ) internal { + // Fund the appropriate account + if (params.isAggregatorFunds) { + deal(params.tokenIn, address(ldaDiamond), params.amountIn); + } else { + deal(params.tokenIn, params.sender, params.amountIn); + } + + vm.startPrank(params.sender); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: pool, + direction: direction, + recipient: params.recipient + }) + ); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route); + + vm.stopPrank(); + } } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index ff5c46a18..f322f73c6 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -135,18 +135,22 @@ contract AlgebraFacetTest is BaseDexFacetTest { }); } - function _addDexFacet() internal override { + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { algebraFacet = new AlgebraFacet(); bytes4[] memory functionSelectors = new bytes4[](2); functionSelectors[0] = algebraFacet.swapAlgebra.selector; functionSelectors[1] = algebraFacet.algebraSwapCallback.selector; - addFacet( - address(ldaDiamond), - address(algebraFacet), - functionSelectors - ); + return (address(algebraFacet), functionSelectors); + } - algebraFacet = AlgebraFacet(payable(address(ldaDiamond))); + function _setFacetInstance( + address payable facetAddress + ) internal override { + algebraFacet = AlgebraFacet(facetAddress); } // Override the abstract test with Algebra implementation @@ -175,8 +179,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { } function test_CanSwap_FeeOnTransferToken() public { - setupApechain(); - uint256 amountIn = 534451326669177; vm.prank(APE_ETH_HOLDER_APECHAIN); IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol deleted file mode 100644 index 444534cce..000000000 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3.t.sol +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; - -contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { - /// @dev HLN token on Flare - IERC20 internal constant HLN = - IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); - - /// @dev USDT0 token on Flare - IERC20 internal constant USDT0 = - IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); - - /// @dev The single EnosysDexV3 pool for HLN–USDT0 - address internal constant ENOSYS_V3_POOL = - 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; - - function _setupForkConfig() internal override { - forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_FLARE", - blockNumber: 42652369 - }); - } - - function _addDexFacet() internal override { - uniV3Facet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3Facet.swapUniV3.selector; - functionSelectors[1] = uniV3Facet.enosysdexV3SwapCallback.selector; - addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); - - uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); - } - - /// @notice Single‐pool swap: USER sends HLN → receives USDT0 - function test_CanSwap() public override { - // Mint 1 000 HLN to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(HLN), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - HLN.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: ENOSYS_V3_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(HLN), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances before swap - uint256 inBefore = HLN.balanceOf(USER_SENDER); - uint256 outBefore = USDT0.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(HLN), - amountIn, - address(USDT0), - 0, - USER_SENDER, - route - ); - - // Verify that HLN was spent and some USDT0 was received - uint256 inAfter = HLN.balanceOf(USER_SENDER); - uint256 outAfter = USDT0.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - - vm.stopPrank(); - } - - /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 - function test_CanSwap_FromDexAggregator() public override { - // Fund the aggregator with 1 000 HLN - uint256 amountIn = 1_000 * 1e18; - deal(address(HLN), address(coreRouteFacet), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: ENOSYS_V3_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(HLN), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDT0.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(HLN), - swapAmount, - address(USDT0), - 0, - USER_SENDER, - route - ); - - // Verify that some USDT0 was received - uint256 outAfter = USDT0.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - - vm.stopPrank(); - } -} diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol new file mode 100644 index 000000000..c995b03e3 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; + +contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { + /// @dev HLN token on Flare + IERC20 internal constant HLN = + IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); + + /// @dev USDT0 token on Flare + IERC20 internal constant USDT0 = + IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); + + /// @dev The single EnosysDexV3 pool for HLN–USDT0 + address internal constant ENOSYS_V3_POOL = + 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_FLARE", + blockNumber: 42652369 + }); + } + + function _getCallbackSelector() internal pure override returns (bytes4) { + return UniV3StyleFacet.enosysdexV3SwapCallback.selector; + } + + function test_CanSwap() public override { + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(HLN), + tokenOut: address(USDT0), + amountIn: 1_000 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + ENOSYS_V3_POOL, + SwapDirection.Token0ToToken1 + ); + } + + function test_CanSwap_FromDexAggregator() public override { + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(HLN), + tokenOut: address(USDT0), + amountIn: 1_000 * 1e18 - 1, // Account for slot-undrain + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), + ENOSYS_V3_POOL, + SwapDirection.Token0ToToken1 + ); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 7dbcac330..75bbc97bf 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -22,14 +22,6 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { IERC20 internal constant WHYPE = IERC20(0x5555555555555555555555555555555555555555); - struct HyperswapV3Params { - CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 - address tokenIn; // Input token address - address recipient; // Address receiving the output tokens - address pool; // HyperswapV3 pool address - bool zeroForOne; // Direction of the swap - } - function _setupForkConfig() internal override { forkConfig = ForkConfig({ rpcEnvName: "ETH_NODE_URI_HYPEREVM", @@ -37,174 +29,99 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { }); } - function _addDexFacet() internal override { - uniV3Facet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3Facet.swapUniV3.selector; - functionSelectors[1] = uniV3Facet.hyperswapV3SwapCallback.selector; - addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); - - uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + function _getCallbackSelector() internal pure override returns (bytes4) { + return UniV3StyleFacet.hyperswapV3SwapCallback.selector; } function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - - deal(address(USDT0), USER_SENDER, amountIn); - - // user approves - vm.prank(USER_SENDER); - USDT0.approve(address(ldaDiamond), amountIn); - - // fetch the real pool and quote + // Get pool and quote address pool = HYPERSWAP_FACTORY.getPool( address(USDT0), address(WHYPE), 3000 ); - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn, - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params - ); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: pool, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDT0), - uint8(1), // 1 pool - FULL_SHARE, // FULL_SHARE - uint16(swapData.length), // length prefix - swapData - ); + uint256 amountIn = 1_000 * 1e6; + // (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + // IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ + // tokenIn: address(USDT0), + // tokenOut: address(WHYPE), + // amountIn: amountIn, + // fee: 3000, + // sqrtPriceLimitX96: 0 + // }) + // ); // expect the Route event - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn, - quoted, - quoted - ); + // vm.expectEmit(true, true, true, true); + // emit Route( + // USER_SENDER, + // USER_SENDER, + // address(USDT0), + // address(WHYPE), + // amountIn, + // quoted, + // quoted + // ); - // execute - vm.prank(USER_SENDER); - coreRouteFacet.processRoute( - address(USDT0), - amountIn, - address(WHYPE), - quoted, - USER_SENDER, - route + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + pool, + SwapDirection.Token1ToToken0 ); } function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - - // Fund dex aggregator contract - deal(address(USDT0), address(ldaDiamond), amountIn); - - // fetch the real pool and quote + // Get pool and quote address pool = HYPERSWAP_FACTORY.getPool( address(USDT0), address(WHYPE), 3000 ); - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params - ); + uint256 amountIn = 1_000 * 1e6; + uint256 swapAmount = amountIn - 1; // Account for slot-undrain - // Build route using our helper function - // bytes memory route = _buildHyperswapV3Route( - // HyperswapV3Params({ - // commandCode: CommandType.ProcessMyERC20, + // (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( + // IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ // tokenIn: address(USDT0), - // recipient: USER_SENDER, - // pool: pool, - // zeroForOne: true // USDT0 < WHYPE + // tokenOut: address(WHYPE), + // amountIn: swapAmount, + // fee: 3000, + // sqrtPriceLimitX96: 0 // }) // ); - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: pool, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(USDT0), - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix - swapData - ); - // expect the Route event - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn - 1, // Account for slot undrain protection - quoted, - quoted - ); + // vm.expectEmit(true, true, true, true); + // emit Route( + // USER_SENDER, + // USER_SENDER, + // address(USDT0), + // address(WHYPE), + // amountIn - 1, // Account for slot undrain protection + // quoted, + // quoted + // ); - // execute - vm.prank(USER_SENDER); - coreRouteFacet.processRoute( - address(USDT0), - amountIn - 1, // Account for slot undrain protection - address(WHYPE), - quoted, - USER_SENDER, - route + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(USDT0), + tokenOut: address(WHYPE), + amountIn: swapAmount, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), + pool, + SwapDirection.Token1ToToken0 ); } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. - // HyperswapV3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. HyperswapV3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } } diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 691e169b0..b1f6983c0 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -61,19 +61,23 @@ contract IzumiV3FacetTest is BaseDexFacetTest { }); } - function _addDexFacet() internal override { + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { izumiV3Facet = new IzumiV3Facet(); bytes4[] memory functionSelectors = new bytes4[](3); functionSelectors[0] = izumiV3Facet.swapIzumiV3.selector; functionSelectors[1] = izumiV3Facet.swapX2YCallback.selector; functionSelectors[2] = izumiV3Facet.swapY2XCallback.selector; - addFacet( - address(ldaDiamond), - address(izumiV3Facet), - functionSelectors - ); + return (address(izumiV3Facet), functionSelectors); + } - izumiV3Facet = IzumiV3Facet(payable(address(ldaDiamond))); + function _setFacetInstance( + address payable facetAddress + ) internal override { + izumiV3Facet = IzumiV3Facet(facetAddress); } function test_CanSwap_FromDexAggregator() public override { diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 83fc1ebfb..ba781f5cd 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -21,105 +21,37 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { }); } - function _addDexFacet() internal override { - uniV3Facet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3Facet.swapUniV3.selector; - functionSelectors[1] = uniV3Facet.laminarV3SwapCallback.selector; - addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); - - uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + function _getCallbackSelector() internal pure override returns (bytes4) { + return UniV3StyleFacet.laminarV3SwapCallback.selector; } function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e18; - - // Fund the user with WHYPE - deal(address(WHYPE), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WHYPE.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: WHYPE_LHYPE_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(WHYPE), - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(WHYPE), + tokenOut: address(LHYPE), + amountIn: 1_000 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + WHYPE_LHYPE_POOL, + SwapDirection.Token0ToToken1 ); - - // Record balances - uint256 inBefore = WHYPE.balanceOf(USER_SENDER); - uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - - // Execute swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(WHYPE), - amountIn, - address(LHYPE), - 0, - USER_SENDER, - route - ); - - // Verify - uint256 inAfter = WHYPE.balanceOf(USER_SENDER); - uint256 outAfter = LHYPE.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - - vm.stopPrank(); } function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the aggregator directly - deal(address(WHYPE), address(ldaDiamond), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: WHYPE_LHYPE_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(WHYPE), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(WHYPE), + tokenOut: address(LHYPE), + amountIn: 1_000 * 1e18 - 1, // Account for slot-undrain + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), + WHYPE_LHYPE_POOL, + SwapDirection.Token0ToToken1 ); - - uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - - // Withdraw 1 wei to avoid slot-undrain protection - coreRouteFacet.processRoute( - address(WHYPE), - amountIn - 1, - address(LHYPE), - 0, - USER_SENDER, - route - ); - - uint256 outAfter = LHYPE.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - - vm.stopPrank(); } } diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 4702c9080..770443836 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -22,106 +22,38 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { }); } - function _addDexFacet() internal override { - uniV3Facet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3Facet.swapUniV3.selector; - functionSelectors[1] = uniV3Facet.rabbitSwapV3SwapCallback.selector; - addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); - - uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + function _getCallbackSelector() internal pure override returns (bytes4) { + return UniV3StyleFacet.rabbitSwapV3SwapCallback.selector; } function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the user with SOROS - deal(address(SOROS), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - SOROS.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: SOROS_C98_POOL, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(SOROS), + tokenOut: address(C98), + amountIn: 1_000 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + SOROS_C98_POOL, + SwapDirection.Token1ToToken0 ); - - // record balances before swap - uint256 inBefore = SOROS.balanceOf(USER_SENDER); - uint256 outBefore = C98.balanceOf(USER_SENDER); - - // execute swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route - ); - - // verify balances after swap - uint256 inAfter = SOROS.balanceOf(USER_SENDER); - uint256 outAfter = C98.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive C98"); - - vm.stopPrank(); } function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the aggregator directly - deal(address(SOROS), address(ldaDiamond), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: SOROS_C98_POOL, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(SOROS), + tokenOut: address(C98), + amountIn: 1_000 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), + SOROS_C98_POOL, + SwapDirection.Token1ToToken0 ); - - uint256 outBefore = C98.balanceOf(USER_SENDER); - - // withdraw 1 wei less to avoid slot-undrain protection - coreRouteFacet.processRoute( - address(SOROS), - amountIn - 1, - address(C98), - 0, - USER_SENDER, - route - ); - - uint256 outAfter = C98.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive C98"); - - vm.stopPrank(); } function testRevert_RabbitSwapInvalidPool() public { @@ -131,20 +63,25 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { vm.startPrank(USER_SENDER); SOROS.approve(address(ldaDiamond), amountIn); + // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ - pool: address(0), + pool: address(0), // Invalid pool address direction: SwapDirection.Token1ToToken0, recipient: USER_SENDER }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix + // Use _buildBaseRoute from base class + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(SOROS), + tokenOut: address(C98), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapData ); @@ -168,20 +105,25 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { vm.startPrank(USER_SENDER); SOROS.approve(address(ldaDiamond), amountIn); + // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ pool: SOROS_C98_POOL, direction: SwapDirection.Token1ToToken0, - recipient: address(0) + recipient: address(0) // Invalid recipient address }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix + // Use _buildBaseRoute from base class + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(SOROS), + tokenOut: address(C98), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapData ); diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index b9065e428..9fda6b95c 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -33,17 +33,21 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }); } - function _addDexFacet() internal override { + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { syncSwapV2Facet = new SyncSwapV2Facet(); bytes4[] memory functionSelectors = new bytes4[](1); functionSelectors[0] = syncSwapV2Facet.swapSyncSwapV2.selector; - addFacet( - address(ldaDiamond), - address(syncSwapV2Facet), - functionSelectors - ); + return (address(syncSwapV2Facet), functionSelectors); + } - syncSwapV2Facet = SyncSwapV2Facet(payable(address(ldaDiamond))); + function _setFacetInstance( + address payable facetAddress + ) internal override { + syncSwapV2Facet = SyncSwapV2Facet(facetAddress); } /// @notice Single‐pool swap: USER sends WETH → receives USDC diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 88c60af96..ec624c058 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -84,17 +84,21 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { }); } - function _addDexFacet() internal override { + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { velodromeV2Facet = new VelodromeV2Facet(); bytes4[] memory functionSelectors = new bytes4[](1); functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; - addFacet( - address(ldaDiamond), - address(velodromeV2Facet), - functionSelectors - ); + return (address(velodromeV2Facet), functionSelectors); + } - velodromeV2Facet = VelodromeV2Facet(payable(address(ldaDiamond))); + function _setFacetInstance( + address payable facetAddress + ) internal override { + velodromeV2Facet = VelodromeV2Facet(facetAddress); } // ============================ Velodrome V2 Tests ============================ diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 75968fa8e..4940ab7e1 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -22,109 +22,38 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { }); } - function _addDexFacet() internal override { - uniV3Facet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3Facet.swapUniV3.selector; - functionSelectors[1] = uniV3Facet.xswapCallback.selector; - addFacet(address(ldaDiamond), address(uniV3Facet), functionSelectors); - - uniV3Facet = UniV3StyleFacet(payable(address(ldaDiamond))); + function _getCallbackSelector() internal pure override returns (bytes4) { + return UniV3StyleFacet.xswapCallback.selector; } function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e6; - deal(address(USDC_E), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - USDC_E.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: USDC_E_WXDC_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(USDC_E), + tokenOut: address(WXDC), + amountIn: 1_000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + USDC_E_WXDC_POOL, + SwapDirection.Token0ToToken1 ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDC_E), - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances before swap - uint256 inBefore = USDC_E.balanceOf(USER_SENDER); - uint256 outBefore = WXDC.balanceOf(USER_SENDER); - - // Execute swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(USDC_E), - amountIn, - address(WXDC), - 0, - USER_SENDER, - route - ); - - // Verify balances after swap - uint256 inAfter = USDC_E.balanceOf(USER_SENDER); - uint256 outAfter = WXDC.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - - vm.stopPrank(); } - /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 5_000 * 1e6; - - // fund the aggregator - deal(address(USDC_E), address(ldaDiamond), amountIn); - - vm.startPrank(USER_SENDER); - - // Account for slot-undrain protection - uint256 swapAmount = amountIn - 1; - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: USDC_E_WXDC_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(USDC_E), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData + _executeUniV3StyleSwap( + SwapTestParams({ + tokenIn: address(USDC_E), + tokenOut: address(WXDC), + amountIn: 5_000 * 1e6 - 1, // Account for slot-undrain + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), + USDC_E_WXDC_POOL, + SwapDirection.Token0ToToken1 ); - - // Record balances before swap - uint256 outBefore = WXDC.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(USDC_E), - swapAmount, - address(WXDC), - 0, - USER_SENDER, - route - ); - - // Verify balances after swap - uint256 outAfter = WXDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - - vm.stopPrank(); } function test_CanSwap_MultiHop() public override { From fcf7457c260d0891b6c632d93d7bb44d013664d0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 8 Aug 2025 13:46:14 +0200 Subject: [PATCH 015/220] tests changes --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 23 ++ .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 92 +++++++- .../Lda/Facets/SyncSwapV2Facet.t.sol | 211 ++++++++++-------- 3 files changed, 219 insertions(+), 107 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 31e88f702..4cfab6862 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -68,6 +68,10 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { error WrongPoolReserves(); error PoolDoesNotExist(); + // Add custom errors at the top of the contract + error ParamsDataLengthMismatch(); + error NoHopsProvided(); + function _addDexFacet() internal virtual { ( address facetAddress, @@ -203,6 +207,25 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { ); } + // Helper for building multi-hop route + function _buildMultiHopRoute( + SwapTestParams[] memory hopParams, + bytes[] memory hopData + ) internal pure returns (bytes memory) { + if (hopParams.length != hopData.length) + revert ParamsDataLengthMismatch(); + if (hopParams.length == 0) revert NoHopsProvided(); + + bytes memory route; + for (uint256 i = 0; i < hopParams.length; i++) { + route = bytes.concat( + route, + _buildBaseRoute(hopParams[i], hopData[i]) + ); + } + return route; + } + // Helper to handle common swap setup and verification function _executeAndVerifySwap( SwapTestParams memory params, diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index b1f6983c0..168f0df5e 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -99,18 +99,90 @@ contract IzumiV3FacetTest is BaseDexFacetTest { } function test_CanSwap_MultiHop() public override { - _testMultiHopSwap( - MultiHopTestParams({ - tokenIn: USDC, - tokenMid: WETH, - tokenOut: USDB_C, - pool1: IZUMI_WETH_USDC_POOL, - pool2: IZUMI_WETH_USDB_C_POOL, - amountIn: AMOUNT_USDC, - direction1: SwapDirection.Token1ToToken0, - direction2: SwapDirection.Token0ToToken1 + // Fund the sender with tokens + uint256 amountIn = AMOUNT_USDC; + deal(USDC, USER_SENDER, amountIn); + + // Capture initial token balances + uint256 initialBalanceIn = IERC20(USDC).balanceOf(USER_SENDER); + uint256 initialBalanceOut = IERC20(USDB_C).balanceOf(USER_SENDER); + + // Build first swap data: USDC -> WETH + bytes memory firstSwapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDC_POOL, + direction: SwapDirection.Token1ToToken0, + recipient: address(coreRouteFacet) + }) + ); + + // Build second swap data: WETH -> USDB_C + bytes memory secondSwapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: IZUMI_WETH_USDB_C_POOL, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER }) ); + + // Prepare params for both hops + SwapTestParams[] memory params = new SwapTestParams[](2); + bytes[] memory swapData = new bytes[](2); + + // First hop: USDC -> WETH + params[0] = SwapTestParams({ + tokenIn: USDC, + tokenOut: WETH, + amountIn: amountIn, + sender: USER_SENDER, + recipient: address(coreRouteFacet), + isAggregatorFunds: false // ProcessUserERC20 + }); + swapData[0] = firstSwapData; + + // Second hop: WETH -> USDB_C + params[1] = SwapTestParams({ + tokenIn: WETH, + tokenOut: USDB_C, + amountIn: 0, // Will be determined by first swap + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true // ProcessMyERC20 + }); + swapData[1] = secondSwapData; + + bytes memory route = _buildMultiHopRoute(params, swapData); + + // Approve tokens + vm.startPrank(USER_SENDER); + IERC20(USDC).approve(address(ldaDiamond), amountIn); + + // Execute the swap + uint256 amountOut = coreRouteFacet.processRoute( + USDC, + amountIn, + USDB_C, + 0, // No minimum amount for testing + USER_SENDER, + route + ); + vm.stopPrank(); + + // Verify balances + uint256 finalBalanceIn = IERC20(USDC).balanceOf(USER_SENDER); + uint256 finalBalanceOut = IERC20(USDB_C).balanceOf(USER_SENDER); + + assertEq( + initialBalanceIn - finalBalanceIn, + amountIn, + "TokenIn amount mismatch" + ); + assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); + assertEq( + amountOut, + finalBalanceOut - initialBalanceOut, + "AmountOut mismatch" + ); } function test_CanSwap() public override { diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 9fda6b95c..a55e2a650 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -69,12 +69,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapData ); @@ -120,12 +123,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapData ); @@ -170,12 +176,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn - 1, // Account for slot-undrain + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), swapData ); @@ -216,12 +225,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn - 1, // Account for slot-undrain + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), swapData ); @@ -252,13 +264,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { vm.startPrank(USER_SENDER); WETH.approve(address(ldaDiamond), amountIn); - uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); - uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); - - // - // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) - // - bytes memory swapData = _buildSyncSwapV2SwapData( + // Build swap data for both hops + bytes memory firstSwapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ pool: USDC_WETH_POOL_V1, to: SYNC_SWAP_VAULT, @@ -268,19 +275,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory routeHop1 = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // - // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 - // - bytes memory swapDataHop2 = _buildSyncSwapV2SwapData( + bytes memory secondSwapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ pool: USDC_USDT_POOL_V1, to: address(USER_SENDER), @@ -290,37 +285,47 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory routeHop2 = abi.encodePacked( - uint8(CommandType.ProcessOnePool), - address(USDC), - uint16(swapDataHop2.length), // length prefix - swapDataHop2 - ); - - bytes memory route = bytes.concat(routeHop1, routeHop2); - - uint256 amountOut = coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDT), - 0, - USER_SENDER, + // Prepare params for both hops + SwapTestParams[] memory params = new SwapTestParams[](2); + bytes[] memory swapData = new bytes[](2); + + // First hop: WETH -> USDC + params[0] = SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: SYNC_SWAP_VAULT, + isAggregatorFunds: false // ProcessUserERC20 + }); + swapData[0] = firstSwapData; + + // Second hop: USDC -> USDT + params[1] = SwapTestParams({ + tokenIn: address(USDC), + tokenOut: address(USDT), + amountIn: 0, // Not used in ProcessOnePool + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true // ProcessOnePool + }); + swapData[1] = secondSwapData; + + bytes memory route = _buildMultiHopRoute(params, swapData); + + // Use _executeAndVerifySwap with first and last token of the chain + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDT), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), route ); - uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); - uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); - - assertEq( - initialBalanceIn - afterBalanceIn, - amountIn, - "WETH spent mismatch" - ); - assertEq( - amountOut, - afterBalanceOut - initialBalanceOut, - "USDT amountOut mismatch" - ); vm.stopPrank(); } @@ -338,16 +343,19 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { to: address(USER_SENDER), withdrawMode: 2, isV1Pool: 1, - vault: address(0) + vault: address(0) // Invalid vault address }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapData ); @@ -383,12 +391,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory routeWithInvalidPool = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix + bytes memory routeWithInvalidPool = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapData ); @@ -413,12 +424,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory routeWithInvalidRecipient = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapDataWithInvalidRecipient.length), // length prefix + bytes memory routeWithInvalidRecipient = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapDataWithInvalidRecipient ); @@ -450,12 +464,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { }) ); - bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapDataWithInvalidWithdrawMode.length), // length prefix + bytes memory routeWithInvalidWithdrawMode = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: 1, // Arbitrary amount for this test + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), swapDataWithInvalidWithdrawMode ); From 5c6211ab84d275ff48753d5021126b7c1b416d1c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 8 Aug 2025 14:39:00 +0200 Subject: [PATCH 016/220] changes --- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 168f0df5e..9cd45db86 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -536,51 +536,6 @@ contract IzumiV3FacetTest is BaseDexFacetTest { ); } - function _buildIzumiV3Route( - CommandType commandCode, - address tokenIn, - uint8 direction, - address pool, - address recipient - ) internal pure returns (bytes memory) { - return - abi.encodePacked( - uint8(commandCode), - tokenIn, - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - IzumiV3Facet.swapIzumiV3.selector, - pool, - uint8(direction), - recipient - ); - } - - function _buildIzumiV3MultiHopRoute( - MultiHopTestParams memory params - ) internal view returns (bytes memory) { - // First hop: USER_ERC20 -> LDA - bytes memory firstHop = _buildIzumiV3Route( - CommandType.ProcessUserERC20, - params.tokenIn, - uint8(params.direction1), - params.pool1, - address(coreRouteFacet) - ); - - // Second hop: MY_ERC20 (LDA) -> pool2 - bytes memory secondHop = _buildIzumiV3Route( - CommandType.ProcessMyERC20, - params.tokenMid, - uint8(params.direction2), - params.pool2, - USER_SENDER // final recipient - ); - - // Combine the two hops - return bytes.concat(firstHop, secondHop); - } - struct IzumiV3SwapParams { address pool; SwapDirection direction; From 5d793629076f7ce9b626ec66f1102deb13438e59 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 11 Aug 2025 19:27:53 +0200 Subject: [PATCH 017/220] changes --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 118 +++++++++-- .../Lda/BaseUniV3StyleDexFacet.t.sol | 2 +- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 72 ++++--- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 42 ++-- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 2 +- .../Lda/Facets/SyncSwapV2Facet.t.sol | 184 ++++++++---------- .../Lda/Facets/VelodromeV2Facet.t.sol | 91 ++++----- 7 files changed, 280 insertions(+), 231 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 4cfab6862..9c5cc23f9 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -72,6 +72,16 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { error ParamsDataLengthMismatch(); error NoHopsProvided(); + // Add this struct to hold event expectations + struct ExpectedEvent { + bool checkTopic1; + bool checkTopic2; + bool checkTopic3; + bool checkData; + bytes32 eventSelector; // The event selector (keccak256 hash of the event signature) + bytes[] eventParams; // The event parameters, each encoded separately + } + function _addDexFacet() internal virtual { ( address facetAddress, @@ -128,16 +138,6 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { // assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); // } - // function testRevert_FailsIfOwnerIsZeroAddress() public { - // vm.expectRevert(InvalidConfig.selector); - - // liFiDEXAggregator = new LiFiDEXAggregator( - // address(0xCAFE), - // privileged, - // address(0) - // ); - // } - // ============================ Abstract DEX Tests ============================ /** * @notice Abstract test for basic token swapping functionality @@ -226,10 +226,11 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { return route; } - // Helper to handle common swap setup and verification + // Modified function with additional events parameter function _executeAndVerifySwap( SwapTestParams memory params, - bytes memory route + bytes memory route, + ExpectedEvent[] memory additionalEvents ) internal { if (!params.isAggregatorFunds) { IERC20(params.tokenIn).approve( @@ -239,14 +240,49 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { } uint256 inBefore; + uint256 outBefore = IERC20(params.tokenOut).balanceOf( + params.recipient + ); + + // For aggregator funds, check the diamond's balance if (params.isAggregatorFunds) { inBefore = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); } else { inBefore = IERC20(params.tokenIn).balanceOf(params.sender); } - uint256 outBefore = IERC20(params.tokenOut).balanceOf( - params.recipient - ); + + // Set up additional event expectations first + for (uint256 i = 0; i < additionalEvents.length; i++) { + vm.expectEmit( + additionalEvents[i].checkTopic1, + additionalEvents[i].checkTopic2, + additionalEvents[i].checkTopic3, + additionalEvents[i].checkData + ); + + // Encode event parameters + bytes memory encodedParams; + for ( + uint256 j = 0; + j < additionalEvents[i].eventParams.length; + j++ + ) { + encodedParams = bytes.concat( + encodedParams, + additionalEvents[i].eventParams[j] + ); + } + + // Emit the event with the correct selector and parameters + assembly { + let selector := mload( + add(mload(add(additionalEvents, 0x20)), 0x80) + ) // access eventSelector + mstore(0x00, selector) + mstore(0x04, encodedParams) + log1(0x00, add(0x04, mload(encodedParams)), selector) + } + } coreRouteFacet.processRoute( params.tokenIn, @@ -257,10 +293,58 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { route ); - uint256 inAfter = IERC20(params.tokenIn).balanceOf(params.sender); + uint256 inAfter; uint256 outAfter = IERC20(params.tokenOut).balanceOf(params.recipient); - assertEq(inBefore - inAfter, params.amountIn, "Token spent mismatch"); + // Check balance change on the correct address + if (params.isAggregatorFunds) { + inAfter = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + assertEq( + inBefore - inAfter, + params.amountIn, + "Token spent mismatch" + ); + } else { + inAfter = IERC20(params.tokenIn).balanceOf(params.sender); + assertEq( + inBefore - inAfter, + params.amountIn, + "Token spent mismatch" + ); + } + assertGt(outAfter - outBefore, 0, "Should receive tokens"); } + + // Keep the original function for backward compatibility + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route + ) internal { + _executeAndVerifySwap(params, route, new ExpectedEvent[](0)); + } + + // Keep the revert case separate + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + bytes4 expectedRevert + ) internal { + if (!params.isAggregatorFunds) { + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + } + + vm.expectRevert(expectedRevert); + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + 0, // minOut = 0 for tests + params.recipient, + route + ); + } } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 10608514a..06e2b7cd1 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -62,7 +62,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { ) internal { // Fund the appropriate account if (params.isAggregatorFunds) { - deal(params.tokenIn, address(ldaDiamond), params.amountIn); + deal(params.tokenIn, address(ldaDiamond), params.amountIn + 1); } else { deal(params.tokenIn, params.sender, params.amountIn); } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index f322f73c6..7db76fd21 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -338,9 +338,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { swapData ); - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - // Mock the algebra pool to not reset lastCalledPool vm.mockCall( invalidPool, @@ -350,16 +347,17 @@ contract AlgebraFacetTest is BaseDexFacetTest { abi.encode(0, 0) ); - // Expect the AlgebraSwapUnexpected error - vm.expectRevert(AlgebraSwapUnexpected.selector); - - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - invalidRoute + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: APE_ETH_TOKEN, + tokenOut: address(WETH_TOKEN), + amountIn: 1 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + invalidRoute, + AlgebraSwapUnexpected.selector ); vm.stopPrank(); @@ -836,19 +834,17 @@ contract AlgebraFacetTest is BaseDexFacetTest { swapData ); - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - route + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: APE_ETH_TOKEN, + tokenOut: address(WETH_TOKEN), + amountIn: 1 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + route, + InvalidCallData.selector ); vm.stopPrank(); @@ -942,19 +938,17 @@ contract AlgebraFacetTest is BaseDexFacetTest { swapData ); - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - route + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: APE_ETH_TOKEN, + tokenOut: address(WETH_TOKEN), + amountIn: 1 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + route, + InvalidCallData.selector ); vm.stopPrank(); diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 9cd45db86..77097e4a4 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -249,8 +249,6 @@ contract IzumiV3FacetTest is BaseDexFacetTest { swapData ); - IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); - // mock the iZiSwap pool to return without updating lastCalledPool vm.mockCall( invalidPool, @@ -258,15 +256,17 @@ contract IzumiV3FacetTest is BaseDexFacetTest { abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool ); - vm.expectRevert(IzumiV3SwapUnexpected.selector); - - coreRouteFacet.processRoute( - USDC, - AMOUNT_USDC, - WETH, - 0, - USER_SENDER, - invalidRoute + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: USDC, + tokenOut: WETH, + amountIn: AMOUNT_USDC, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + invalidRoute, + IzumiV3SwapUnexpected.selector ); vm.stopPrank(); @@ -320,7 +320,6 @@ contract IzumiV3FacetTest is BaseDexFacetTest { deal(address(WETH), USER_SENDER, type(uint256).max); vm.startPrank(USER_SENDER); - IERC20(WETH).approve(address(ldaDiamond), type(uint256).max); bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ @@ -339,14 +338,17 @@ contract IzumiV3FacetTest is BaseDexFacetTest { swapData ); - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - WETH, - type(uint216).max, - USDC, - 0, - USER_RECEIVER, - route + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: WETH, + tokenOut: USDC, + amountIn: type(uint216).max, + sender: USER_SENDER, + recipient: USER_RECEIVER, + isAggregatorFunds: false + }), + route, + InvalidCallData.selector ); vm.stopPrank(); diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 770443836..f98a006c7 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -46,7 +46,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(SOROS), tokenOut: address(C98), - amountIn: 1_000 * 1e18, + amountIn: 1_000 * 1e18 - 1, // Subtract 1 for slot-undrain protection sender: USER_SENDER, recipient: USER_SENDER, isAggregatorFunds: true diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index a55e2a650..f088773ae 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -57,7 +57,6 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { deal(address(WETH), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); bytes memory swapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ @@ -81,27 +80,18 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapData ); - // Record balances before swap - uint256 inBefore = WETH.balanceOf(USER_SENDER); - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), route ); - // Verify that WETH was spent and some USDC_C was received - uint256 inAfter = WETH.balanceOf(USER_SENDER); - uint256 outAfter = USDC.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - vm.stopPrank(); } @@ -111,7 +101,6 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { deal(address(WETH), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); bytes memory swapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ @@ -135,27 +124,18 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapData ); - // Record balances before swap - uint256 inBefore = WETH.balanceOf(USER_SENDER); - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), route ); - // Verify that WETH was spent and some USDC_C was received - uint256 inAfter = WETH.balanceOf(USER_SENDER); - uint256 outAfter = USDC.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - vm.stopPrank(); } @@ -188,23 +168,18 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapData ); - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(WETH), - swapAmount, - address(USDC), - 0, - USER_SENDER, + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn - 1, // Account for slot-undrain + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), route ); - // Verify that some USDC was received - uint256 outAfter = USDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - vm.stopPrank(); } @@ -237,23 +212,18 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapData ); - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(WETH), - swapAmount, - address(USDC), - 0, - USER_SENDER, + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn - 1, // Account for slot-undrain + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: true + }), route ); - // Verify that some USDC was received - uint256 outAfter = USDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - vm.stopPrank(); } @@ -335,7 +305,6 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { deal(address(WETH), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); bytes memory swapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ @@ -359,15 +328,17 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapData ); - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - route + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + route, + InvalidCallData.selector ); vm.stopPrank(); @@ -379,7 +350,6 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { deal(address(WETH), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); bytes memory swapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ @@ -403,15 +373,17 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapData ); - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidPool + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + routeWithInvalidPool, + InvalidCallData.selector ); bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( @@ -436,15 +408,17 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapDataWithInvalidRecipient ); - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidRecipient + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + routeWithInvalidRecipient, + InvalidCallData.selector ); vm.stopPrank(); @@ -476,15 +450,17 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { swapDataWithInvalidWithdrawMode ); - // Expect revert with InvalidCallData because withdrawMode is invalid - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - 1, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidWithdrawMode + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(WETH), + tokenOut: address(USDC), + amountIn: 1, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + routeWithInvalidWithdrawMode, + InvalidCallData.selector ); vm.stopPrank(); diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index ec624c058..7aff54842 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -539,60 +539,53 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // capture initial token balances. uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); emit log_named_uint("Initial tokenIn balance", initialTokenIn); - address from = params.from == address(ldaDiamond) - ? USER_SENDER - : params.from; - if (params.callback == true) { - vm.expectEmit(true, false, false, false); - emit HookCalled( - address(ldaDiamond), - 0, - 0, - abi.encode(params.tokenIn) - ); + ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](1); + if (params.callback) { + bytes[] memory eventParams = new bytes[](4); + eventParams[0] = abi.encode(address(ldaDiamond)); + eventParams[1] = abi.encode(uint256(0)); + eventParams[2] = abi.encode(uint256(0)); + eventParams[3] = abi.encode(abi.encode(params.tokenIn)); + + expectedEvents[0] = ExpectedEvent({ + checkTopic1: true, + checkTopic2: false, + checkTopic3: false, + checkData: false, + eventSelector: keccak256( + "HookCalled(address,uint256,uint256,bytes)" + ), + eventParams: eventParams + }); + } else { + expectedEvents = new ExpectedEvent[](0); } - vm.expectEmit(true, true, true, true); - emit Route( - from, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - amounts[1], - amounts[1] - ); - - // execute the swap - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - amounts[1], - params.to, - route - ); - uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); - emit log_named_uint( - "TokenOut received", - finalTokenOut - initialTokenOut - ); + // vm.expectEmit(true, true, true, true); + // emit Route( + // from, + // params.to, + // params.tokenIn, + // params.tokenOut, + // params.amountIn, + // amounts[1], + // amounts[1] + // ); - assertApproxEqAbs( - initialTokenIn - finalTokenIn, - params.amountIn, - 1, // 1 wei tolerance - "TokenIn amount mismatch" - ); - assertEq( - finalTokenOut - initialTokenOut, - amounts[1], - "TokenOut amount mismatch" + // execute the swap + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: params.amountIn, + sender: params.from, + recipient: params.to, + isAggregatorFunds: params.from == address(ldaDiamond) + }), + route, + expectedEvents ); } From 5634a52c2dfd616e2c2fc01c6dc34d0b4dcd3cb4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 11 Aug 2025 20:41:19 +0200 Subject: [PATCH 018/220] changed to _executeAndVerifySwap in Velodrome --- .../Lda/Facets/VelodromeV2Facet.t.sol | 108 ++++++++---------- 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 7aff54842..1910a561d 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -257,40 +257,34 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { ) = IVelodromeV2Pool(params.pool2).getReserves(); - uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - // Build route and execute swap bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); // Approve and execute IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - params.tokenIn, - params.tokenOut, - 1000 * 1e6, - params.amounts2[1], - params.amounts2[1] - ); + // vm.expectEmit(true, true, true, true); + // emit Route( + // USER_SENDER, + // USER_SENDER, + // params.tokenIn, + // params.tokenOut, + // 1000 * 1e6, + // params.amounts2[1], + // params.amounts2[1] + // ); - coreRouteFacet.processRoute( - params.tokenIn, - 1000 * 1e6, - params.tokenOut, - params.amounts2[1], - USER_SENDER, + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), route ); - - _verifyUserBalances(params, initialBalance1, initialBalance2); _verifyReserves(params, initialReserves); vm.stopPrank(); @@ -323,14 +317,6 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { ) = IVelodromeV2Pool(params.pool2).getReserves(); - // Record initial balances - uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - // Build route and execute swap bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); @@ -348,18 +334,18 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // params.amounts2[1] // ); - coreRouteFacet.processRoute( - params.tokenIn, - 1000 * 1e6, - params.tokenOut, - params.amounts2[1], - USER_SENDER, + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), route ); - - _verifyUserBalances(params, initialBalance1, initialBalance2); _verifyReserves(params, initialReserves); - vm.stopPrank(); } @@ -396,14 +382,17 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(USDC_TOKEN), - 1000 * 1e6, - address(STG_TOKEN), - 0, - USER_SENDER, - routeWithZeroPool + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(USDC_TOKEN), + tokenOut: address(STG_TOKEN), + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + routeWithZeroPool, + InvalidCallData.selector ); // --- Test case 2: Zero recipient address --- @@ -424,14 +413,17 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { swapDataZeroRecipient ); - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(USDC_TOKEN), - 1000 * 1e6, - address(STG_TOKEN), - 0, - USER_SENDER, - routeWithZeroRecipient + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(USDC_TOKEN), + tokenOut: address(STG_TOKEN), + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + isAggregatorFunds: false + }), + routeWithZeroRecipient, + InvalidCallData.selector ); vm.stopPrank(); From 3af3cc7f34c5a3953a75762b241b49cc3234f026 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 12 Aug 2025 14:42:50 +0200 Subject: [PATCH 019/220] changed to commandType in basedexFacet --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 49 +- .../Lda/BaseUniV3StyleDexFacet.t.sol | 3 +- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 6 +- .../Lda/Facets/EnosysDexV3Facet.t.sol | 4 +- .../Lda/Facets/HyperswapV3Facet.t.sol | 4 +- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 8 +- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 4 +- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 8 +- .../Lda/Facets/SyncSwapV2Facet.t.sol | 38 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 12 +- .../Lda/Facets/VelodromeV2Facet2.t.sol | 877 ++++++++++++++++++ .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 4 +- 12 files changed, 951 insertions(+), 66 deletions(-) create mode 100644 test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 9c5cc23f9..687c8c67e 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -22,11 +22,10 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { // Command codes for route processing enum CommandType { None, // 0 - not used - ProcessMyERC20, // 1 - processMyERC20 - ProcessUserERC20, // 2 - processUserERC20 + ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) + ProcessUserERC20, // 2 - processUserERC20 (User's funds) ProcessNative, // 3 - processNative - ProcessOnePool, // 4 - processOnePool - ProcessInsideBento, // 5 - processInsideBento + ProcessOnePool, // 4 - processOnePool (Pool's funds) ApplyPermit // 6 - applyPermit } @@ -175,7 +174,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { uint256 amountIn; address sender; address recipient; - bool isAggregatorFunds; // true for ProcessMyERC20, false for ProcessUserERC20 + CommandType commandType; } // Add this struct for route building @@ -192,19 +191,25 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { SwapTestParams memory params, bytes memory swapData ) internal pure returns (bytes memory) { - return - abi.encodePacked( - uint8( - params.isAggregatorFunds - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 - ), - params.tokenIn, - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), - swapData - ); + if (params.commandType == CommandType.ProcessOnePool) { + return + abi.encodePacked( + uint8(params.commandType), + params.tokenIn, + uint16(swapData.length), + swapData + ); + } else { + return + abi.encodePacked( + uint8(params.commandType), + params.tokenIn, + uint8(1), // one pool + FULL_SHARE, // 100% + uint16(swapData.length), + swapData + ); + } } // Helper for building multi-hop route @@ -232,7 +237,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { bytes memory route, ExpectedEvent[] memory additionalEvents ) internal { - if (!params.isAggregatorFunds) { + if (params.commandType != CommandType.ProcessMyERC20) { IERC20(params.tokenIn).approve( address(ldaDiamond), params.amountIn @@ -245,7 +250,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { ); // For aggregator funds, check the diamond's balance - if (params.isAggregatorFunds) { + if (params.commandType == CommandType.ProcessMyERC20) { inBefore = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); } else { inBefore = IERC20(params.tokenIn).balanceOf(params.sender); @@ -297,7 +302,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { uint256 outAfter = IERC20(params.tokenOut).balanceOf(params.recipient); // Check balance change on the correct address - if (params.isAggregatorFunds) { + if (params.commandType == CommandType.ProcessMyERC20) { inAfter = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); assertEq( inBefore - inAfter, @@ -330,7 +335,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { bytes memory route, bytes4 expectedRevert ) internal { - if (!params.isAggregatorFunds) { + if (params.commandType != CommandType.ProcessMyERC20) { IERC20(params.tokenIn).approve( address(ldaDiamond), params.amountIn diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 06e2b7cd1..31dec66a1 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -61,7 +61,8 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { SwapDirection direction ) internal { // Fund the appropriate account - if (params.isAggregatorFunds) { + if (params.commandType == CommandType.ProcessMyERC20) { + // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. deal(params.tokenIn, address(ldaDiamond), params.amountIn + 1); } else { deal(params.tokenIn, params.sender, params.amountIn); diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 7db76fd21..f0b952da5 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -354,7 +354,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { amountIn: 1 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), invalidRoute, AlgebraSwapUnexpected.selector @@ -841,7 +841,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { amountIn: 1 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route, InvalidCallData.selector @@ -945,7 +945,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { amountIn: 1 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route, InvalidCallData.selector diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index c995b03e3..f4c2ba568 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -37,7 +37,7 @@ contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 1_000 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), ENOSYS_V3_POOL, SwapDirection.Token0ToToken1 @@ -52,7 +52,7 @@ contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 1_000 * 1e18 - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), ENOSYS_V3_POOL, SwapDirection.Token0ToToken1 diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 75bbc97bf..4fd81683d 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -71,7 +71,7 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), pool, SwapDirection.Token1ToToken0 @@ -118,7 +118,7 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: swapAmount, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), pool, SwapDirection.Token1ToToken0 diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 77097e4a4..b75a2b75c 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -136,7 +136,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: address(coreRouteFacet), - isAggregatorFunds: false // ProcessUserERC20 + commandType: CommandType.ProcessUserERC20 }); swapData[0] = firstSwapData; @@ -147,7 +147,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { amountIn: 0, // Will be determined by first swap sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true // ProcessMyERC20 + commandType: CommandType.ProcessMyERC20 }); swapData[1] = secondSwapData; @@ -263,7 +263,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { amountIn: AMOUNT_USDC, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), invalidRoute, IzumiV3SwapUnexpected.selector @@ -345,7 +345,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { amountIn: type(uint216).max, sender: USER_SENDER, recipient: USER_RECEIVER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route, InvalidCallData.selector diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index ba781f5cd..64783fad3 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -33,7 +33,7 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 1_000 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), WHYPE_LHYPE_POOL, SwapDirection.Token0ToToken1 @@ -48,7 +48,7 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 1_000 * 1e18 - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), WHYPE_LHYPE_POOL, SwapDirection.Token0ToToken1 diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index f98a006c7..22ba37c45 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -34,7 +34,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 1_000 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), SOROS_C98_POOL, SwapDirection.Token1ToToken0 @@ -49,7 +49,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 1_000 * 1e18 - 1, // Subtract 1 for slot-undrain protection sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), SOROS_C98_POOL, SwapDirection.Token1ToToken0 @@ -80,7 +80,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapData ); @@ -122,7 +122,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapData ); diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index f088773ae..796d7d02c 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -75,7 +75,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapData ); @@ -87,7 +87,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route ); @@ -119,7 +119,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapData ); @@ -131,7 +131,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route ); @@ -163,7 +163,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), swapData ); @@ -175,7 +175,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), route ); @@ -207,7 +207,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), swapData ); @@ -219,7 +219,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), route ); @@ -266,7 +266,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: SYNC_SWAP_VAULT, - isAggregatorFunds: false // ProcessUserERC20 + commandType: CommandType.ProcessUserERC20 }); swapData[0] = firstSwapData; @@ -277,7 +277,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: 0, // Not used in ProcessOnePool sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true // ProcessOnePool + commandType: CommandType.ProcessOnePool }); swapData[1] = secondSwapData; @@ -291,7 +291,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route ); @@ -323,7 +323,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapData ); @@ -335,7 +335,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route, InvalidCallData.selector @@ -368,7 +368,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapData ); @@ -380,7 +380,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), routeWithInvalidPool, InvalidCallData.selector @@ -403,7 +403,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapDataWithInvalidRecipient ); @@ -415,7 +415,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), routeWithInvalidRecipient, InvalidCallData.selector @@ -445,7 +445,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: 1, // Arbitrary amount for this test sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), swapDataWithInvalidWithdrawMode ); @@ -457,7 +457,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { amountIn: 1, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), routeWithInvalidWithdrawMode, InvalidCallData.selector diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 1910a561d..1100fdc1b 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -281,7 +281,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route ); @@ -341,7 +341,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), route ); @@ -389,7 +389,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), routeWithZeroPool, InvalidCallData.selector @@ -420,7 +420,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), routeWithZeroRecipient, InvalidCallData.selector @@ -574,7 +574,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { amountIn: params.amountIn, sender: params.from, recipient: params.to, - isAggregatorFunds: params.from == address(ldaDiamond) + commandType: params.from == address(ldaDiamond) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 }), route, expectedEvents diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol new file mode 100644 index 000000000..5145031f8 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol @@ -0,0 +1,877 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; +import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; +import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; +import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; + +contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + function hook( + address sender, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external { + emit HookCalled(sender, amount0, amount1, data); + } +} + +contract VelodromeV2FacetTest is BaseDexFacetTest { + VelodromeV2Facet internal velodromeV2Facet; + + // ==================== Velodrome V2 specific variables ==================== + IVelodromeV2Router internal constant VELODROME_V2_ROUTER = + IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router + address internal constant VELODROME_V2_FACTORY_REGISTRY = + 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; + IERC20 internal constant USDC_TOKEN = + IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); + IERC20 internal constant STG_TOKEN = + IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); + IERC20 internal constant USDC_E_TOKEN = + IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); + + MockVelodromeV2FlashLoanCallbackReceiver + internal mockFlashloanCallbackReceiver; + + // Velodrome V2 structs + struct VelodromeV2SwapTestParams { + address from; + address to; + address tokenIn; + uint256 amountIn; + address tokenOut; + bool stable; + SwapDirection direction; + CallbackStatus callbackStatus; + } + + struct MultiHopTestParams { + address tokenIn; + address tokenMid; + address tokenOut; + address pool1; + address pool2; + uint256[] amounts1; + uint256[] amounts2; + uint256 pool1Fee; + uint256 pool2Fee; + } + + struct ReserveState { + uint256 reserve0Pool1; + uint256 reserve1Pool1; + uint256 reserve0Pool2; + uint256 reserve1Pool2; + } + + struct VelodromeV2SwapData { + address pool; + SwapDirection direction; + address recipient; + CallbackStatus callbackStatus; + } + + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + rpcEnvName: "ETH_NODE_URI_OPTIMISM", + blockNumber: 133999121 + }); + } + + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { + velodromeV2Facet = new VelodromeV2Facet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; + return (address(velodromeV2Facet), functionSelectors); + } + + function _setFacetInstance( + address payable facetAddress + ) internal override { + velodromeV2Facet = VelodromeV2Facet(facetAddress); + } + + // ============================ Velodrome V2 Tests ============================ + + // no stable swap + function test_CanSwap() public override { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + + _testSwap( + VelodromeV2SwapTestParams({ + from: address(USER_SENDER), + to: address(USER_SENDER), + tokenIn: address(USDC_TOKEN), + amountIn: 1_000 * 1e6, + tokenOut: address(STG_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callbackStatus: CallbackStatus.Disabled + }) + ); + + vm.stopPrank(); + } + + function test_CanSwap_NoStable_Reverse() public { + // first perform the forward swap. + test_CanSwap(); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(STG_TOKEN), + amountIn: 500 * 1e18, + tokenOut: address(USDC_TOKEN), + stable: false, + direction: SwapDirection.Token1ToToken0, + callbackStatus: CallbackStatus.Disabled + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_Stable() public { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(USDC_TOKEN), + amountIn: 1_000 * 1e6, + tokenOut: address(USDC_E_TOKEN), + stable: true, + direction: SwapDirection.Token0ToToken1, + callbackStatus: CallbackStatus.Disabled + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_Stable_Reverse() public { + // first perform the forward stable swap. + test_CanSwap_Stable(); + + vm.startPrank(USER_SENDER); + + _testSwap( + VelodromeV2SwapTestParams({ + from: USER_SENDER, + to: USER_SENDER, + tokenIn: address(USDC_E_TOKEN), + amountIn: 500 * 1e6, + tokenOut: address(USDC_TOKEN), + stable: false, + direction: SwapDirection.Token1ToToken0, + callbackStatus: CallbackStatus.Disabled + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + // // fund dex aggregator contract so that the contract holds USDC + deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: address(ldaDiamond), + to: address(USER_SENDER), + tokenIn: address(USDC_TOKEN), + amountIn: IERC20(address(USDC_TOKEN)).balanceOf( + address(ldaDiamond) + ) - 1, // adjust for slot undrain protection: subtract 1 token so that the + // aggregator's balance isn't completely drained, matching the contract's safeguard + tokenOut: address(USDC_E_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callbackStatus: CallbackStatus.Disabled + }) + ); + vm.stopPrank(); + } + + function test_CanSwap_FlashloanCallback() public { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); + + vm.startPrank(USER_SENDER); + _testSwap( + VelodromeV2SwapTestParams({ + from: address(USER_SENDER), + to: address(mockFlashloanCallbackReceiver), + tokenIn: address(USDC_TOKEN), + amountIn: 1_000 * 1e6, + tokenOut: address(USDC_E_TOKEN), + stable: false, + direction: SwapDirection.Token0ToToken1, + callbackStatus: CallbackStatus.Enabled + }) + ); + vm.stopPrank(); + } + + // Override the abstract test with VelodromeV2 implementation + function test_CanSwap_MultiHop() public override { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + + // Setup routes and get amounts + MultiHopTestParams memory params = _setupRoutes( + address(USDC_TOKEN), + address(STG_TOKEN), + address(USDC_E_TOKEN), + false, + false + ); + + // Get initial reserves BEFORE the swap + ReserveState memory initialReserves; + ( + initialReserves.reserve0Pool1, + initialReserves.reserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + initialReserves.reserve0Pool2, + initialReserves.reserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + // Build route and execute swap + SwapTestParams[] memory swapParams = new SwapTestParams[](2); + bytes[] memory swapData = new bytes[](2); + + // First hop: USDC -> USDC.e (stable) + swapParams[0] = SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenMid, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: params.pool2, // Send to next pool + commandType: CommandType.ProcessUserERC20 + }); + + // Build first hop swap data + swapData[0] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool1, + direction: SwapDirection.Token0ToToken1, + recipient: params.pool2, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Second hop: USDC.e -> STG (volatile) + swapParams[1] = SwapTestParams({ + tokenIn: params.tokenMid, + tokenOut: params.tokenOut, + amountIn: params.amounts1[1], // Use output from first hop + sender: params.pool2, + recipient: USER_SENDER, + commandType: CommandType.ProcessOnePool + }); + + // Build second hop swap data + swapData[1] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool2, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Use the base _buildMultiHopRoute + bytes memory route = _buildMultiHopRoute(swapParams, swapData); + + // Approve and execute + IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); + + // vm.expectEmit(true, true, true, true); + // emit Route( + // USER_SENDER, + // USER_SENDER, + // params.tokenIn, + // params.tokenOut, + // 1000 * 1e6, + // params.amounts2[1], + // params.amounts2[1] + // ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route + ); + _verifyReserves(params, initialReserves); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop_WithStable() public { + deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + + vm.startPrank(USER_SENDER); + + // Setup routes and get amounts for stable->volatile path + MultiHopTestParams memory params = _setupRoutes( + address(USDC_TOKEN), + address(USDC_E_TOKEN), + address(STG_TOKEN), + true, // stable pool for first hop + false // volatile pool for second hop + ); + + // Get initial reserves BEFORE the swap + ReserveState memory initialReserves; + ( + initialReserves.reserve0Pool1, + initialReserves.reserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + initialReserves.reserve0Pool2, + initialReserves.reserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + // Build route and execute swap + SwapTestParams[] memory hopParams = new SwapTestParams[](2); + bytes[] memory hopData = new bytes[](2); + + // First hop: USDC -> USDC.e (stable) + hopParams[0] = SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenMid, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: params.pool2, // Send to next pool + commandType: CommandType.ProcessUserERC20 + }); + + hopData[0] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool1, + direction: SwapDirection.Token0ToToken1, + recipient: params.pool2, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Second hop: USDC.e -> STG (volatile) + hopParams[1] = SwapTestParams({ + tokenIn: params.tokenMid, + tokenOut: params.tokenOut, + amountIn: params.amounts1[1], // Use output from first hop + sender: params.pool2, + recipient: USER_SENDER, + commandType: CommandType.ProcessOnePool + }); + + hopData[1] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool2, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER, + callbackStatus: CallbackStatus.Disabled + }) + ); + + bytes memory route = _buildMultiHopRoute(hopParams, hopData); + + // Approve and execute + IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); + + // vm.expectEmit(true, true, true, true); + // emit Route( + // USER_SENDER, + // USER_SENDER, + // params.tokenIn, + // params.tokenOut, + // 1000 * 1e6, + // params.amounts2[1], + // params.amounts2[1] + // ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route + ); + _verifyReserves(params, initialReserves); + vm.stopPrank(); + } + + function testRevert_InvalidPoolOrRecipient() public { + vm.startPrank(USER_SENDER); + + // Get a valid pool address first for comparison + address validPool = VELODROME_V2_ROUTER.poolFor( + address(USDC_TOKEN), + address(STG_TOKEN), + false, + VELODROME_V2_FACTORY_REGISTRY + ); + + // --- Test case 1: Zero pool address --- + // 1. Create the specific swap data blob + bytes memory swapDataZeroPool = abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + address(0), // Invalid pool + uint8(SwapDirection.Token1ToToken0), + USER_SENDER, + uint8(CallbackStatus.Disabled) + ); + + // 2. Create the full route with the length-prefixed swap data + bytes memory routeWithZeroPool = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDC_TOKEN), + uint8(1), + FULL_SHARE, + uint16(swapDataZeroPool.length), // Length prefix + swapDataZeroPool + ); + + IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(USDC_TOKEN), + tokenOut: address(STG_TOKEN), + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + routeWithZeroPool, + InvalidCallData.selector + ); + + // --- Test case 2: Zero recipient address --- + bytes memory swapDataZeroRecipient = abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + validPool, + uint8(SwapDirection.Token1ToToken0), + address(0), // Invalid recipient + uint8(CallbackStatus.Disabled) + ); + + bytes memory routeWithZeroRecipient = abi.encodePacked( + uint8(CommandType.ProcessUserERC20), + address(USDC_TOKEN), + uint8(1), + FULL_SHARE, + uint16(swapDataZeroRecipient.length), // Length prefix + swapDataZeroRecipient + ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(USDC_TOKEN), + tokenOut: address(STG_TOKEN), + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + routeWithZeroRecipient, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + + function testRevert_WrongPoolReserves() public { + vm.startPrank(USER_SENDER); + + // Setup multi-hop route: USDC -> STG -> USDC.e + MultiHopTestParams memory params = _setupRoutes( + address(USDC_TOKEN), + address(STG_TOKEN), + address(USDC_E_TOKEN), + false, + false + ); + + // Build multi-hop route + SwapTestParams[] memory hopParams = new SwapTestParams[](2); + bytes[] memory hopData = new bytes[](2); + + // First hop: USDC -> USDC.e (stable) + hopParams[0] = SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenMid, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: params.pool2, // Send to next pool + commandType: CommandType.ProcessUserERC20 + }); + + hopData[0] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool1, + direction: SwapDirection.Token0ToToken1, + recipient: params.pool2, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Second hop: USDC.e -> STG (volatile) + hopParams[1] = SwapTestParams({ + tokenIn: params.tokenMid, + tokenOut: params.tokenOut, + amountIn: 0, // Not used in ProcessOnePool + sender: params.pool2, + recipient: USER_SENDER, + commandType: CommandType.ProcessOnePool + }); + + hopData[1] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool2, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER, + callbackStatus: CallbackStatus.Disabled + }) + ); + + bytes memory route = _buildMultiHopRoute(hopParams, hopData); + + deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); + + IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + + // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves + vm.mockCall( + params.pool2, + abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), + abi.encode(0, 0, block.timestamp) + ); + + vm.expectRevert(WrongPoolReserves.selector); + + coreRouteFacet.processRoute( + address(USDC_TOKEN), + 1000 * 1e6, + address(USDC_E_TOKEN), + 0, + USER_SENDER, + route + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + // ============================ Velodrome V2 Helper Functions ============================ + + /** + * @dev Helper function to test a VelodromeV2 swap. + * Uses a struct to group parameters and reduce stack depth. + */ + function _testSwap(VelodromeV2SwapTestParams memory params) internal { + // get expected output amounts from the router. + IVelodromeV2Router.Route[] + memory routes = new IVelodromeV2Router.Route[](1); + routes[0] = IVelodromeV2Router.Route({ + from: params.tokenIn, + to: params.tokenOut, + stable: params.stable, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( + params.amountIn, + routes + ); + emit log_named_uint("Expected amount out", amounts[1]); + + // Retrieve the pool address. + address pool = VELODROME_V2_ROUTER.poolFor( + params.tokenIn, + params.tokenOut, + params.stable, + VELODROME_V2_FACTORY_REGISTRY + ); + emit log_named_uint("Pool address:", uint256(uint160(pool))); + + // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. + CommandType commandCode = params.from == address(ldaDiamond) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20; + + // 1. Pack the data for the specific swap FIRST + bytes memory swapData = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: pool, + direction: params.direction, + recipient: params.to, + callbackStatus: params.callbackStatus + }) + ); + // build the route. + bytes memory route = abi.encodePacked( + uint8(commandCode), + params.tokenIn, + uint8(1), // num splits + FULL_SHARE, + uint16(swapData.length), // <--- Add length prefix + swapData + ); + + // approve the aggregator to spend tokenIn. + IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); + + // capture initial token balances. + uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); + emit log_named_uint("Initial tokenIn balance", initialTokenIn); + + ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](1); + if (params.callbackStatus == CallbackStatus.Enabled) { + bytes[] memory eventParams = new bytes[](4); + eventParams[0] = abi.encode(address(ldaDiamond)); + eventParams[1] = abi.encode(uint256(0)); + eventParams[2] = abi.encode(uint256(0)); + eventParams[3] = abi.encode(abi.encode(params.tokenIn)); + + expectedEvents[0] = ExpectedEvent({ + checkTopic1: true, + checkTopic2: false, + checkTopic3: false, + checkData: false, + eventSelector: keccak256( + "HookCalled(address,uint256,uint256,bytes)" + ), + eventParams: eventParams + }); + } else { + expectedEvents = new ExpectedEvent[](0); + } + + // vm.expectEmit(true, true, true, true); + // emit Route( + // from, + // params.to, + // params.tokenIn, + // params.tokenOut, + // params.amountIn, + // amounts[1], + // amounts[1] + // ); + + // execute the swap + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: params.amountIn, + sender: params.from, + recipient: params.to, + commandType: params.from == address(ldaDiamond) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 + }), + route, + expectedEvents + ); + } + + // Helper function to set up routes and get amounts + function _setupRoutes( + address tokenIn, + address tokenMid, + address tokenOut, + bool isStableFirst, + bool isStableSecond + ) private view returns (MultiHopTestParams memory params) { + params.tokenIn = tokenIn; + params.tokenMid = tokenMid; + params.tokenOut = tokenOut; + + // Setup first hop route + IVelodromeV2Router.Route[] + memory routes1 = new IVelodromeV2Router.Route[](1); + routes1[0] = IVelodromeV2Router.Route({ + from: tokenIn, + to: tokenMid, + stable: isStableFirst, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( + 1000 * 1e6, + routes1 + ); + + // Setup second hop route + IVelodromeV2Router.Route[] + memory routes2 = new IVelodromeV2Router.Route[](1); + routes2[0] = IVelodromeV2Router.Route({ + from: tokenMid, + to: tokenOut, + stable: isStableSecond, + factory: address(VELODROME_V2_FACTORY_REGISTRY) + }); + params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( + params.amounts1[1], + routes2 + ); + + // Get pool addresses + params.pool1 = VELODROME_V2_ROUTER.poolFor( + tokenIn, + tokenMid, + isStableFirst, + VELODROME_V2_FACTORY_REGISTRY + ); + + params.pool2 = VELODROME_V2_ROUTER.poolFor( + tokenMid, + tokenOut, + isStableSecond, + VELODROME_V2_FACTORY_REGISTRY + ); + + // Get pool fees info + params.pool1Fee = IVelodromeV2PoolFactory( + VELODROME_V2_FACTORY_REGISTRY + ).getFee(params.pool1, isStableFirst); + params.pool2Fee = IVelodromeV2PoolFactory( + VELODROME_V2_FACTORY_REGISTRY + ).getFee(params.pool2, isStableSecond); + + return params; + } + + function _buildVelodromeV2SwapData( + VelodromeV2SwapData memory params + ) private pure returns (bytes memory) { + return + abi.encodePacked( + VelodromeV2Facet.swapVelodromeV2.selector, + params.pool, + uint8(params.direction), + params.recipient, + params.callbackStatus + ); + } + + function _verifyReserves( + MultiHopTestParams memory params, + ReserveState memory initialReserves + ) private { + // Get reserves after swap + ( + uint256 finalReserve0Pool1, + uint256 finalReserve1Pool1, + + ) = IVelodromeV2Pool(params.pool1).getReserves(); + ( + uint256 finalReserve0Pool2, + uint256 finalReserve1Pool2, + + ) = IVelodromeV2Pool(params.pool2).getReserves(); + + address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); + address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); + + // Calculate exact expected changes + uint256 amountInAfterFees = 1000 * + 1e6 - + ((1000 * 1e6 * params.pool1Fee) / 10000); + + // Assert exact reserve changes for Pool1 + if (token0Pool1 == params.tokenIn) { + // tokenIn is token0, so reserve0 should increase and reserve1 should decrease + assertEq( + finalReserve0Pool1 - initialReserves.reserve0Pool1, + amountInAfterFees, + "Pool1 reserve0 (tokenIn) change incorrect" + ); + assertEq( + initialReserves.reserve1Pool1 - finalReserve1Pool1, + params.amounts1[1], + "Pool1 reserve1 (tokenMid) change incorrect" + ); + } else { + // tokenIn is token1, so reserve1 should increase and reserve0 should decrease + assertEq( + finalReserve1Pool1 - initialReserves.reserve1Pool1, + amountInAfterFees, + "Pool1 reserve1 (tokenIn) change incorrect" + ); + assertEq( + initialReserves.reserve0Pool1 - finalReserve0Pool1, + params.amounts1[1], + "Pool1 reserve0 (tokenMid) change incorrect" + ); + } + + // Assert exact reserve changes for Pool2 + if (token0Pool2 == params.tokenMid) { + // tokenMid is token0, so reserve0 should increase and reserve1 should decrease + assertEq( + finalReserve0Pool2 - initialReserves.reserve0Pool2, + params.amounts1[1] - + ((params.amounts1[1] * params.pool2Fee) / 10000), + "Pool2 reserve0 (tokenMid) change incorrect" + ); + assertEq( + initialReserves.reserve1Pool2 - finalReserve1Pool2, + params.amounts2[1], + "Pool2 reserve1 (tokenOut) change incorrect" + ); + } else { + // tokenMid is token1, so reserve1 should increase and reserve0 should decrease + assertEq( + finalReserve1Pool2 - initialReserves.reserve1Pool2, + params.amounts1[1] - + ((params.amounts1[1] * params.pool2Fee) / 10000), + "Pool2 reserve1 (tokenMid) change incorrect" + ); + assertEq( + initialReserves.reserve0Pool2 - finalReserve0Pool2, + params.amounts2[1], + "Pool2 reserve0 (tokenOut) change incorrect" + ); + } + } +} diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 4940ab7e1..0bde3e695 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -34,7 +34,7 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 1_000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: false + commandType: CommandType.ProcessUserERC20 }), USDC_E_WXDC_POOL, SwapDirection.Token0ToToken1 @@ -49,7 +49,7 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { amountIn: 5_000 * 1e6 - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, - isAggregatorFunds: true + commandType: CommandType.ProcessMyERC20 }), USDC_E_WXDC_POOL, SwapDirection.Token0ToToken1 From d934fe271feb079a5be864f572efb303e3888c18 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 12 Aug 2025 15:12:51 +0200 Subject: [PATCH 020/220] changes --- .../Lda/Facets/VelodromeV2Facet.t.sol | 268 +++--- .../Lda/Facets/VelodromeV2Facet2.t.sol | 877 ------------------ 2 files changed, 158 insertions(+), 987 deletions(-) delete mode 100644 test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 1100fdc1b..5145031f8 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -55,7 +55,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { address tokenOut; bool stable; SwapDirection direction; - bool callback; + CallbackStatus callbackStatus; } struct MultiHopTestParams { @@ -77,6 +77,13 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { uint256 reserve1Pool2; } + struct VelodromeV2SwapData { + address pool; + SwapDirection direction; + address recipient; + CallbackStatus callbackStatus; + } + function _setupForkConfig() internal override { forkConfig = ForkConfig({ rpcEnvName: "ETH_NODE_URI_OPTIMISM", @@ -118,7 +125,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: address(STG_TOKEN), stable: false, direction: SwapDirection.Token0ToToken1, - callback: false + callbackStatus: CallbackStatus.Disabled }) ); @@ -139,7 +146,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: address(USDC_TOKEN), stable: false, direction: SwapDirection.Token1ToToken0, - callback: false + callbackStatus: CallbackStatus.Disabled }) ); vm.stopPrank(); @@ -158,7 +165,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: address(USDC_E_TOKEN), stable: true, direction: SwapDirection.Token0ToToken1, - callback: false + callbackStatus: CallbackStatus.Disabled }) ); vm.stopPrank(); @@ -179,14 +186,14 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: address(USDC_TOKEN), stable: false, direction: SwapDirection.Token1ToToken0, - callback: false + callbackStatus: CallbackStatus.Disabled }) ); vm.stopPrank(); } function test_CanSwap_FromDexAggregator() public override { - // fund dex aggregator contract so that the contract holds USDC + // // fund dex aggregator contract so that the contract holds USDC deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); vm.startPrank(USER_SENDER); @@ -202,7 +209,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: address(USDC_E_TOKEN), stable: false, direction: SwapDirection.Token0ToToken1, - callback: false + callbackStatus: CallbackStatus.Disabled }) ); vm.stopPrank(); @@ -223,7 +230,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: address(USDC_E_TOKEN), stable: false, direction: SwapDirection.Token0ToToken1, - callback: true + callbackStatus: CallbackStatus.Enabled }) ); vm.stopPrank(); @@ -258,7 +265,51 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { ) = IVelodromeV2Pool(params.pool2).getReserves(); // Build route and execute swap - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); + SwapTestParams[] memory swapParams = new SwapTestParams[](2); + bytes[] memory swapData = new bytes[](2); + + // First hop: USDC -> USDC.e (stable) + swapParams[0] = SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenMid, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: params.pool2, // Send to next pool + commandType: CommandType.ProcessUserERC20 + }); + + // Build first hop swap data + swapData[0] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool1, + direction: SwapDirection.Token0ToToken1, + recipient: params.pool2, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Second hop: USDC.e -> STG (volatile) + swapParams[1] = SwapTestParams({ + tokenIn: params.tokenMid, + tokenOut: params.tokenOut, + amountIn: params.amounts1[1], // Use output from first hop + sender: params.pool2, + recipient: USER_SENDER, + commandType: CommandType.ProcessOnePool + }); + + // Build second hop swap data + swapData[1] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool2, + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Use the base _buildMultiHopRoute + bytes memory route = _buildMultiHopRoute(swapParams, swapData); // Approve and execute IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); @@ -318,7 +369,48 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { ) = IVelodromeV2Pool(params.pool2).getReserves(); // Build route and execute swap - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + SwapTestParams[] memory hopParams = new SwapTestParams[](2); + bytes[] memory hopData = new bytes[](2); + + // First hop: USDC -> USDC.e (stable) + hopParams[0] = SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenMid, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: params.pool2, // Send to next pool + commandType: CommandType.ProcessUserERC20 + }); + + hopData[0] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool1, + direction: SwapDirection.Token0ToToken1, + recipient: params.pool2, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Second hop: USDC.e -> STG (volatile) + hopParams[1] = SwapTestParams({ + tokenIn: params.tokenMid, + tokenOut: params.tokenOut, + amountIn: params.amounts1[1], // Use output from first hop + sender: params.pool2, + recipient: USER_SENDER, + commandType: CommandType.ProcessOnePool + }); + + hopData[1] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool2, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER, + callbackStatus: CallbackStatus.Disabled + }) + ); + + bytes memory route = _buildMultiHopRoute(hopParams, hopData); // Approve and execute IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); @@ -442,7 +534,48 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { ); // Build multi-hop route - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + SwapTestParams[] memory hopParams = new SwapTestParams[](2); + bytes[] memory hopData = new bytes[](2); + + // First hop: USDC -> USDC.e (stable) + hopParams[0] = SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenMid, + amountIn: 1000 * 1e6, + sender: USER_SENDER, + recipient: params.pool2, // Send to next pool + commandType: CommandType.ProcessUserERC20 + }); + + hopData[0] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool1, + direction: SwapDirection.Token0ToToken1, + recipient: params.pool2, + callbackStatus: CallbackStatus.Disabled + }) + ); + + // Second hop: USDC.e -> STG (volatile) + hopParams[1] = SwapTestParams({ + tokenIn: params.tokenMid, + tokenOut: params.tokenOut, + amountIn: 0, // Not used in ProcessOnePool + sender: params.pool2, + recipient: USER_SENDER, + commandType: CommandType.ProcessOnePool + }); + + hopData[1] = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: params.pool2, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER, + callbackStatus: CallbackStatus.Disabled + }) + ); + + bytes memory route = _buildMultiHopRoute(hopParams, hopData); deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); @@ -507,14 +640,13 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { : CommandType.ProcessUserERC20; // 1. Pack the data for the specific swap FIRST - bytes memory swapData = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - pool, - params.direction, - params.to, - params.callback - ? uint8(CallbackStatus.Enabled) - : uint8(CallbackStatus.Disabled) + bytes memory swapData = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: pool, + direction: params.direction, + recipient: params.to, + callbackStatus: params.callbackStatus + }) ); // build the route. bytes memory route = abi.encodePacked( @@ -534,7 +666,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { emit log_named_uint("Initial tokenIn balance", initialTokenIn); ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](1); - if (params.callback) { + if (params.callbackStatus == CallbackStatus.Enabled) { bytes[] memory eventParams = new bytes[](4); eventParams[0] = abi.encode(address(ldaDiamond)); eventParams[1] = abi.encode(uint256(0)); @@ -649,103 +781,19 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { return params; } - // function to build first hop of the route - function _buildFirstHop( - address pool1, - address pool2, // The recipient of the first hop is the next pool - uint8 direction - ) private pure returns (bytes memory) { - return - abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - pool1, - direction, - pool2, // Send intermediate tokens to the next pool for the second hop - uint8(CallbackStatus.Disabled) - ); - } - - // function to build second hop of the route - function _buildSecondHop( - address pool2, - address recipient, - uint8 direction + function _buildVelodromeV2SwapData( + VelodromeV2SwapData memory params ) private pure returns (bytes memory) { return abi.encodePacked( VelodromeV2Facet.swapVelodromeV2.selector, - pool2, - direction, - recipient, // Final recipient - uint8(CallbackStatus.Disabled) + params.pool, + uint8(params.direction), + params.recipient, + params.callbackStatus ); } - // route building function - function _buildMultiHopRoute( - MultiHopTestParams memory params, - address recipient, - uint8 firstHopDirection, - uint8 secondHopDirection - ) private pure returns (bytes memory) { - // 1. Get the specific data for each hop - bytes memory firstHopData = _buildFirstHop( - params.pool1, - params.pool2, - firstHopDirection - ); - - bytes memory secondHopData = _buildSecondHop( - params.pool2, - recipient, - secondHopDirection - ); - - // 2. Assemble the first command - bytes memory firstCommand = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - params.tokenIn, - uint8(1), // num splits - FULL_SHARE, - uint16(firstHopData.length), // <--- Add length prefix - firstHopData - ); - - // 3. Assemble the second command - // The second hop takes tokens already held by the diamond, so we use ProcessOnePool - bytes memory secondCommand = abi.encodePacked( - uint8(CommandType.ProcessOnePool), - params.tokenMid, - uint16(secondHopData.length), // <--- Add length prefix - secondHopData - ); - - // 4. Concatenate the commands to create the final route - return bytes.concat(firstCommand, secondCommand); - } - - function _verifyUserBalances( - MultiHopTestParams memory params, - uint256 initialBalance1, - uint256 initialBalance2 - ) private { - // Verify token balances - uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); - uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); - - assertApproxEqAbs( - initialBalance1 - finalBalance1, - 1000 * 1e6, - 1, // 1 wei tolerance - "Token1 spent amount mismatch" - ); - assertEq( - finalBalance2 - initialBalance2, - params.amounts2[1], - "Token2 received amount mismatch" - ); - } - function _verifyReserves( MultiHopTestParams memory params, ReserveState memory initialReserves diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol deleted file mode 100644 index 5145031f8..000000000 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet2.t.sol +++ /dev/null @@ -1,877 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; -import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; -import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; -import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; - -contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - function hook( - address sender, - uint256 amount0, - uint256 amount1, - bytes calldata data - ) external { - emit HookCalled(sender, amount0, amount1, data); - } -} - -contract VelodromeV2FacetTest is BaseDexFacetTest { - VelodromeV2Facet internal velodromeV2Facet; - - // ==================== Velodrome V2 specific variables ==================== - IVelodromeV2Router internal constant VELODROME_V2_ROUTER = - IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router - address internal constant VELODROME_V2_FACTORY_REGISTRY = - 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; - IERC20 internal constant USDC_TOKEN = - IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); - IERC20 internal constant STG_TOKEN = - IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); - IERC20 internal constant USDC_E_TOKEN = - IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); - - MockVelodromeV2FlashLoanCallbackReceiver - internal mockFlashloanCallbackReceiver; - - // Velodrome V2 structs - struct VelodromeV2SwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; - bool stable; - SwapDirection direction; - CallbackStatus callbackStatus; - } - - struct MultiHopTestParams { - address tokenIn; - address tokenMid; - address tokenOut; - address pool1; - address pool2; - uint256[] amounts1; - uint256[] amounts2; - uint256 pool1Fee; - uint256 pool2Fee; - } - - struct ReserveState { - uint256 reserve0Pool1; - uint256 reserve1Pool1; - uint256 reserve0Pool2; - uint256 reserve1Pool2; - } - - struct VelodromeV2SwapData { - address pool; - SwapDirection direction; - address recipient; - CallbackStatus callbackStatus; - } - - function _setupForkConfig() internal override { - forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_OPTIMISM", - blockNumber: 133999121 - }); - } - - function _createFacetAndSelectors() - internal - override - returns (address, bytes4[] memory) - { - velodromeV2Facet = new VelodromeV2Facet(); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; - return (address(velodromeV2Facet), functionSelectors); - } - - function _setFacetInstance( - address payable facetAddress - ) internal override { - velodromeV2Facet = VelodromeV2Facet(facetAddress); - } - - // ============================ Velodrome V2 Tests ============================ - - // no stable swap - function test_CanSwap() public override { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); - - vm.startPrank(USER_SENDER); - - _testSwap( - VelodromeV2SwapTestParams({ - from: address(USER_SENDER), - to: address(USER_SENDER), - tokenIn: address(USDC_TOKEN), - amountIn: 1_000 * 1e6, - tokenOut: address(STG_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callbackStatus: CallbackStatus.Disabled - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_NoStable_Reverse() public { - // first perform the forward swap. - test_CanSwap(); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(STG_TOKEN), - amountIn: 500 * 1e18, - tokenOut: address(USDC_TOKEN), - stable: false, - direction: SwapDirection.Token1ToToken0, - callbackStatus: CallbackStatus.Disabled - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_Stable() public { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(USDC_TOKEN), - amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), - stable: true, - direction: SwapDirection.Token0ToToken1, - callbackStatus: CallbackStatus.Disabled - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_Stable_Reverse() public { - // first perform the forward stable swap. - test_CanSwap_Stable(); - - vm.startPrank(USER_SENDER); - - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(USDC_E_TOKEN), - amountIn: 500 * 1e6, - tokenOut: address(USDC_TOKEN), - stable: false, - direction: SwapDirection.Token1ToToken0, - callbackStatus: CallbackStatus.Disabled - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - // // fund dex aggregator contract so that the contract holds USDC - deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: address(ldaDiamond), - to: address(USER_SENDER), - tokenIn: address(USDC_TOKEN), - amountIn: IERC20(address(USDC_TOKEN)).balanceOf( - address(ldaDiamond) - ) - 1, // adjust for slot undrain protection: subtract 1 token so that the - // aggregator's balance isn't completely drained, matching the contract's safeguard - tokenOut: address(USDC_E_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callbackStatus: CallbackStatus.Disabled - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_FlashloanCallback() public { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); - - mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: address(USER_SENDER), - to: address(mockFlashloanCallbackReceiver), - tokenIn: address(USDC_TOKEN), - amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callbackStatus: CallbackStatus.Enabled - }) - ); - vm.stopPrank(); - } - - // Override the abstract test with VelodromeV2 implementation - function test_CanSwap_MultiHop() public override { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); - - vm.startPrank(USER_SENDER); - - // Setup routes and get amounts - MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(STG_TOKEN), - address(USDC_E_TOKEN), - false, - false - ); - - // Get initial reserves BEFORE the swap - ReserveState memory initialReserves; - ( - initialReserves.reserve0Pool1, - initialReserves.reserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - initialReserves.reserve0Pool2, - initialReserves.reserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - // Build route and execute swap - SwapTestParams[] memory swapParams = new SwapTestParams[](2); - bytes[] memory swapData = new bytes[](2); - - // First hop: USDC -> USDC.e (stable) - swapParams[0] = SwapTestParams({ - tokenIn: params.tokenIn, - tokenOut: params.tokenMid, - amountIn: 1000 * 1e6, - sender: USER_SENDER, - recipient: params.pool2, // Send to next pool - commandType: CommandType.ProcessUserERC20 - }); - - // Build first hop swap data - swapData[0] = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: params.pool1, - direction: SwapDirection.Token0ToToken1, - recipient: params.pool2, - callbackStatus: CallbackStatus.Disabled - }) - ); - - // Second hop: USDC.e -> STG (volatile) - swapParams[1] = SwapTestParams({ - tokenIn: params.tokenMid, - tokenOut: params.tokenOut, - amountIn: params.amounts1[1], // Use output from first hop - sender: params.pool2, - recipient: USER_SENDER, - commandType: CommandType.ProcessOnePool - }); - - // Build second hop swap data - swapData[1] = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: params.pool2, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER, - callbackStatus: CallbackStatus.Disabled - }) - ); - - // Use the base _buildMultiHopRoute - bytes memory route = _buildMultiHopRoute(swapParams, swapData); - - // Approve and execute - IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - - // vm.expectEmit(true, true, true, true); - // emit Route( - // USER_SENDER, - // USER_SENDER, - // params.tokenIn, - // params.tokenOut, - // 1000 * 1e6, - // params.amounts2[1], - // params.amounts2[1] - // ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: params.tokenIn, - tokenOut: params.tokenOut, - amountIn: 1000 * 1e6, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route - ); - _verifyReserves(params, initialReserves); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop_WithStable() public { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); - - vm.startPrank(USER_SENDER); - - // Setup routes and get amounts for stable->volatile path - MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(USDC_E_TOKEN), - address(STG_TOKEN), - true, // stable pool for first hop - false // volatile pool for second hop - ); - - // Get initial reserves BEFORE the swap - ReserveState memory initialReserves; - ( - initialReserves.reserve0Pool1, - initialReserves.reserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - initialReserves.reserve0Pool2, - initialReserves.reserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - // Build route and execute swap - SwapTestParams[] memory hopParams = new SwapTestParams[](2); - bytes[] memory hopData = new bytes[](2); - - // First hop: USDC -> USDC.e (stable) - hopParams[0] = SwapTestParams({ - tokenIn: params.tokenIn, - tokenOut: params.tokenMid, - amountIn: 1000 * 1e6, - sender: USER_SENDER, - recipient: params.pool2, // Send to next pool - commandType: CommandType.ProcessUserERC20 - }); - - hopData[0] = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: params.pool1, - direction: SwapDirection.Token0ToToken1, - recipient: params.pool2, - callbackStatus: CallbackStatus.Disabled - }) - ); - - // Second hop: USDC.e -> STG (volatile) - hopParams[1] = SwapTestParams({ - tokenIn: params.tokenMid, - tokenOut: params.tokenOut, - amountIn: params.amounts1[1], // Use output from first hop - sender: params.pool2, - recipient: USER_SENDER, - commandType: CommandType.ProcessOnePool - }); - - hopData[1] = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: params.pool2, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER, - callbackStatus: CallbackStatus.Disabled - }) - ); - - bytes memory route = _buildMultiHopRoute(hopParams, hopData); - - // Approve and execute - IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - - // vm.expectEmit(true, true, true, true); - // emit Route( - // USER_SENDER, - // USER_SENDER, - // params.tokenIn, - // params.tokenOut, - // 1000 * 1e6, - // params.amounts2[1], - // params.amounts2[1] - // ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: params.tokenIn, - tokenOut: params.tokenOut, - amountIn: 1000 * 1e6, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route - ); - _verifyReserves(params, initialReserves); - vm.stopPrank(); - } - - function testRevert_InvalidPoolOrRecipient() public { - vm.startPrank(USER_SENDER); - - // Get a valid pool address first for comparison - address validPool = VELODROME_V2_ROUTER.poolFor( - address(USDC_TOKEN), - address(STG_TOKEN), - false, - VELODROME_V2_FACTORY_REGISTRY - ); - - // --- Test case 1: Zero pool address --- - // 1. Create the specific swap data blob - bytes memory swapDataZeroPool = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - address(0), // Invalid pool - uint8(SwapDirection.Token1ToToken0), - USER_SENDER, - uint8(CallbackStatus.Disabled) - ); - - // 2. Create the full route with the length-prefixed swap data - bytes memory routeWithZeroPool = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDC_TOKEN), - uint8(1), - FULL_SHARE, - uint16(swapDataZeroPool.length), // Length prefix - swapDataZeroPool - ); - - IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(USDC_TOKEN), - tokenOut: address(STG_TOKEN), - amountIn: 1000 * 1e6, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - routeWithZeroPool, - InvalidCallData.selector - ); - - // --- Test case 2: Zero recipient address --- - bytes memory swapDataZeroRecipient = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - validPool, - uint8(SwapDirection.Token1ToToken0), - address(0), // Invalid recipient - uint8(CallbackStatus.Disabled) - ); - - bytes memory routeWithZeroRecipient = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDC_TOKEN), - uint8(1), - FULL_SHARE, - uint16(swapDataZeroRecipient.length), // Length prefix - swapDataZeroRecipient - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(USDC_TOKEN), - tokenOut: address(STG_TOKEN), - amountIn: 1000 * 1e6, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - routeWithZeroRecipient, - InvalidCallData.selector - ); - - vm.stopPrank(); - } - - function testRevert_WrongPoolReserves() public { - vm.startPrank(USER_SENDER); - - // Setup multi-hop route: USDC -> STG -> USDC.e - MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(STG_TOKEN), - address(USDC_E_TOKEN), - false, - false - ); - - // Build multi-hop route - SwapTestParams[] memory hopParams = new SwapTestParams[](2); - bytes[] memory hopData = new bytes[](2); - - // First hop: USDC -> USDC.e (stable) - hopParams[0] = SwapTestParams({ - tokenIn: params.tokenIn, - tokenOut: params.tokenMid, - amountIn: 1000 * 1e6, - sender: USER_SENDER, - recipient: params.pool2, // Send to next pool - commandType: CommandType.ProcessUserERC20 - }); - - hopData[0] = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: params.pool1, - direction: SwapDirection.Token0ToToken1, - recipient: params.pool2, - callbackStatus: CallbackStatus.Disabled - }) - ); - - // Second hop: USDC.e -> STG (volatile) - hopParams[1] = SwapTestParams({ - tokenIn: params.tokenMid, - tokenOut: params.tokenOut, - amountIn: 0, // Not used in ProcessOnePool - sender: params.pool2, - recipient: USER_SENDER, - commandType: CommandType.ProcessOnePool - }); - - hopData[1] = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: params.pool2, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER, - callbackStatus: CallbackStatus.Disabled - }) - ); - - bytes memory route = _buildMultiHopRoute(hopParams, hopData); - - deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); - - IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); - - // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves - vm.mockCall( - params.pool2, - abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), - abi.encode(0, 0, block.timestamp) - ); - - vm.expectRevert(WrongPoolReserves.selector); - - coreRouteFacet.processRoute( - address(USDC_TOKEN), - 1000 * 1e6, - address(USDC_E_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - // ============================ Velodrome V2 Helper Functions ============================ - - /** - * @dev Helper function to test a VelodromeV2 swap. - * Uses a struct to group parameters and reduce stack depth. - */ - function _testSwap(VelodromeV2SwapTestParams memory params) internal { - // get expected output amounts from the router. - IVelodromeV2Router.Route[] - memory routes = new IVelodromeV2Router.Route[](1); - routes[0] = IVelodromeV2Router.Route({ - from: params.tokenIn, - to: params.tokenOut, - stable: params.stable, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( - params.amountIn, - routes - ); - emit log_named_uint("Expected amount out", amounts[1]); - - // Retrieve the pool address. - address pool = VELODROME_V2_ROUTER.poolFor( - params.tokenIn, - params.tokenOut, - params.stable, - VELODROME_V2_FACTORY_REGISTRY - ); - emit log_named_uint("Pool address:", uint256(uint160(pool))); - - // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. - CommandType commandCode = params.from == address(ldaDiamond) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; - - // 1. Pack the data for the specific swap FIRST - bytes memory swapData = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: pool, - direction: params.direction, - recipient: params.to, - callbackStatus: params.callbackStatus - }) - ); - // build the route. - bytes memory route = abi.encodePacked( - uint8(commandCode), - params.tokenIn, - uint8(1), // num splits - FULL_SHARE, - uint16(swapData.length), // <--- Add length prefix - swapData - ); - - // approve the aggregator to spend tokenIn. - IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - - // capture initial token balances. - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - emit log_named_uint("Initial tokenIn balance", initialTokenIn); - - ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](1); - if (params.callbackStatus == CallbackStatus.Enabled) { - bytes[] memory eventParams = new bytes[](4); - eventParams[0] = abi.encode(address(ldaDiamond)); - eventParams[1] = abi.encode(uint256(0)); - eventParams[2] = abi.encode(uint256(0)); - eventParams[3] = abi.encode(abi.encode(params.tokenIn)); - - expectedEvents[0] = ExpectedEvent({ - checkTopic1: true, - checkTopic2: false, - checkTopic3: false, - checkData: false, - eventSelector: keccak256( - "HookCalled(address,uint256,uint256,bytes)" - ), - eventParams: eventParams - }); - } else { - expectedEvents = new ExpectedEvent[](0); - } - - // vm.expectEmit(true, true, true, true); - // emit Route( - // from, - // params.to, - // params.tokenIn, - // params.tokenOut, - // params.amountIn, - // amounts[1], - // amounts[1] - // ); - - // execute the swap - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: params.tokenIn, - tokenOut: params.tokenOut, - amountIn: params.amountIn, - sender: params.from, - recipient: params.to, - commandType: params.from == address(ldaDiamond) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 - }), - route, - expectedEvents - ); - } - - // Helper function to set up routes and get amounts - function _setupRoutes( - address tokenIn, - address tokenMid, - address tokenOut, - bool isStableFirst, - bool isStableSecond - ) private view returns (MultiHopTestParams memory params) { - params.tokenIn = tokenIn; - params.tokenMid = tokenMid; - params.tokenOut = tokenOut; - - // Setup first hop route - IVelodromeV2Router.Route[] - memory routes1 = new IVelodromeV2Router.Route[](1); - routes1[0] = IVelodromeV2Router.Route({ - from: tokenIn, - to: tokenMid, - stable: isStableFirst, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( - 1000 * 1e6, - routes1 - ); - - // Setup second hop route - IVelodromeV2Router.Route[] - memory routes2 = new IVelodromeV2Router.Route[](1); - routes2[0] = IVelodromeV2Router.Route({ - from: tokenMid, - to: tokenOut, - stable: isStableSecond, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( - params.amounts1[1], - routes2 - ); - - // Get pool addresses - params.pool1 = VELODROME_V2_ROUTER.poolFor( - tokenIn, - tokenMid, - isStableFirst, - VELODROME_V2_FACTORY_REGISTRY - ); - - params.pool2 = VELODROME_V2_ROUTER.poolFor( - tokenMid, - tokenOut, - isStableSecond, - VELODROME_V2_FACTORY_REGISTRY - ); - - // Get pool fees info - params.pool1Fee = IVelodromeV2PoolFactory( - VELODROME_V2_FACTORY_REGISTRY - ).getFee(params.pool1, isStableFirst); - params.pool2Fee = IVelodromeV2PoolFactory( - VELODROME_V2_FACTORY_REGISTRY - ).getFee(params.pool2, isStableSecond); - - return params; - } - - function _buildVelodromeV2SwapData( - VelodromeV2SwapData memory params - ) private pure returns (bytes memory) { - return - abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - params.pool, - uint8(params.direction), - params.recipient, - params.callbackStatus - ); - } - - function _verifyReserves( - MultiHopTestParams memory params, - ReserveState memory initialReserves - ) private { - // Get reserves after swap - ( - uint256 finalReserve0Pool1, - uint256 finalReserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - uint256 finalReserve0Pool2, - uint256 finalReserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); - address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); - - // Calculate exact expected changes - uint256 amountInAfterFees = 1000 * - 1e6 - - ((1000 * 1e6 * params.pool1Fee) / 10000); - - // Assert exact reserve changes for Pool1 - if (token0Pool1 == params.tokenIn) { - // tokenIn is token0, so reserve0 should increase and reserve1 should decrease - assertEq( - finalReserve0Pool1 - initialReserves.reserve0Pool1, - amountInAfterFees, - "Pool1 reserve0 (tokenIn) change incorrect" - ); - assertEq( - initialReserves.reserve1Pool1 - finalReserve1Pool1, - params.amounts1[1], - "Pool1 reserve1 (tokenMid) change incorrect" - ); - } else { - // tokenIn is token1, so reserve1 should increase and reserve0 should decrease - assertEq( - finalReserve1Pool1 - initialReserves.reserve1Pool1, - amountInAfterFees, - "Pool1 reserve1 (tokenIn) change incorrect" - ); - assertEq( - initialReserves.reserve0Pool1 - finalReserve0Pool1, - params.amounts1[1], - "Pool1 reserve0 (tokenMid) change incorrect" - ); - } - - // Assert exact reserve changes for Pool2 - if (token0Pool2 == params.tokenMid) { - // tokenMid is token0, so reserve0 should increase and reserve1 should decrease - assertEq( - finalReserve0Pool2 - initialReserves.reserve0Pool2, - params.amounts1[1] - - ((params.amounts1[1] * params.pool2Fee) / 10000), - "Pool2 reserve0 (tokenMid) change incorrect" - ); - assertEq( - initialReserves.reserve1Pool2 - finalReserve1Pool2, - params.amounts2[1], - "Pool2 reserve1 (tokenOut) change incorrect" - ); - } else { - // tokenMid is token1, so reserve1 should increase and reserve0 should decrease - assertEq( - finalReserve1Pool2 - initialReserves.reserve1Pool2, - params.amounts1[1] - - ((params.amounts1[1] * params.pool2Fee) / 10000), - "Pool2 reserve1 (tokenMid) change incorrect" - ); - assertEq( - initialReserves.reserve0Pool2 - finalReserve0Pool2, - params.amounts2[1], - "Pool2 reserve0 (tokenOut) change incorrect" - ); - } - } -} From a7e2c38433da8f98c7cbdc058590448fa8d3eb5d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 12 Aug 2025 23:33:19 +0200 Subject: [PATCH 021/220] abstracted top level vars --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 30 +++ .../Lda/BaseUniV3StyleDexFacet.t.sol | 79 ++++++ .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 8 + .../Lda/Facets/EnosysDexV3Facet.t.sol | 45 +--- .../Lda/Facets/HyperswapV3Facet.t.sol | 43 ++-- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 236 ++++++------------ .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 27 +- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 83 +++--- .../Lda/Facets/SyncSwapV2Facet.t.sol | 123 ++++----- .../Lda/Facets/VelodromeV2Facet.t.sol | 99 ++++---- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 28 +-- 11 files changed, 404 insertions(+), 397 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 687c8c67e..bd6772cf3 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -81,6 +81,18 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { bytes[] eventParams; // The event parameters, each encoded separately } + // At top-level state + IERC20 internal tokenIn; + IERC20 internal tokenMid; // optional for multi-hop + IERC20 internal tokenOut; + + address internal poolInOut; // for single hop or UniV2-style + address internal poolInMid; // for hop 1 + address internal poolMidOut; // for hop 2 + + // Optional flag/hook for aggregator slot-undrain behavior + bool internal aggregatorUndrainMinusOne; + function _addDexFacet() internal virtual { ( address facetAddress, @@ -99,6 +111,8 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { returns (address, bytes4[] memory); function _setFacetInstance(address payable facetAddress) internal virtual; + function _setupDexEnv() internal virtual; + function setUp() public virtual override { // forkConfig should be set in the child contract via _setupForkConfig() _setupForkConfig(); @@ -110,6 +124,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { fork(); LdaDiamondTest.setUp(); _addCoreRouteFacet(); + _setupDexEnv(); // NEW: populate tokens/pools _addDexFacet(); } @@ -352,4 +367,19 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { route ); } + + // Optional helper + function _adjustAmountFor( + CommandType cmd, + uint256 amount + ) internal view returns (uint256) { + if ( + cmd == CommandType.ProcessMyERC20 && + aggregatorUndrainMinusOne && + amount > 0 + ) { + return amount - 1; + } + return amount; + } } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 31dec66a1..fabd571d9 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -4,15 +4,27 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; +// Minimal UniV3-like pool interface for direction detection +interface IUniV3LikePool { + function token0() external view returns (address); + function token1() external view returns (address); +} + abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { UniV3StyleFacet internal uniV3Facet; + // Single-pool slot for UniV3-style tests + address internal uniV3Pool; + struct UniV3SwapParams { address pool; SwapDirection direction; address recipient; } + // Add the custom error + error TokenNotInPool(address token, address pool); + function _createFacetAndSelectors() internal override @@ -83,4 +95,71 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.stopPrank(); } + + // === Additions below === + + // Infer swap direction from pool’s token0/token1 and TOKEN_IN + function _getDirection( + address pool, + address tokenIn + ) internal view returns (SwapDirection) { + address t0 = IUniV3LikePool(pool).token0(); + address t1 = IUniV3LikePool(pool).token1(); + if (tokenIn == t0) return SwapDirection.Token0ToToken1; + if (tokenIn == t1) return SwapDirection.Token1ToToken0; + revert TokenNotInPool(tokenIn, pool); + } + + // Auto-wired UniV3-style swap using base token/pool slots + function _executeUniV3StyleSwapAuto( + CommandType cmd, + uint256 rawAmountIn + ) internal { + uint256 amountIn = _adjustAmountFor(cmd, rawAmountIn); + + // Fund the appropriate account + if (cmd == CommandType.ProcessMyERC20) { + deal(address(tokenIn), address(ldaDiamond), amountIn + 1); + } else { + deal(address(tokenIn), USER_SENDER, amountIn); + } + + vm.startPrank(USER_SENDER); + + SwapDirection direction = _getDirection(uniV3Pool, address(tokenIn)); + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: uniV3Pool, + direction: direction, + recipient: USER_SENDER + }) + ); + + // Build route and execute + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: cmd + }), + swapData + ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: cmd + }), + route + ); + + vm.stopPrank(); + } } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index f0b952da5..4fec606c7 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -153,6 +153,14 @@ contract AlgebraFacetTest is BaseDexFacetTest { algebraFacet = AlgebraFacet(facetAddress); } + // NEW: slot wiring for primary pair + function _setupDexEnv() internal override { + tokenIn = IERC20(APE_ETH_TOKEN); + tokenOut = IERC20(WETH_TOKEN); + poolInOut = ALGEBRA_POOL_APECHAIN; + aggregatorUndrainMinusOne = true; + } + // Override the abstract test with Algebra implementation function test_CanSwap_FromDexAggregator() public override { // Fund LDA from whale address diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index f4c2ba568..30bea9c1a 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -6,18 +6,6 @@ import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { - /// @dev HLN token on Flare - IERC20 internal constant HLN = - IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); - - /// @dev USDT0 token on Flare - IERC20 internal constant USDT0 = - IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); - - /// @dev The single EnosysDexV3 pool for HLN–USDT0 - address internal constant ENOSYS_V3_POOL = - 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; - function _setupForkConfig() internal override { forkConfig = ForkConfig({ rpcEnvName: "ETH_NODE_URI_FLARE", @@ -29,33 +17,18 @@ contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { return UniV3StyleFacet.enosysdexV3SwapCallback.selector; } + function _setupDexEnv() internal override { + tokenIn = IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); // HLN + tokenOut = IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); // USDT0 + uniV3Pool = 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; // ENOSYS_V3_POOL + aggregatorUndrainMinusOne = true; // if needed + } + function test_CanSwap() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(HLN), - tokenOut: address(USDT0), - amountIn: 1_000 * 1e18, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - ENOSYS_V3_POOL, - SwapDirection.Token0ToToken1 - ); + _executeUniV3StyleSwapAuto(CommandType.ProcessUserERC20, 1_000 * 1e18); } function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(HLN), - tokenOut: address(USDT0), - amountIn: 1_000 * 1e18 - 1, // Account for slot-undrain - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - ENOSYS_V3_POOL, - SwapDirection.Token0ToToken1 - ); + _executeUniV3StyleSwapAuto(CommandType.ProcessMyERC20, 1_000 * 1e18); } } diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 4fd81683d..f3dc50065 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -15,13 +15,6 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); - /// @dev a liquid USDT on HyperEVM - IERC20 internal constant USDT0 = - IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); - /// @dev WHYPE on HyperEVM - IERC20 internal constant WHYPE = - IERC20(0x5555555555555555555555555555555555555555); - function _setupForkConfig() internal override { forkConfig = ForkConfig({ rpcEnvName: "ETH_NODE_URI_HYPEREVM", @@ -33,19 +26,27 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { return UniV3StyleFacet.hyperswapV3SwapCallback.selector; } + function _setupDexEnv() internal override { + tokenIn = IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); // USDT0 + tokenOut = IERC20(0x5555555555555555555555555555555555555555); // WHYPE + uniV3Pool = IHyperswapV3Factory( + 0xB1c0fa0B789320044A6F623cFe5eBda9562602E3 + ).getPool(address(tokenIn), address(tokenOut), 3000); + } + function test_CanSwap() public override { // Get pool and quote address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), + address(tokenIn), + address(tokenOut), 3000 ); uint256 amountIn = 1_000 * 1e6; // (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( // IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - // tokenIn: address(USDT0), - // tokenOut: address(WHYPE), + // tokenIn: address(TOKEN_IN), + // tokenOut: address(TOKEN_OUT), // amountIn: amountIn, // fee: 3000, // sqrtPriceLimitX96: 0 @@ -57,8 +58,8 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { // emit Route( // USER_SENDER, // USER_SENDER, - // address(USDT0), - // address(WHYPE), + // address(TOKEN_IN), + // address(TOKEN_OUT), // amountIn, // quoted, // quoted @@ -66,8 +67,8 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -81,8 +82,8 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { function test_CanSwap_FromDexAggregator() public override { // Get pool and quote address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), + address(tokenIn), + address(tokenOut), 3000 ); @@ -104,8 +105,8 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { // emit Route( // USER_SENDER, // USER_SENDER, - // address(USDT0), - // address(WHYPE), + // address(TOKEN_IN), + // address(TOKEN_OUT), // amountIn - 1, // Account for slot undrain protection // quoted, // quoted @@ -113,8 +114,8 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: swapAmount, sender: USER_SENDER, recipient: USER_SENDER, diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index b75a2b75c..dd63a90d9 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -10,24 +10,7 @@ import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; contract IzumiV3FacetTest is BaseDexFacetTest { IzumiV3Facet internal izumiV3Facet; - // ==================== iZiSwap V3 specific variables ==================== - // Base constants - address internal constant USDC = - 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - address internal constant WETH = - 0x4200000000000000000000000000000000000006; - address internal constant USDB_C = - 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; - - // iZiSwap pools - address internal constant IZUMI_WETH_USDC_POOL = - 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; - address internal constant IZUMI_WETH_USDB_C_POOL = - 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; - - // Test parameters uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals - uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals // structs struct IzumiV3SwapTestParams { @@ -39,19 +22,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { SwapDirection direction; } - struct MultiHopTestParams { - address tokenIn; - address tokenMid; - address tokenOut; - address pool1; - address pool2; - uint256 amountIn; - SwapDirection direction1; - SwapDirection direction2; - } - error IzumiV3SwapUnexpected(); - error IzumiV3SwapCallbackUnknownSource(); error IzumiV3SwapCallbackNotPositiveAmount(); function _setupForkConfig() internal override { @@ -80,18 +51,27 @@ contract IzumiV3FacetTest is BaseDexFacetTest { izumiV3Facet = IzumiV3Facet(facetAddress); } + // NEW + function _setupDexEnv() internal override { + tokenIn = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); // USDC + tokenMid = IERC20(0x4200000000000000000000000000000000000006); // WETH + tokenOut = IERC20(0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA); // USDB_C + poolInMid = 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; // WETH/USDC + poolMidOut = 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; // WETH/USDB_C + } + function test_CanSwap_FromDexAggregator() public override { // Test USDC -> WETH - deal(USDC, address(coreRouteFacet), AMOUNT_USDC); + deal(address(tokenIn), address(coreRouteFacet), AMOUNT_USDC); vm.startPrank(USER_SENDER); _testSwap( IzumiV3SwapTestParams({ from: address(coreRouteFacet), to: USER_SENDER, - tokenIn: USDC, + tokenIn: address(tokenIn), amountIn: AMOUNT_USDC, - tokenOut: WETH, + tokenOut: address(tokenMid), direction: SwapDirection.Token1ToToken0 }) ); @@ -101,16 +81,16 @@ contract IzumiV3FacetTest is BaseDexFacetTest { function test_CanSwap_MultiHop() public override { // Fund the sender with tokens uint256 amountIn = AMOUNT_USDC; - deal(USDC, USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); // Capture initial token balances - uint256 initialBalanceIn = IERC20(USDC).balanceOf(USER_SENDER); - uint256 initialBalanceOut = IERC20(USDB_C).balanceOf(USER_SENDER); + uint256 initialBalanceIn = IERC20(tokenIn).balanceOf(USER_SENDER); + uint256 initialBalanceOut = IERC20(tokenOut).balanceOf(USER_SENDER); // Build first swap data: USDC -> WETH bytes memory firstSwapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ - pool: IZUMI_WETH_USDC_POOL, + pool: poolInMid, direction: SwapDirection.Token1ToToken0, recipient: address(coreRouteFacet) }) @@ -119,7 +99,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { // Build second swap data: WETH -> USDB_C bytes memory secondSwapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ - pool: IZUMI_WETH_USDB_C_POOL, + pool: poolMidOut, direction: SwapDirection.Token0ToToken1, recipient: USER_SENDER }) @@ -131,8 +111,8 @@ contract IzumiV3FacetTest is BaseDexFacetTest { // First hop: USDC -> WETH params[0] = SwapTestParams({ - tokenIn: USDC, - tokenOut: WETH, + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn, sender: USER_SENDER, recipient: address(coreRouteFacet), @@ -142,8 +122,8 @@ contract IzumiV3FacetTest is BaseDexFacetTest { // Second hop: WETH -> USDB_C params[1] = SwapTestParams({ - tokenIn: WETH, - tokenOut: USDB_C, + tokenIn: address(tokenMid), + tokenOut: address(tokenOut), amountIn: 0, // Will be determined by first swap sender: USER_SENDER, recipient: USER_SENDER, @@ -155,13 +135,13 @@ contract IzumiV3FacetTest is BaseDexFacetTest { // Approve tokens vm.startPrank(USER_SENDER); - IERC20(USDC).approve(address(ldaDiamond), amountIn); + IERC20(tokenIn).approve(address(ldaDiamond), amountIn); // Execute the swap uint256 amountOut = coreRouteFacet.processRoute( - USDC, + address(tokenIn), amountIn, - USDB_C, + address(tokenOut), 0, // No minimum amount for testing USER_SENDER, route @@ -169,8 +149,12 @@ contract IzumiV3FacetTest is BaseDexFacetTest { vm.stopPrank(); // Verify balances - uint256 finalBalanceIn = IERC20(USDC).balanceOf(USER_SENDER); - uint256 finalBalanceOut = IERC20(USDB_C).balanceOf(USER_SENDER); + uint256 finalBalanceIn = IERC20(address(tokenIn)).balanceOf( + USER_SENDER + ); + uint256 finalBalanceOut = IERC20(address(tokenOut)).balanceOf( + USER_SENDER + ); assertEq( initialBalanceIn - finalBalanceIn, @@ -186,14 +170,14 @@ contract IzumiV3FacetTest is BaseDexFacetTest { } function test_CanSwap() public override { - deal(address(USDC), USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); vm.startPrank(USER_SENDER); - IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); + IERC20(tokenIn).approve(address(ldaDiamond), AMOUNT_USDC); bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ - pool: IZUMI_WETH_USDC_POOL, + pool: poolInMid, direction: SwapDirection.Token1ToToken0, recipient: USER_RECEIVER }) @@ -201,7 +185,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { bytes memory route = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - USDC, + address(tokenIn), uint8(1), // number of pools/splits FULL_SHARE, // 100% share uint16(swapData.length), // length prefix @@ -209,12 +193,20 @@ contract IzumiV3FacetTest is BaseDexFacetTest { ); vm.expectEmit(true, true, true, false); - emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); + emit Route( + USER_SENDER, + USER_RECEIVER, + address(tokenIn), + address(tokenMid), + AMOUNT_USDC, + 0, + 0 + ); coreRouteFacet.processRoute( - USDC, + address(tokenIn), AMOUNT_USDC, - WETH, + address(tokenMid), 0, USER_RECEIVER, route @@ -224,7 +216,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { } function testRevert_IzumiV3SwapUnexpected() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); vm.startPrank(USER_SENDER); @@ -242,7 +234,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { // create a route with an invalid pool bytes memory invalidRoute = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - USDC, + address(tokenIn), uint8(1), // number of pools (1) FULL_SHARE, // 100% share uint16(swapData.length), // length prefix @@ -258,8 +250,8 @@ contract IzumiV3FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: USDC, - tokenOut: WETH, + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: AMOUNT_USDC, sender: USER_SENDER, recipient: USER_SENDER, @@ -274,13 +266,13 @@ contract IzumiV3FacetTest is BaseDexFacetTest { } function testRevert_UnexpectedCallbackSender() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); // Set up the expected callback sender through the diamond vm.store( address(ldaDiamond), keccak256("com.lifi.lda.callbackmanager"), - bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) + bytes32(uint256(uint160(poolInMid))) ); // Try to call callback from a different address than expected @@ -290,40 +282,40 @@ contract IzumiV3FacetTest is BaseDexFacetTest { abi.encodeWithSelector( LibCallbackManager.UnexpectedCallbackSender.selector, unexpectedCaller, - IZUMI_WETH_USDC_POOL + poolInMid ) ); - izumiV3Facet.swapY2XCallback(1, 1, abi.encode(USDC)); + izumiV3Facet.swapY2XCallback(1, 1, abi.encode(tokenIn)); } function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); // Set the expected callback sender through the diamond storage vm.store( address(ldaDiamond), keccak256("com.lifi.lda.callbackmanager"), - bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) + bytes32(uint256(uint160(poolInMid))) ); // try to call the callback with zero amount - vm.prank(IZUMI_WETH_USDC_POOL); + vm.prank(poolInMid); vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); izumiV3Facet.swapY2XCallback( 0, 0, // zero amount should trigger the error - abi.encode(USDC) + abi.encode(tokenIn) ); } function testRevert_FailsIfAmountInIsTooLarge() public { - deal(address(WETH), USER_SENDER, type(uint256).max); + deal(address(tokenMid), USER_SENDER, type(uint256).max); vm.startPrank(USER_SENDER); bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ - pool: IZUMI_WETH_USDC_POOL, + pool: poolInMid, direction: SwapDirection.Token0ToToken1, recipient: USER_RECEIVER }) @@ -331,7 +323,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { bytes memory route = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - WETH, + address(tokenMid), uint8(1), // number of pools (1) FULL_SHARE, // 100% share uint16(swapData.length), // length prefix @@ -340,8 +332,8 @@ contract IzumiV3FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: WETH, - tokenOut: USDC, + tokenIn: address(tokenMid), + tokenOut: address(tokenIn), amountIn: type(uint216).max, sender: USER_SENDER, recipient: USER_RECEIVER, @@ -357,7 +349,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { function _testSwap(IzumiV3SwapTestParams memory params) internal { // Fund the sender with tokens if not the contract itself if (params.from != address(coreRouteFacet)) { - deal(params.tokenIn, params.from, params.amountIn); + deal(address(params.tokenIn), params.from, params.amountIn); } // Capture initial token balances @@ -375,7 +367,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ - pool: IZUMI_WETH_USDC_POOL, + pool: poolInMid, direction: params.direction == SwapDirection.Token0ToToken1 ? SwapDirection.Token0ToToken1 : SwapDirection.Token1ToToken0, @@ -395,7 +387,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { // Approve tokens if necessary if (params.from == USER_SENDER) { vm.startPrank(USER_SENDER); - IERC20(params.tokenIn).approve( + IERC20(address(params.tokenIn)).approve( address(ldaDiamond), params.amountIn ); @@ -419,9 +411,9 @@ contract IzumiV3FacetTest is BaseDexFacetTest { // Execute the swap uint256 amountOut = coreRouteFacet.processRoute( - params.tokenIn, + address(params.tokenIn), params.amountIn, - params.tokenOut, + address(params.tokenOut), 0, // No minimum amount for testing params.to, route @@ -432,8 +424,12 @@ contract IzumiV3FacetTest is BaseDexFacetTest { } // Verify balances have changed correctly - uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); + uint256 finalBalanceIn = IERC20(address(params.tokenIn)).balanceOf( + params.from + ); + uint256 finalBalanceOut = IERC20(address(params.tokenOut)).balanceOf( + params.to + ); assertApproxEqAbs( initialBalanceIn - finalBalanceIn, @@ -452,92 +448,6 @@ contract IzumiV3FacetTest is BaseDexFacetTest { emit log_named_uint("Amount Out", amountOut); } - function _testMultiHopSwap(MultiHopTestParams memory params) internal { - // Fund the sender with tokens - deal(params.tokenIn, USER_SENDER, params.amountIn); - - // Capture initial token balances - uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - - // Build first swap data - bytes memory firstSwapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: params.pool1, - direction: params.direction1, - recipient: address(coreRouteFacet) - }) - ); - - bytes memory firstHop = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - params.tokenIn, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(firstSwapData.length), // length prefix - firstSwapData - ); - - // Build second swap data - bytes memory secondSwapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: params.pool2, - direction: params.direction2, - recipient: USER_SENDER - }) - ); - - bytes memory secondHop = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - params.tokenMid, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(secondSwapData.length), // length prefix - secondSwapData - ); - - // Combine into route - bytes memory route = bytes.concat(firstHop, secondHop); - - // Approve tokens - vm.startPrank(USER_SENDER); - IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - - // Execute the swap - uint256 amountOut = coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - 0, // No minimum amount for testing - USER_SENDER, - route - ); - vm.stopPrank(); - - // Verify balances have changed correctly - uint256 finalBalanceIn; - uint256 finalBalanceOut; - - finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); - finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - - assertEq( - initialBalanceIn - finalBalanceIn, - params.amountIn, - "TokenIn amount mismatch" - ); - assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); - assertEq( - amountOut, - finalBalanceOut - initialBalanceOut, - "AmountOut mismatch" - ); - } - struct IzumiV3SwapParams { address pool; SwapDirection direction; diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 64783fad3..e8f3ca985 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -6,14 +6,6 @@ import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { - IERC20 internal constant WHYPE = - IERC20(0x5555555555555555555555555555555555555555); - IERC20 internal constant LHYPE = - IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); - - address internal constant WHYPE_LHYPE_POOL = - 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; - function _setupForkConfig() internal override { forkConfig = ForkConfig({ rpcEnvName: "ETH_NODE_URI_HYPEREVM", @@ -25,17 +17,24 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { return UniV3StyleFacet.laminarV3SwapCallback.selector; } + function _setupDexEnv() internal override { + tokenIn = IERC20(0x5555555555555555555555555555555555555555); // WHYPE + tokenOut = IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); // LHYPE + uniV3Pool = 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; // WHYPE_LHYPE_POOL + aggregatorUndrainMinusOne = true; // if needed + } + function test_CanSwap() public override { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(WHYPE), - tokenOut: address(LHYPE), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1_000 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - WHYPE_LHYPE_POOL, + uniV3Pool, SwapDirection.Token0ToToken1 ); } @@ -43,14 +42,14 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { function test_CanSwap_FromDexAggregator() public override { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(WHYPE), - tokenOut: address(LHYPE), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1_000 * 1e18 - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 }), - WHYPE_LHYPE_POOL, + uniV3Pool, SwapDirection.Token0ToToken1 ); } diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 22ba37c45..6614cf0fa 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -7,14 +7,6 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { - // Constants for RabbitSwap on Viction - IERC20 internal constant SOROS = - IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); - IERC20 internal constant C98 = - IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); - address internal constant SOROS_C98_POOL = - 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; - function _setupForkConfig() internal override { forkConfig = ForkConfig({ rpcEnvName: "ETH_NODE_URI_VICTION", @@ -26,17 +18,24 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { return UniV3StyleFacet.rabbitSwapV3SwapCallback.selector; } + function _setupDexEnv() internal override { + tokenIn = IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); // SOROS + tokenOut = IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); // C98 + uniV3Pool = 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; // pool + aggregatorUndrainMinusOne = true; + } + function test_CanSwap() public override { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(SOROS), - tokenOut: address(C98), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1_000 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - SOROS_C98_POOL, + uniV3Pool, SwapDirection.Token1ToToken0 ); } @@ -44,24 +43,24 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function test_CanSwap_FromDexAggregator() public override { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(SOROS), - tokenOut: address(C98), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1_000 * 1e18 - 1, // Subtract 1 for slot-undrain protection sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 }), - SOROS_C98_POOL, + uniV3Pool, SwapDirection.Token1ToToken0 ); } function testRevert_RabbitSwapInvalidPool() public { uint256 amountIn = 1_000 * 1e18; - deal(address(SOROS), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - SOROS.approve(address(ldaDiamond), amountIn); + tokenIn.approve(address(ldaDiamond), amountIn); // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( @@ -75,8 +74,8 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { // Use _buildBaseRoute from base class bytes memory route = _buildBaseRoute( SwapTestParams({ - tokenIn: address(SOROS), - tokenOut: address(C98), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -85,14 +84,17 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { swapData ); - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route, + InvalidCallData.selector ); vm.stopPrank(); @@ -100,15 +102,15 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function testRevert_RabbitSwapInvalidRecipient() public { uint256 amountIn = 1_000 * 1e18; - deal(address(SOROS), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - SOROS.approve(address(ldaDiamond), amountIn); + tokenIn.approve(address(ldaDiamond), amountIn); // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ - pool: SOROS_C98_POOL, + pool: uniV3Pool, direction: SwapDirection.Token1ToToken0, recipient: address(0) // Invalid recipient address }) @@ -117,8 +119,8 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { // Use _buildBaseRoute from base class bytes memory route = _buildBaseRoute( SwapTestParams({ - tokenIn: address(SOROS), - tokenOut: address(C98), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -127,14 +129,17 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { swapData ); - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route, + InvalidCallData.selector ); vm.stopPrank(); diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 796d7d02c..36b7e8ff4 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -9,12 +9,6 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; contract SyncSwapV2FacetTest is BaseDexFacetTest { SyncSwapV2Facet internal syncSwapV2Facet; - IERC20 internal constant USDC = - IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); - IERC20 internal constant WETH = - IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); - address internal constant USDC_WETH_POOL_V1 = - address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); address internal constant SYNC_SWAP_VAULT = address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); @@ -50,17 +44,26 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { syncSwapV2Facet = SyncSwapV2Facet(facetAddress); } + function _setupDexEnv() internal override { + tokenIn = IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); // WETH + tokenMid = IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); // USDC + tokenOut = IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); // USDT + poolInMid = 0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308; // WETH-USDC V1 + poolMidOut = 0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2; // USDC-USDT V1 + aggregatorUndrainMinusOne = true; + } + /// @notice Single‐pool swap: USER sends WETH → receives USDC function test_CanSwap() public override { // Transfer 1 000 WETH from whale to USER_SENDER uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); bytes memory swapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, + pool: poolInMid, to: address(USER_SENDER), withdrawMode: 2, isV1Pool: 1, @@ -70,8 +73,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory route = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -82,8 +85,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -98,7 +101,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_PoolV2() public { // Transfer 1 000 WETH from whale to USER_SENDER uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); @@ -114,8 +117,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory route = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -126,8 +129,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -142,13 +145,13 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator() public override { // Fund the aggregator with 1 000 WETH uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), address(ldaDiamond), amountIn); + deal(address(tokenIn), address(ldaDiamond), amountIn); vm.startPrank(USER_SENDER); bytes memory swapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, + pool: poolInMid, to: address(USER_SENDER), withdrawMode: 2, isV1Pool: 1, @@ -158,8 +161,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory route = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, @@ -170,8 +173,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, @@ -186,7 +189,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator_PoolV2() public { // Fund the aggregator with 1 000 WETH uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), address(ldaDiamond), amountIn); + deal(address(tokenIn), address(ldaDiamond), amountIn); vm.startPrank(USER_SENDER); @@ -202,8 +205,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory route = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, @@ -214,8 +217,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, @@ -229,15 +232,15 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_MultiHop() public override { uint256 amountIn = 1_000e18; - deal(address(WETH), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); + tokenIn.approve(address(ldaDiamond), amountIn); // Build swap data for both hops bytes memory firstSwapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, + pool: poolInMid, to: SYNC_SWAP_VAULT, withdrawMode: 2, isV1Pool: 1, @@ -247,7 +250,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory secondSwapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: USDC_USDT_POOL_V1, + pool: poolMidOut, to: address(USER_SENDER), withdrawMode: 2, isV1Pool: 1, @@ -261,8 +264,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { // First hop: WETH -> USDC params[0] = SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: amountIn, sender: USER_SENDER, recipient: SYNC_SWAP_VAULT, @@ -272,8 +275,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { // Second hop: USDC -> USDT params[1] = SwapTestParams({ - tokenIn: address(USDC), - tokenOut: address(USDT), + tokenIn: address(tokenMid), + tokenOut: address(tokenOut), amountIn: 0, // Not used in ProcessOnePool sender: USER_SENDER, recipient: USER_SENDER, @@ -286,8 +289,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { // Use _executeAndVerifySwap with first and last token of the chain _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDT), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -302,13 +305,13 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function testRevert_V1PoolMissingVaultAddress() public { // Transfer 1 000 WETH from whale to USER_SENDER uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); bytes memory swapData = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, + pool: poolInOut, to: address(USER_SENDER), withdrawMode: 2, isV1Pool: 1, @@ -318,8 +321,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory route = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -330,8 +333,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -347,7 +350,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function testRevert_InvalidPoolOrRecipient() public { // Transfer 1 000 WETH from whale to USER_SENDER uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, amountIn); vm.startPrank(USER_SENDER); @@ -363,8 +366,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory routeWithInvalidPool = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -375,8 +378,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -388,7 +391,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, + pool: poolInOut, to: address(0), withdrawMode: 2, isV1Pool: 1, @@ -398,8 +401,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory routeWithInvalidRecipient = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -410,8 +413,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, @@ -430,7 +433,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory swapDataWithInvalidWithdrawMode = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, + pool: poolInOut, to: address(USER_SENDER), withdrawMode: 3, isV1Pool: 1, @@ -440,8 +443,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { bytes memory routeWithInvalidWithdrawMode = _buildBaseRoute( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1, // Arbitrary amount for this test sender: USER_SENDER, recipient: USER_SENDER, @@ -452,8 +455,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(WETH), - tokenOut: address(USDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1, sender: USER_SENDER, recipient: USER_SENDER, @@ -476,7 +479,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function _buildSyncSwapV2SwapData( SyncSwapV2SwapParams memory params - ) internal returns (bytes memory) { + ) internal view returns (bytes memory) { return abi.encodePacked( syncSwapV2Facet.swapSyncSwapV2.selector, diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 5145031f8..0f3417100 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -36,12 +36,6 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router address internal constant VELODROME_V2_FACTORY_REGISTRY = 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; - IERC20 internal constant USDC_TOKEN = - IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); - IERC20 internal constant STG_TOKEN = - IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); - IERC20 internal constant USDC_E_TOKEN = - IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); MockVelodromeV2FlashLoanCallbackReceiver internal mockFlashloanCallbackReceiver; @@ -108,11 +102,18 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { velodromeV2Facet = VelodromeV2Facet(facetAddress); } + function _setupDexEnv() internal override { + tokenIn = IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); // USDC + tokenMid = IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); // STG + tokenOut = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); // STG + // pools vary by test; set per-test as locals or use POOL_IN_OUT for the default path + } + // ============================ Velodrome V2 Tests ============================ // no stable swap function test_CanSwap() public override { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); vm.startPrank(USER_SENDER); @@ -120,9 +121,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { VelodromeV2SwapTestParams({ from: address(USER_SENDER), to: address(USER_SENDER), - tokenIn: address(USDC_TOKEN), + tokenIn: address(tokenIn), amountIn: 1_000 * 1e6, - tokenOut: address(STG_TOKEN), + tokenOut: address(tokenOut), stable: false, direction: SwapDirection.Token0ToToken1, callbackStatus: CallbackStatus.Disabled @@ -141,9 +142,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { VelodromeV2SwapTestParams({ from: USER_SENDER, to: USER_SENDER, - tokenIn: address(STG_TOKEN), + tokenIn: address(tokenMid), amountIn: 500 * 1e18, - tokenOut: address(USDC_TOKEN), + tokenOut: address(tokenIn), stable: false, direction: SwapDirection.Token1ToToken0, callbackStatus: CallbackStatus.Disabled @@ -153,16 +154,16 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } function test_CanSwap_Stable() public { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); vm.startPrank(USER_SENDER); _testSwap( VelodromeV2SwapTestParams({ from: USER_SENDER, to: USER_SENDER, - tokenIn: address(USDC_TOKEN), + tokenIn: address(tokenIn), amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), + tokenOut: address(tokenOut), stable: true, direction: SwapDirection.Token0ToToken1, callbackStatus: CallbackStatus.Disabled @@ -181,9 +182,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { VelodromeV2SwapTestParams({ from: USER_SENDER, to: USER_SENDER, - tokenIn: address(USDC_E_TOKEN), + tokenIn: address(tokenOut), amountIn: 500 * 1e6, - tokenOut: address(USDC_TOKEN), + tokenOut: address(tokenIn), stable: false, direction: SwapDirection.Token1ToToken0, callbackStatus: CallbackStatus.Disabled @@ -194,19 +195,19 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator() public override { // // fund dex aggregator contract so that the contract holds USDC - deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); + deal(address(tokenIn), address(ldaDiamond), 100_000 * 1e6); vm.startPrank(USER_SENDER); _testSwap( VelodromeV2SwapTestParams({ from: address(ldaDiamond), to: address(USER_SENDER), - tokenIn: address(USDC_TOKEN), - amountIn: IERC20(address(USDC_TOKEN)).balanceOf( + tokenIn: address(tokenIn), + amountIn: IERC20(address(tokenIn)).balanceOf( address(ldaDiamond) ) - 1, // adjust for slot undrain protection: subtract 1 token so that the // aggregator's balance isn't completely drained, matching the contract's safeguard - tokenOut: address(USDC_E_TOKEN), + tokenOut: address(tokenOut), stable: false, direction: SwapDirection.Token0ToToken1, callbackStatus: CallbackStatus.Disabled @@ -216,7 +217,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } function test_CanSwap_FlashloanCallback() public { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); @@ -225,9 +226,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { VelodromeV2SwapTestParams({ from: address(USER_SENDER), to: address(mockFlashloanCallbackReceiver), - tokenIn: address(USDC_TOKEN), + tokenIn: address(tokenIn), amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), + tokenOut: address(tokenOut), stable: false, direction: SwapDirection.Token0ToToken1, callbackStatus: CallbackStatus.Enabled @@ -238,15 +239,15 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // Override the abstract test with VelodromeV2 implementation function test_CanSwap_MultiHop() public override { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); vm.startPrank(USER_SENDER); // Setup routes and get amounts MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(STG_TOKEN), - address(USDC_E_TOKEN), + address(tokenIn), + address(tokenMid), + address(tokenOut), false, false ); @@ -342,15 +343,15 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } function test_CanSwap_MultiHop_WithStable() public { - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); + deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); vm.startPrank(USER_SENDER); // Setup routes and get amounts for stable->volatile path MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(USDC_E_TOKEN), - address(STG_TOKEN), + address(tokenIn), + address(tokenOut), + address(tokenMid), true, // stable pool for first hop false // volatile pool for second hop ); @@ -428,8 +429,8 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: params.tokenIn, - tokenOut: params.tokenOut, + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, @@ -446,8 +447,8 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // Get a valid pool address first for comparison address validPool = VELODROME_V2_ROUTER.poolFor( - address(USDC_TOKEN), - address(STG_TOKEN), + address(tokenIn), + address(tokenMid), false, VELODROME_V2_FACTORY_REGISTRY ); @@ -465,19 +466,19 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // 2. Create the full route with the length-prefixed swap data bytes memory routeWithZeroPool = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - address(USDC_TOKEN), + address(tokenIn), uint8(1), FULL_SHARE, uint16(swapDataZeroPool.length), // Length prefix swapDataZeroPool ); - IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + IERC20(address(tokenIn)).approve(address(ldaDiamond), 1000 * 1e6); _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(USDC_TOKEN), - tokenOut: address(STG_TOKEN), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, @@ -498,7 +499,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { bytes memory routeWithZeroRecipient = abi.encodePacked( uint8(CommandType.ProcessUserERC20), - address(USDC_TOKEN), + address(tokenIn), uint8(1), FULL_SHARE, uint16(swapDataZeroRecipient.length), // Length prefix @@ -507,8 +508,8 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(USDC_TOKEN), - tokenOut: address(STG_TOKEN), + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, @@ -526,9 +527,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // Setup multi-hop route: USDC -> STG -> USDC.e MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(STG_TOKEN), - address(USDC_E_TOKEN), + address(tokenIn), + address(tokenMid), + address(tokenOut), false, false ); @@ -577,9 +578,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { bytes memory route = _buildMultiHopRoute(hopParams, hopData); - deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); + deal(address(tokenIn), USER_SENDER, 1000 * 1e6); - IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + IERC20(address(tokenIn)).approve(address(ldaDiamond), 1000 * 1e6); // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves vm.mockCall( @@ -591,9 +592,9 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { vm.expectRevert(WrongPoolReserves.selector); coreRouteFacet.processRoute( - address(USDC_TOKEN), + address(tokenIn), 1000 * 1e6, - address(USDC_E_TOKEN), + address(tokenOut), 0, USER_SENDER, route diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 0bde3e695..042013887 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -6,15 +6,6 @@ import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { - address internal constant USDC_E_WXDC_POOL = - 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; - - /// @dev our two tokens: USDC.e and wrapped XDC - IERC20 internal constant USDC_E = - IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); - IERC20 internal constant WXDC = - IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); - function _setupForkConfig() internal override { forkConfig = ForkConfig({ rpcEnvName: "ETH_NODE_URI_XDC", @@ -26,17 +17,24 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { return UniV3StyleFacet.xswapCallback.selector; } + function _setupDexEnv() internal override { + tokenIn = IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); // USDC.e + tokenOut = IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); // WXDC + uniV3Pool = 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; // pool + aggregatorUndrainMinusOne = true; + } + function test_CanSwap() public override { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(USDC_E), - tokenOut: address(WXDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1_000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - USDC_E_WXDC_POOL, + uniV3Pool, SwapDirection.Token0ToToken1 ); } @@ -44,14 +42,14 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function test_CanSwap_FromDexAggregator() public override { _executeUniV3StyleSwap( SwapTestParams({ - tokenIn: address(USDC_E), - tokenOut: address(WXDC), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 5_000 * 1e6 - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 }), - USDC_E_WXDC_POOL, + uniV3Pool, SwapDirection.Token0ToToken1 ); } From c1e9ab50077feadf1448639d04836b94e453e37a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 13 Aug 2025 12:12:00 +0200 Subject: [PATCH 022/220] changed rpcEnvName to networkName --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 48 ++++++++++++++++--- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 2 +- .../Lda/Facets/EnosysDexV3Facet.t.sol | 2 +- .../Lda/Facets/HyperswapV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 2 +- .../Lda/Facets/SyncSwapV2Facet.t.sol | 2 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 2 +- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 2 +- 10 files changed, 51 insertions(+), 15 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index bd6772cf3..b4e9e3793 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -6,6 +6,7 @@ import { TestHelpers } from "../../utils/TestHelpers.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { stdJson } from "forge-std/StdJson.sol"; /** * @title BaseDexFacetTest @@ -15,7 +16,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { using SafeERC20 for IERC20; struct ForkConfig { - string rpcEnvName; + string networkName; // e.g. "taiko" (not "ETH_NODE_URI_TAIKO") uint256 blockNumber; } @@ -70,6 +71,8 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { // Add custom errors at the top of the contract error ParamsDataLengthMismatch(); error NoHopsProvided(); + error InvalidForkConfig(string reason); + error UnknownNetwork(string name); // Add this struct to hold event expectations struct ExpectedEvent { @@ -113,18 +116,51 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { function _setupDexEnv() internal virtual; + // helper to uppercase ASCII + function _toUpper(string memory s) internal virtual override pure returns (string memory) { + bytes memory b = bytes(s); + for (uint256 i; i < b.length; i++) { + uint8 c = uint8(b[i]); + if (c >= 97 && c <= 122) { + b[i] = bytes1(c - 32); + } + } + return string(b); + } + + // optional: ensure key exists in config/networks.json + function _ensureNetworkExists(string memory name) internal view { + // will revert if the key path is missing + string memory json = vm.readFile("config/networks.json"); + // read the "..name" path to confirm key presence + string memory path = string.concat(".", name, ".name"); + string memory value = stdJson.readString(json, path); + if (bytes(value).length == 0) { + revert UnknownNetwork(name); + } + } + function setUp() public virtual override { - // forkConfig should be set in the child contract via _setupForkConfig() _setupForkConfig(); - // TODO if rpcEnvName is not set, revert - // TODO if blockNumber is not set, revert - customRpcUrlForForking = forkConfig.rpcEnvName; + + if (bytes(forkConfig.networkName).length == 0) { + revert InvalidForkConfig("networkName not set"); + } + // optional validation against networks.json + _ensureNetworkExists(forkConfig.networkName); + + // compute env var name and assign for fork() + string memory rpc = string.concat( + "ETH_NODE_URI_", + _toUpper(forkConfig.networkName) + ); + customRpcUrlForForking = rpc; customBlockNumberForForking = forkConfig.blockNumber; fork(); LdaDiamondTest.setUp(); _addCoreRouteFacet(); - _setupDexEnv(); // NEW: populate tokens/pools + _setupDexEnv(); // populate tokens/pools _addDexFacet(); } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 4fec606c7..596dbfdf1 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -130,7 +130,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_APECHAIN", + networkName: "apechain", blockNumber: 12912470 }); } diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 30bea9c1a..84c68d5b6 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -8,7 +8,7 @@ import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_FLARE", + networkName: "flare", blockNumber: 42652369 }); } diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index f3dc50065..ba6240276 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -17,7 +17,7 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_HYPEREVM", + networkName: "hyperevm", blockNumber: 4433562 }); } diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index dd63a90d9..8a9773249 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -27,7 +27,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_BASE", + networkName: "base", blockNumber: 29831758 }); } diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index e8f3ca985..a4d7b3dbb 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -8,7 +8,7 @@ import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_HYPEREVM", + networkName: "hyperevm", blockNumber: 4433562 }); } diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 6614cf0fa..b107f6929 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -9,7 +9,7 @@ import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_VICTION", + networkName: "viction", blockNumber: 94490946 }); } diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 36b7e8ff4..ea72e10d1 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -22,7 +22,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_LINEA", + networkName: "linea", blockNumber: 20077881 }); } diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 0f3417100..aceeba85c 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -80,7 +80,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_OPTIMISM", + networkName: "optimism", blockNumber: 133999121 }); } diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 042013887..ef4f391ca 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -8,7 +8,7 @@ import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ - rpcEnvName: "ETH_NODE_URI_XDC", + networkName: "xdc", blockNumber: 89279495 }); } From 24d9e256aeccb62dfaa3aba32fbbeda29f14af2a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 14 Aug 2025 00:30:58 +0200 Subject: [PATCH 023/220] Refactored BaseDexFacetTest: renamed _toUpper to _convertToUpperCase, improved network validation logic --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 34 ++++++++++++++----- .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 4 +-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index b4e9e3793..9293e6d5e 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -16,7 +16,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { using SafeERC20 for IERC20; struct ForkConfig { - string networkName; // e.g. "taiko" (not "ETH_NODE_URI_TAIKO") + string networkName; // e.g. "taiko" (not "ETH_NODE_URI_TAIKO") uint256 blockNumber; } @@ -117,7 +117,9 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { function _setupDexEnv() internal virtual; // helper to uppercase ASCII - function _toUpper(string memory s) internal virtual override pure returns (string memory) { + function _convertToUpperCase( + string memory s + ) internal pure returns (string memory) { bytes memory b = bytes(s); for (uint256 i; i < b.length; i++) { uint8 c = uint8(b[i]); @@ -129,7 +131,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { } // optional: ensure key exists in config/networks.json - function _ensureNetworkExists(string memory name) internal view { + function _ensureNetworkExists(string memory name) internal { // will revert if the key path is missing string memory json = vm.readFile("config/networks.json"); // read the "..name" path to confirm key presence @@ -143,18 +145,34 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { function setUp() public virtual override { _setupForkConfig(); + // Validate network name if (bytes(forkConfig.networkName).length == 0) { revert InvalidForkConfig("networkName not set"); } - // optional validation against networks.json - _ensureNetworkExists(forkConfig.networkName); - // compute env var name and assign for fork() + // Validate block number + if (forkConfig.blockNumber == 0) { + revert InvalidForkConfig("blockNumber not set"); + } + + // Compute RPC URL and validate it exists string memory rpc = string.concat( "ETH_NODE_URI_", - _toUpper(forkConfig.networkName) + _convertToUpperCase(forkConfig.networkName) ); - customRpcUrlForForking = rpc; + + try vm.envString(rpc) returns (string memory rpcUrl) { + if (bytes(rpcUrl).length == 0) { + revert InvalidForkConfig("RPC URL is empty"); + } + customRpcUrlForForking = rpc; + } catch { + revert InvalidForkConfig("RPC URL not found"); + } + + // optional validation against networks.json + _ensureNetworkExists(forkConfig.networkName); + customBlockNumberForForking = forkConfig.blockNumber; fork(); diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index cf8899d9b..526954979 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -3443,7 +3443,7 @@ contract LiFiDexAggregatorEnosysDexV3UpgradeTest is function _buildUniV3SwapData( UniV3SwapParams memory params - ) internal returns (bytes memory) { + ) internal view returns (bytes memory) { return abi.encodePacked( uniV3StyleFacet.swapUniV3.selector, @@ -3932,7 +3932,7 @@ contract LiFiDexAggregatorSyncSwapV2UpgradeTest is function _buildSyncSwapV2SwapData( SyncSwapV2SwapParams memory params - ) internal returns (bytes memory) { + ) internal view returns (bytes memory) { return abi.encodePacked( syncSwapV2Facet.swapSyncSwapV2.selector, From 1c45137b2f51b9dd0cf52612eaac5c7707265403 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 14 Aug 2025 00:48:57 +0200 Subject: [PATCH 024/220] tests fixes --- .../solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 4 +++- .../Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 596dbfdf1..3b4ef1ef7 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -269,6 +269,8 @@ contract AlgebraFacetTest is BaseDexFacetTest { function test_CanSwap_Reverse() public { test_CanSwap(); + uint256 amountIn = IERC20(address(WETH_TOKEN)).balanceOf(USER_SENDER); + vm.startPrank(USER_SENDER); _testAlgebraSwap( @@ -276,7 +278,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { from: USER_SENDER, to: USER_SENDER, tokenIn: address(WETH_TOKEN), - amountIn: 5 * 1e18, + amountIn: amountIn, tokenOut: APE_ETH_TOKEN, direction: SwapDirection.Token1ToToken0, supportsFeeOnTransfer: false diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index aceeba85c..8c341f4c7 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -137,14 +137,16 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // first perform the forward swap. test_CanSwap(); + uint256 amountIn = IERC20(address(tokenOut)).balanceOf(USER_SENDER); vm.startPrank(USER_SENDER); + _testSwap( VelodromeV2SwapTestParams({ from: USER_SENDER, to: USER_SENDER, - tokenIn: address(tokenMid), - amountIn: 500 * 1e18, - tokenOut: address(tokenIn), + tokenIn: address(tokenOut), // USDC.e from first swap + amountIn: amountIn, + tokenOut: address(tokenIn), // USDC stable: false, direction: SwapDirection.Token1ToToken0, callbackStatus: CallbackStatus.Disabled @@ -429,8 +431,8 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, amountIn: 1000 * 1e6, sender: USER_SENDER, recipient: USER_SENDER, From 9ae482131f0b3487d257f161e3e46d610d27fb50 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 15 Aug 2025 00:06:17 +0200 Subject: [PATCH 025/220] use auto swpa for univ3 --- .../Lda/BaseUniV3StyleDexFacet.t.sol | 20 +++-- .../Lda/Facets/EnosysDexV3Facet.t.sol | 14 ++- .../Lda/Facets/HyperswapV3Facet.t.sol | 88 +++---------------- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 32 +++---- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 32 +++---- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 37 +++----- 6 files changed, 70 insertions(+), 153 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index fabd571d9..55b51e314 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -110,15 +110,21 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { revert TokenNotInPool(tokenIn, pool); } - // Auto-wired UniV3-style swap using base token/pool slots + struct UniV3AutoSwapParams { + CommandType commandType; + uint256 amountIn; + } + function _executeUniV3StyleSwapAuto( - CommandType cmd, - uint256 rawAmountIn + UniV3AutoSwapParams memory params ) internal { - uint256 amountIn = _adjustAmountFor(cmd, rawAmountIn); + uint256 amountIn = _adjustAmountFor( + params.commandType, + params.amountIn + ); // Fund the appropriate account - if (cmd == CommandType.ProcessMyERC20) { + if (params.commandType == CommandType.ProcessMyERC20) { deal(address(tokenIn), address(ldaDiamond), amountIn + 1); } else { deal(address(tokenIn), USER_SENDER, amountIn); @@ -143,7 +149,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - commandType: cmd + commandType: params.commandType }), swapData ); @@ -155,7 +161,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { amountIn: amountIn, sender: USER_SENDER, recipient: USER_SENDER, - commandType: cmd + commandType: params.commandType }), route ); diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 84c68d5b6..98c0780ee 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -25,10 +25,20 @@ contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { } function test_CanSwap() public override { - _executeUniV3StyleSwapAuto(CommandType.ProcessUserERC20, 1_000 * 1e18); + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessUserERC20, + amountIn: 1_000 * 1e18 + }) + ); } function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwapAuto(CommandType.ProcessMyERC20, 1_000 * 1e18); + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessMyERC20, + amountIn: 1_000 * 1e18 + }) + ); } } diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index ba6240276..89d1a527b 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -35,94 +35,34 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { } function test_CanSwap() public override { - // Get pool and quote - address pool = HYPERSWAP_FACTORY.getPool( + // Get pool first since it's dynamically calculated + uniV3Pool = HYPERSWAP_FACTORY.getPool( address(tokenIn), address(tokenOut), 3000 ); - uint256 amountIn = 1_000 * 1e6; - // (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - // IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - // tokenIn: address(TOKEN_IN), - // tokenOut: address(TOKEN_OUT), - // amountIn: amountIn, - // fee: 3000, - // sqrtPriceLimitX96: 0 - // }) - // ); - - // expect the Route event - // vm.expectEmit(true, true, true, true); - // emit Route( - // USER_SENDER, - // USER_SENDER, - // address(TOKEN_IN), - // address(TOKEN_OUT), - // amountIn, - // quoted, - // quoted - // ); - - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: amountIn, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - pool, - SwapDirection.Token1ToToken0 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessUserERC20, + amountIn: 1_000 * 1e6 + }) ); } function test_CanSwap_FromDexAggregator() public override { - // Get pool and quote - address pool = HYPERSWAP_FACTORY.getPool( + // Get pool first since it's dynamically calculated + uniV3Pool = HYPERSWAP_FACTORY.getPool( address(tokenIn), address(tokenOut), 3000 ); - uint256 amountIn = 1_000 * 1e6; - uint256 swapAmount = amountIn - 1; // Account for slot-undrain - - // (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - // IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - // tokenIn: address(USDT0), - // tokenOut: address(WHYPE), - // amountIn: swapAmount, - // fee: 3000, - // sqrtPriceLimitX96: 0 - // }) - // ); - - // expect the Route event - // vm.expectEmit(true, true, true, true); - // emit Route( - // USER_SENDER, - // USER_SENDER, - // address(TOKEN_IN), - // address(TOKEN_OUT), - // amountIn - 1, // Account for slot undrain protection - // quoted, - // quoted - // ); - - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: swapAmount, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - pool, - SwapDirection.Token1ToToken0 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessMyERC20, + amountIn: 1_000 * 1e6 - 1 // Account for slot-undrain + }) ); } } diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index a4d7b3dbb..3276b42f1 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -25,32 +25,20 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { } function test_CanSwap() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 1_000 * 1e18, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - uniV3Pool, - SwapDirection.Token0ToToken1 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessUserERC20, + amountIn: 1_000 * 1e18 + }) ); } function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 1_000 * 1e18 - 1, // Account for slot-undrain - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - uniV3Pool, - SwapDirection.Token0ToToken1 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessMyERC20, + amountIn: 1_000 * 1e18 - 1 // Account for slot-undrain + }) ); } } diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index b107f6929..c26a7a571 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -26,32 +26,20 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { } function test_CanSwap() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 1_000 * 1e18, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - uniV3Pool, - SwapDirection.Token1ToToken0 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessUserERC20, + amountIn: 1_000 * 1e18 + }) ); } function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 1_000 * 1e18 - 1, // Subtract 1 for slot-undrain protection - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - uniV3Pool, - SwapDirection.Token1ToToken0 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessMyERC20, + amountIn: 1_000 * 1e18 - 1 // Account for slot-undrain + }) ); } diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index ef4f391ca..cad0896b7 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -7,10 +7,7 @@ import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupForkConfig() internal override { - forkConfig = ForkConfig({ - networkName: "xdc", - blockNumber: 89279495 - }); + forkConfig = ForkConfig({ networkName: "xdc", blockNumber: 89279495 }); } function _getCallbackSelector() internal pure override returns (bytes4) { @@ -25,32 +22,20 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { } function test_CanSwap() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 1_000 * 1e6, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - uniV3Pool, - SwapDirection.Token0ToToken1 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessUserERC20, + amountIn: 1_000 * 1e6 + }) ); } function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 5_000 * 1e6 - 1, // Account for slot-undrain - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - uniV3Pool, - SwapDirection.Token0ToToken1 + _executeUniV3StyleSwapAuto( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessMyERC20, + amountIn: 5_000 * 1e6 - 1 // Account for slot-undrain + }) ); } From d2efaeda042e69a3ca880be23bef52b45704532d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 15 Aug 2025 09:33:18 +0200 Subject: [PATCH 026/220] abstracted test_CanSwap and test_CanSwap_FromDexAggregator for UniV3StyleDex facets and added getDefaultAmountIn --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 32 +++------- .../Lda/BaseUniV3StyleDexFacet.t.sol | 29 +++++++-- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 1 - .../Lda/Facets/EnosysDexV3Facet.t.sol | 19 ------ .../Lda/Facets/HyperswapV3Facet.t.sol | 40 +----------- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 19 ------ .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 37 +++-------- .../Lda/Facets/SyncSwapV2Facet.t.sol | 61 ++++++++----------- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 28 +-------- 9 files changed, 68 insertions(+), 198 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 9293e6d5e..e3cc5fe36 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -197,15 +197,6 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { function _setupForkConfig() internal virtual; - // function test_ContractIsSetUpCorrectly() public { - // assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); - // assertEq( - // liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), - // true - // ); - // assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); - // } - // ============================ Abstract DEX Tests ============================ /** * @notice Abstract test for basic token swapping functionality @@ -390,6 +381,10 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { assertGt(outAfter - outBefore, 0, "Should receive tokens"); } + function _getDefaultAmount() internal virtual returns (uint256) { + return 1_000 * 1e18; // Default, can be overridden + } + // Keep the original function for backward compatibility function _executeAndVerifySwap( SwapTestParams memory params, @@ -414,26 +409,13 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { vm.expectRevert(expectedRevert); coreRouteFacet.processRoute( params.tokenIn, - params.amountIn, + params.commandType == CommandType.ProcessMyERC20 + ? params.amountIn + : params.amountIn - 1, params.tokenOut, 0, // minOut = 0 for tests params.recipient, route ); } - - // Optional helper - function _adjustAmountFor( - CommandType cmd, - uint256 amount - ) internal view returns (uint256) { - if ( - cmd == CommandType.ProcessMyERC20 && - aggregatorUndrainMinusOne && - amount > 0 - ) { - return amount - 1; - } - return amount; - } } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 55b51e314..16e6e6214 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -118,10 +118,9 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { function _executeUniV3StyleSwapAuto( UniV3AutoSwapParams memory params ) internal { - uint256 amountIn = _adjustAmountFor( - params.commandType, - params.amountIn - ); + uint256 amountIn = params.commandType == CommandType.ProcessMyERC20 + ? params.amountIn + 1 + : params.amountIn; // Fund the appropriate account if (params.commandType == CommandType.ProcessMyERC20) { @@ -168,4 +167,26 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.stopPrank(); } + + function _runStandardSwapTest(UniV3AutoSwapParams memory params) internal { + _executeUniV3StyleSwapAuto(params); + } + + function test_CanSwap() public virtual override { + _runStandardSwapTest( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessUserERC20, + amountIn: _getDefaultAmount() + }) + ); + } + + function test_CanSwap_FromDexAggregator() public virtual override { + _runStandardSwapTest( + UniV3AutoSwapParams({ + commandType: CommandType.ProcessMyERC20, + amountIn: _getDefaultAmount() - 1 + }) + ); + } } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 3b4ef1ef7..c3c73fa43 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -158,7 +158,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn = IERC20(APE_ETH_TOKEN); tokenOut = IERC20(WETH_TOKEN); poolInOut = ALGEBRA_POOL_APECHAIN; - aggregatorUndrainMinusOne = true; } // Override the abstract test with Algebra implementation diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 98c0780ee..786997a54 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -21,24 +21,5 @@ contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn = IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); // HLN tokenOut = IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); // USDT0 uniV3Pool = 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; // ENOSYS_V3_POOL - aggregatorUndrainMinusOne = true; // if needed - } - - function test_CanSwap() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessUserERC20, - amountIn: 1_000 * 1e18 - }) - ); - } - - function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessMyERC20, - amountIn: 1_000 * 1e18 - }) - ); } } diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 89d1a527b..994056975 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -3,18 +3,10 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { - /// @dev HyperswapV3 router on HyperEVM chain - IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = - IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); - /// @dev HyperswapV3 quoter on HyperEVM chain - IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = - IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); - function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "hyperevm", @@ -34,35 +26,7 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { ).getPool(address(tokenIn), address(tokenOut), 3000); } - function test_CanSwap() public override { - // Get pool first since it's dynamically calculated - uniV3Pool = HYPERSWAP_FACTORY.getPool( - address(tokenIn), - address(tokenOut), - 3000 - ); - - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessUserERC20, - amountIn: 1_000 * 1e6 - }) - ); - } - - function test_CanSwap_FromDexAggregator() public override { - // Get pool first since it's dynamically calculated - uniV3Pool = HYPERSWAP_FACTORY.getPool( - address(tokenIn), - address(tokenOut), - 3000 - ); - - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessMyERC20, - amountIn: 1_000 * 1e6 - 1 // Account for slot-undrain - }) - ); + function _getDefaultAmount() internal override returns (uint256) { + return 1_000 * 1e6; } } diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 3276b42f1..43a95ca3d 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -21,24 +21,5 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn = IERC20(0x5555555555555555555555555555555555555555); // WHYPE tokenOut = IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); // LHYPE uniV3Pool = 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; // WHYPE_LHYPE_POOL - aggregatorUndrainMinusOne = true; // if needed - } - - function test_CanSwap() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessUserERC20, - amountIn: 1_000 * 1e18 - }) - ); - } - - function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessMyERC20, - amountIn: 1_000 * 1e18 - 1 // Account for slot-undrain - }) - ); } } diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index c26a7a571..2f841b81e 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -22,33 +22,13 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn = IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); // SOROS tokenOut = IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); // C98 uniV3Pool = 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; // pool - aggregatorUndrainMinusOne = true; - } - - function test_CanSwap() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessUserERC20, - amountIn: 1_000 * 1e18 - }) - ); - } - - function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessMyERC20, - amountIn: 1_000 * 1e18 - 1 // Account for slot-undrain - }) - ); } function testRevert_RabbitSwapInvalidPool() public { - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), amountIn); + tokenIn.approve(address(ldaDiamond), _getDefaultAmount()); // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( @@ -64,7 +44,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -76,7 +56,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -89,11 +69,10 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { } function testRevert_RabbitSwapInvalidRecipient() public { - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), amountIn); + tokenIn.approve(address(ldaDiamond), _getDefaultAmount()); // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( @@ -109,7 +88,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -121,7 +100,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index ea72e10d1..52b098eac 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -15,11 +15,6 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { address internal constant USDC_WETH_POOL_V2 = address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); - IERC20 internal constant USDT = - IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); - address internal constant USDC_USDT_POOL_V1 = - address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); - function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "linea", @@ -50,14 +45,12 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenOut = IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); // USDT poolInMid = 0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308; // WETH-USDC V1 poolMidOut = 0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2; // USDC-USDT V1 - aggregatorUndrainMinusOne = true; } /// @notice Single‐pool swap: USER sends WETH → receives USDC function test_CanSwap() public override { // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); vm.startPrank(USER_SENDER); @@ -75,7 +68,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -87,7 +80,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -100,8 +93,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_PoolV2() public { // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); vm.startPrank(USER_SENDER); @@ -119,7 +111,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -131,7 +123,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -144,8 +136,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator() public override { // Fund the aggregator with 1 000 WETH - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), address(ldaDiamond), amountIn); + deal(address(tokenIn), address(ldaDiamond), _getDefaultAmount()); vm.startPrank(USER_SENDER); @@ -163,7 +154,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn - 1, // Account for slot-undrain + amountIn: _getDefaultAmount() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -175,7 +166,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn - 1, // Account for slot-undrain + amountIn: _getDefaultAmount() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -188,8 +179,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator_PoolV2() public { // Fund the aggregator with 1 000 WETH - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), address(ldaDiamond), amountIn); + deal(address(tokenIn), address(ldaDiamond), _getDefaultAmount()); vm.startPrank(USER_SENDER); @@ -207,7 +197,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn - 1, // Account for slot-undrain + amountIn: _getDefaultAmount() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -219,7 +209,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn - 1, // Account for slot-undrain + amountIn: _getDefaultAmount() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -231,11 +221,10 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { } function test_CanSwap_MultiHop() public override { - uint256 amountIn = 1_000e18; - deal(address(tokenIn), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), amountIn); + tokenIn.approve(address(ldaDiamond), _getDefaultAmount()); // Build swap data for both hops bytes memory firstSwapData = _buildSyncSwapV2SwapData( @@ -266,7 +255,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { params[0] = SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: SYNC_SWAP_VAULT, commandType: CommandType.ProcessUserERC20 @@ -291,7 +280,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -304,8 +293,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function testRevert_V1PoolMissingVaultAddress() public { // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); vm.startPrank(USER_SENDER); @@ -323,7 +311,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -335,7 +323,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -349,8 +337,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function testRevert_InvalidPoolOrRecipient() public { // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(tokenIn), USER_SENDER, amountIn); + deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); vm.startPrank(USER_SENDER); @@ -368,7 +355,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -380,7 +367,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -403,7 +390,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -415,7 +402,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: amountIn, + amountIn: _getDefaultAmount(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index cad0896b7..42f184444 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -18,33 +18,9 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn = IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); // USDC.e tokenOut = IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); // WXDC uniV3Pool = 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; // pool - aggregatorUndrainMinusOne = true; } - function test_CanSwap() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessUserERC20, - amountIn: 1_000 * 1e6 - }) - ); - } - - function test_CanSwap_FromDexAggregator() public override { - _executeUniV3StyleSwapAuto( - UniV3AutoSwapParams({ - commandType: CommandType.ProcessMyERC20, - amountIn: 5_000 * 1e6 - 1 // Account for slot-undrain - }) - ); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. - // XSwap V3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. XSwap V3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. + function _getDefaultAmount() internal override returns (uint256) { + return 1_000 * 1e6; } } From 48acd954c80e012f78f50f13fe782c6686de59db Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 15 Aug 2025 14:24:10 +0200 Subject: [PATCH 027/220] Refactored swap tests across various facets to use _getDefaultAmountForTokenIn, improved event handling, and standardized swap execution logic --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 27 +- .../Lda/BaseUniV3StyleDexFacet.t.sol | 12 +- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 469 ++++++++---------- .../Lda/Facets/HyperswapV3Facet.t.sol | 6 +- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 307 +++++------- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 16 +- .../Lda/Facets/SyncSwapV2Facet.t.sol | 56 ++- .../Lda/Facets/VelodromeV2Facet.t.sol | 15 +- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 6 +- 9 files changed, 408 insertions(+), 506 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index e3cc5fe36..1c6a564ed 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -291,11 +291,11 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { return route; } - // Modified function with additional events parameter function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, - ExpectedEvent[] memory additionalEvents + ExpectedEvent[] memory additionalEvents, + bool isFeeOnTransferToken ) internal { if (params.commandType != CommandType.ProcessMyERC20) { IERC20(params.tokenIn).approve( @@ -364,13 +364,19 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { // Check balance change on the correct address if (params.commandType == CommandType.ProcessMyERC20) { inAfter = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); - assertEq( + } else { + inAfter = IERC20(params.tokenIn).balanceOf(params.sender); + } + + // Use assertEq or assertApproxEqAbs based on isFeeOnTransferToken + if (isFeeOnTransferToken) { + assertApproxEqAbs( inBefore - inAfter, params.amountIn, + 1, // Allow 1 wei difference for fee-on-transfer tokens "Token spent mismatch" ); } else { - inAfter = IERC20(params.tokenIn).balanceOf(params.sender); assertEq( inBefore - inAfter, params.amountIn, @@ -381,16 +387,23 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { assertGt(outAfter - outBefore, 0, "Should receive tokens"); } - function _getDefaultAmount() internal virtual returns (uint256) { + function _getDefaultAmountForTokenIn() internal virtual returns (uint256) { return 1_000 * 1e18; // Default, can be overridden } - // Keep the original function for backward compatibility + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + ExpectedEvent[] memory additionalEvents + ) internal { + _executeAndVerifySwap(params, route, additionalEvents, false); + } + function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route ) internal { - _executeAndVerifySwap(params, route, new ExpectedEvent[](0)); + _executeAndVerifySwap(params, route, new ExpectedEvent[](0), false); } // Keep the revert case separate diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 16e6e6214..8b56a42bf 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -168,24 +168,20 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.stopPrank(); } - function _runStandardSwapTest(UniV3AutoSwapParams memory params) internal { - _executeUniV3StyleSwapAuto(params); - } - function test_CanSwap() public virtual override { - _runStandardSwapTest( + _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ commandType: CommandType.ProcessUserERC20, - amountIn: _getDefaultAmount() + amountIn: _getDefaultAmountForTokenIn() }) ); } function test_CanSwap_FromDexAggregator() public virtual override { - _runStandardSwapTest( + _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ commandType: CommandType.ProcessMyERC20, - amountIn: _getDefaultAmount() - 1 + amountIn: _getDefaultAmountForTokenIn() - 1 }) ); } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index c3c73fa43..94c9be1cd 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -100,17 +100,11 @@ contract AlgebraLiquidityAdderHelper { contract AlgebraFacetTest is BaseDexFacetTest { AlgebraFacet internal algebraFacet; - address private constant APE_ETH_TOKEN = - 0xcF800F4948D16F23333508191B1B1591daF70438; - address private constant WETH_TOKEN = - 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; address private constant ALGEBRA_FACTORY_APECHAIN = 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; address private constant ALGEBRA_QUOTER_V2_APECHAIN = 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; - address private constant ALGEBRA_POOL_APECHAIN = - 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; - address private constant APE_ETH_HOLDER_APECHAIN = + address private constant RANDOM_APE_ETH_HOLDER_APECHAIN = address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); address private constant IMPOSSIBLE_POOL_ADDRESS = @@ -155,28 +149,37 @@ contract AlgebraFacetTest is BaseDexFacetTest { // NEW: slot wiring for primary pair function _setupDexEnv() internal override { - tokenIn = IERC20(APE_ETH_TOKEN); - tokenOut = IERC20(WETH_TOKEN); - poolInOut = ALGEBRA_POOL_APECHAIN; + tokenIn = IERC20(0xcF800F4948D16F23333508191B1B1591daF70438); // APE_ETH_TOKEN + tokenOut = IERC20(0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949); // WETH_TOKEN + poolInOut = 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; // ALGEBRA_POOL_APECHAIN + } + + function _getDefaultAmountForTokenIn() + internal + override + returns (uint256) + { + return 1_000 * 1e6; } // Override the abstract test with Algebra implementation function test_CanSwap_FromDexAggregator() public override { // Fund LDA from whale address - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(address(coreRouteFacet), 1 * 1e18); + vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); + IERC20(tokenIn).transfer( + address(coreRouteFacet), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); - _testAlgebraSwap( + _testSwap( AlgebraSwapTestParams({ from: address(coreRouteFacet), to: address(USER_SENDER), - tokenIn: APE_ETH_TOKEN, - amountIn: IERC20(APE_ETH_TOKEN).balanceOf( - address(coreRouteFacet) - ) - 1, - tokenOut: address(WETH_TOKEN), + tokenIn: address(tokenIn), + amountIn: _getDefaultAmountForTokenIn() - 1, + tokenOut: address(tokenOut), direction: SwapDirection.Token0ToToken1, supportsFeeOnTransfer: true }) @@ -186,77 +189,71 @@ contract AlgebraFacetTest is BaseDexFacetTest { } function test_CanSwap_FeeOnTransferToken() public { - uint256 amountIn = 534451326669177; - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); - - vm.startPrank(APE_ETH_HOLDER_APECHAIN); + vm.startPrank(RANDOM_APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), amountIn); + IERC20(tokenIn).approve( + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); // Build route for algebra swap with command code 2 (user funds) bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: APE_ETH_HOLDER_APECHAIN, - pool: ALGEBRA_POOL_APECHAIN, + tokenIn: address(tokenIn), + recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, + pool: poolInOut, supportsFeeOnTransfer: true }) ); - // 2. Build the final route with the command and length-prefixed swapData - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + sender: RANDOM_APE_ETH_HOLDER_APECHAIN, + recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, + commandType: CommandType.ProcessUserERC20 + }), swapData ); - // Track initial balance - uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( - APE_ETH_HOLDER_APECHAIN - ); - - // Execute the swap - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - amountIn, - WETH_TOKEN, - 0, // minOut = 0 for this test - APE_ETH_HOLDER_APECHAIN, - route - ); - - // Verify balances - uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( - APE_ETH_HOLDER_APECHAIN + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + sender: RANDOM_APE_ETH_HOLDER_APECHAIN, + recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, + commandType: CommandType.ProcessUserERC20 + }), + route, + new ExpectedEvent[](0), + true // This is a fee-on-transfer token ); - assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); vm.stopPrank(); } function test_CanSwap() public override { - vm.startPrank(APE_ETH_HOLDER_APECHAIN); + vm.startPrank(RANDOM_APE_ETH_HOLDER_APECHAIN); // Transfer tokens from whale to USER_SENDER - uint256 amountToTransfer = 100 * 1e18; - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); + uint256 amountToTransfer = _getDefaultAmountForTokenIn(); + IERC20(tokenIn).transfer(USER_SENDER, amountToTransfer); vm.stopPrank(); vm.startPrank(USER_SENDER); - _testAlgebraSwap( + _testSwap( AlgebraSwapTestParams({ from: USER_SENDER, to: USER_SENDER, - tokenIn: APE_ETH_TOKEN, - amountIn: 10 * 1e18, - tokenOut: address(WETH_TOKEN), + tokenIn: address(tokenIn), + amountIn: _getDefaultAmountForTokenIn(), + tokenOut: address(tokenOut), direction: SwapDirection.Token0ToToken1, supportsFeeOnTransfer: true }) @@ -268,17 +265,17 @@ contract AlgebraFacetTest is BaseDexFacetTest { function test_CanSwap_Reverse() public { test_CanSwap(); - uint256 amountIn = IERC20(address(WETH_TOKEN)).balanceOf(USER_SENDER); + uint256 amountIn = IERC20(address(tokenOut)).balanceOf(USER_SENDER); vm.startPrank(USER_SENDER); - _testAlgebraSwap( + _testSwap( AlgebraSwapTestParams({ from: USER_SENDER, to: USER_SENDER, - tokenIn: address(WETH_TOKEN), + tokenIn: address(tokenOut), amountIn: amountIn, - tokenOut: APE_ETH_TOKEN, + tokenOut: address(tokenIn), direction: SwapDirection.Token1ToToken0, supportsFeeOnTransfer: false }) @@ -312,8 +309,8 @@ contract AlgebraFacetTest is BaseDexFacetTest { // Test that the proper error is thrown when algebra swap fails function testRevert_SwapUnexpected() public { // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); + IERC20(tokenIn).transfer(USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); @@ -324,26 +321,29 @@ contract AlgebraFacetTest is BaseDexFacetTest { vm.mockCall( invalidPool, abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) + abi.encode(tokenIn) ); // Create a route with an invalid pool bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, + tokenIn: address(tokenIn), recipient: USER_SENDER, pool: invalidPool, supportsFeeOnTransfer: true }) ); - bytes memory invalidRoute = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix + bytes memory invalidRoute = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), swapData ); @@ -358,9 +358,9 @@ contract AlgebraFacetTest is BaseDexFacetTest { _executeAndVerifySwap( SwapTestParams({ - tokenIn: APE_ETH_TOKEN, - tokenOut: address(WETH_TOKEN), - amountIn: 1 * 1e18, + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -464,114 +464,73 @@ contract AlgebraFacetTest is BaseDexFacetTest { ) private { vm.startPrank(USER_SENDER); - uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( - USER_SENDER - ); - uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( - USER_SENDER - ); - // Approve spending IERC20(address(state.tokenA)).approve( address(ldaDiamond), state.amountIn ); - // Build route - bytes memory route = _buildMultiHopRouteForTest(state); + // Build route and execute swap + SwapTestParams[] memory swapParams = new SwapTestParams[](2); + bytes[] memory swapData = new bytes[](2); - // Execute swap - coreRouteFacet.processRoute( - address(state.tokenA), - state.amountIn, - address(state.tokenC), - 0, // No minimum amount out for testing - USER_SENDER, - route - ); - - // Verify results - _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); - - vm.stopPrank(); - } + // First hop: TokenA -> TokenB + swapParams[0] = SwapTestParams({ + tokenIn: address(state.tokenA), + tokenOut: address(state.tokenB), + amountIn: state.amountIn, + sender: USER_SENDER, + recipient: address(ldaDiamond), // Send to aggregator for next hop + commandType: CommandType.ProcessUserERC20 + }); - // Helper function to build the multi-hop route for test - function _buildMultiHopRouteForTest( - MultiHopTestState memory state - ) private view returns (bytes memory) { - // 1. Get the specific data payload for each hop - bytes memory firstHopData = _buildAlgebraSwapData( + // Build first hop swap data + swapData[0] = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, tokenIn: address(state.tokenA), - recipient: address(ldaDiamond), // Hop 1 sends to the contract itself + recipient: address(ldaDiamond), pool: state.pool1, supportsFeeOnTransfer: false }) ); - bytes memory secondHopData = _buildAlgebraSwapData( + // Second hop: TokenB -> TokenC + swapParams[1] = SwapTestParams({ + tokenIn: address(state.tokenB), + tokenOut: address(state.tokenC), + amountIn: 0, // Not used for ProcessMyERC20 + sender: address(ldaDiamond), + recipient: USER_SENDER, + commandType: CommandType.ProcessMyERC20 + }); + + // Build second hop swap data + swapData[1] = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessMyERC20, tokenIn: address(state.tokenB), - recipient: USER_SENDER, // Hop 2 sends to the final user + recipient: USER_SENDER, pool: state.pool2, supportsFeeOnTransfer: state.isFeeOnTransfer }) ); - // 2. Assemble the first full command with its length prefix - bytes memory firstCommand = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - state.tokenA, - uint8(1), - FULL_SHARE, - uint16(firstHopData.length), - firstHopData - ); - - // 3. Assemble the second full command with its length prefix - bytes memory secondCommand = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - state.tokenB, - uint8(1), // num splits for the second hop - FULL_SHARE, // full share for the second hop - uint16(secondHopData.length), - secondHopData - ); - - // 4. Concatenate the commands to create the final route - return bytes.concat(firstCommand, secondCommand); - } - - // Helper function to verify multi-hop results - function _verifyMultiHopResults( - MultiHopTestState memory state, - uint256 initialBalanceA, - uint256 initialBalanceC - ) private { - uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( - USER_SENDER - ); - uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( - USER_SENDER - ); + bytes memory route = _buildMultiHopRoute(swapParams, swapData); - assertApproxEqAbs( - initialBalanceA - finalBalanceA, - state.amountIn, - 1, // 1 wei tolerance - "TokenA spent amount mismatch" + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(state.tokenA), + tokenOut: address(state.tokenC), + amountIn: state.amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route ); - assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); - emit log_named_uint( - state.isFeeOnTransfer - ? "Output amount with fee tokens" - : "Output amount with regular tokens", - finalBalanceC - initialBalanceC - ); + vm.stopPrank(); } // Helper function to create an Algebra pool @@ -685,48 +644,25 @@ contract AlgebraFacetTest is BaseDexFacetTest { } // Helper function to test an Algebra swap - function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { + function _testSwap(AlgebraSwapTestParams memory params) internal { // Find or create a pool address pool = _getPool(params.tokenIn, params.tokenOut); - vm.label(pool, "AlgebraPool"); - - // Get token0 from pool for labeling - address token0 = IAlgebraPool(pool).token0(); - if (params.tokenIn == token0) { - vm.label( - params.tokenIn, - string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( - params.tokenOut, - string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") - ); - } else { - vm.label( - params.tokenIn, - string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( - params.tokenOut, - string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") - ); - } - - // Record initial balances - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); // Get expected output from QuoterV2 - uint256 expectedOutput = _getQuoteExactInput( - params.tokenIn, - params.tokenOut, - params.amountIn - ); - + // uint256 expectedOutput = _getQuoteExactInput( + // params.tokenIn, + // params.tokenOut, + // params.amountIn + // ); + + // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. + CommandType commandCode = params.from == address(ldaDiamond) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20; // 1. Pack the specific data for this swap bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper + commandCode: commandCode, // Placeholder, not used in this helper tokenIn: params.tokenIn, recipient: params.to, pool: pool, @@ -738,53 +674,52 @@ contract AlgebraFacetTest is BaseDexFacetTest { IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); // 3. Set up event expectations - address fromAddress = params.from == address(coreRouteFacet) - ? USER_SENDER - : params.from; + // address fromAddress = params.from == address(coreRouteFacet) + // ? USER_SENDER + // : params.from; + + ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](0); + // vm.expectEmit(true, true, true, false); + // emit Route( + // fromAddress, + // params.to, + // params.tokenIn, + // params.tokenOut, + // params.amountIn, + // expectedOutput, + // expectedOutput + // ); - vm.expectEmit(true, true, true, false); - emit Route( - fromAddress, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - expectedOutput, - expectedOutput + // 4. Build the route inline and execute the swap to save stack space + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: params.amountIn, + sender: params.from, + recipient: params.to, + commandType: params.from == address(coreRouteFacet) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 + }), + swapData ); - // 4. Build the route inline and execute the swap to save stack space - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - (expectedOutput * 995) / 1000, // minOut calculated inline - params.to, - abi.encodePacked( - uint8( - params.from == address(coreRouteFacet) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 - ), - params.tokenIn, - uint8(1), - FULL_SHARE, - uint16(swapData.length), - swapData - ) - ); - - // 5. Verify final balances - uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - - assertApproxEqAbs( - initialTokenIn - finalTokenIn, - params.amountIn, - 1, - "TokenIn amount mismatch" - ); - assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: params.amountIn, + sender: params.from, + recipient: params.to, + commandType: params.from == address(ldaDiamond) + ? CommandType.ProcessMyERC20 + : CommandType.ProcessUserERC20 + }), + route, + expectedEvents, + true // This is a fee-on-transfer token + ); } function _getPool( @@ -811,8 +746,8 @@ contract AlgebraFacetTest is BaseDexFacetTest { function testRevert_AlgebraSwap_ZeroAddressPool() public { // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); + IERC20(tokenIn).transfer(USER_SENDER, 1 * 1e18); vm.startPrank(USER_SENDER); @@ -820,33 +755,36 @@ contract AlgebraFacetTest is BaseDexFacetTest { vm.mockCall( address(0), abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) + abi.encode(tokenIn) ); // Build route with address(0) as pool bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, + tokenIn: address(tokenIn), recipient: USER_SENDER, pool: address(0), // Zero address pool supportsFeeOnTransfer: true }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: 1 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), swapData ); _executeAndVerifySwap( SwapTestParams({ - tokenIn: APE_ETH_TOKEN, - tokenOut: address(WETH_TOKEN), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, @@ -862,7 +800,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { // // Transfer tokens from whale to user - // vm.prank(APE_ETH_HOLDER_APECHAIN); + // vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); // vm.startPrank(USER_SENDER); @@ -915,42 +853,45 @@ contract AlgebraFacetTest is BaseDexFacetTest { function testRevert_AlgebraSwap_ZeroAddressRecipient() public { // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); + IERC20(tokenIn).transfer(USER_SENDER, 1 * 1e18); vm.startPrank(USER_SENDER); // Mock token0() call on the pool vm.mockCall( - ALGEBRA_POOL_APECHAIN, + poolInOut, abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) + abi.encode(tokenIn) ); // Build route with address(0) as recipient bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, + tokenIn: address(tokenIn), recipient: address(0), // Zero address recipient - pool: ALGEBRA_POOL_APECHAIN, + pool: poolInOut, supportsFeeOnTransfer: true }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: 1 * 1e18, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), swapData ); _executeAndVerifySwap( SwapTestParams({ - tokenIn: APE_ETH_TOKEN, - tokenOut: address(WETH_TOKEN), + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), amountIn: 1 * 1e18, sender: USER_SENDER, recipient: USER_SENDER, diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 994056975..812600c05 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -26,7 +26,11 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { ).getPool(address(tokenIn), address(tokenOut), 3000); } - function _getDefaultAmount() internal override returns (uint256) { + function _getDefaultAmountForTokenIn() + internal + override + returns (uint256) + { return 1_000 * 1e6; } } diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 8a9773249..bedcf403d 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -10,8 +10,6 @@ import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; contract IzumiV3FacetTest is BaseDexFacetTest { IzumiV3Facet internal izumiV3Facet; - uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals - // structs struct IzumiV3SwapTestParams { address from; @@ -51,6 +49,14 @@ contract IzumiV3FacetTest is BaseDexFacetTest { izumiV3Facet = IzumiV3Facet(facetAddress); } + function _getDefaultAmountForTokenIn() + internal + override + returns (uint256) + { + return 100 * 1e6; // 100 USDC with 6 decimals + } + // NEW function _setupDexEnv() internal override { tokenIn = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); // USDC @@ -62,31 +68,54 @@ contract IzumiV3FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator() public override { // Test USDC -> WETH - deal(address(tokenIn), address(coreRouteFacet), AMOUNT_USDC); + deal( + address(tokenIn), + address(coreRouteFacet), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); - _testSwap( - IzumiV3SwapTestParams({ - from: address(coreRouteFacet), - to: USER_SENDER, + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: poolInMid, + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER + }) + ); + + bytes memory route = _buildBaseRoute( + SwapTestParams({ tokenIn: address(tokenIn), - amountIn: AMOUNT_USDC, tokenOut: address(tokenMid), - direction: SwapDirection.Token1ToToken0 - }) + amountIn: _getDefaultAmountForTokenIn() - 1, // -1 for undrain protection + sender: address(coreRouteFacet), + recipient: USER_SENDER, + commandType: CommandType.ProcessMyERC20 + }), + swapData ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn() - 1, // -1 for undrain protection + sender: address(coreRouteFacet), + recipient: USER_SENDER, + commandType: CommandType.ProcessMyERC20 + }), + route + ); + vm.stopPrank(); } function test_CanSwap_MultiHop() public override { // Fund the sender with tokens - uint256 amountIn = AMOUNT_USDC; + uint256 amountIn = _getDefaultAmountForTokenIn(); deal(address(tokenIn), USER_SENDER, amountIn); - // Capture initial token balances - uint256 initialBalanceIn = IERC20(tokenIn).balanceOf(USER_SENDER); - uint256 initialBalanceOut = IERC20(tokenOut).balanceOf(USER_SENDER); - // Build first swap data: USDC -> WETH bytes memory firstSwapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ @@ -133,47 +162,31 @@ contract IzumiV3FacetTest is BaseDexFacetTest { bytes memory route = _buildMultiHopRoute(params, swapData); - // Approve tokens vm.startPrank(USER_SENDER); - IERC20(tokenIn).approve(address(ldaDiamond), amountIn); - // Execute the swap - uint256 amountOut = coreRouteFacet.processRoute( - address(tokenIn), - amountIn, - address(tokenOut), - 0, // No minimum amount for testing - USER_SENDER, + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), route ); - vm.stopPrank(); - - // Verify balances - uint256 finalBalanceIn = IERC20(address(tokenIn)).balanceOf( - USER_SENDER - ); - uint256 finalBalanceOut = IERC20(address(tokenOut)).balanceOf( - USER_SENDER - ); - assertEq( - initialBalanceIn - finalBalanceIn, - amountIn, - "TokenIn amount mismatch" - ); - assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); - assertEq( - amountOut, - finalBalanceOut - initialBalanceOut, - "AmountOut mismatch" - ); + vm.stopPrank(); } function test_CanSwap() public override { - deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); - IERC20(tokenIn).approve(address(ldaDiamond), AMOUNT_USDC); + IERC20(tokenIn).approve( + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ @@ -183,40 +196,56 @@ contract IzumiV3FacetTest is BaseDexFacetTest { }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(tokenIn), - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), swapData ); - vm.expectEmit(true, true, true, false); - emit Route( - USER_SENDER, - USER_RECEIVER, - address(tokenIn), - address(tokenMid), - AMOUNT_USDC, - 0, - 0 - ); + // Create expected event + ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](0); + // bytes[] memory eventParams = new bytes[](7); + // eventParams[0] = abi.encode(USER_SENDER); + // eventParams[1] = abi.encode(USER_RECEIVER); + // eventParams[2] = abi.encode(address(tokenIn)); + // eventParams[3] = abi.encode(address(tokenMid)); + // eventParams[4] = abi.encode(AMOUNT_USDC); + // eventParams[5] = abi.encode(uint256(0)); // minOut + // eventParams[6] = abi.encode(uint256(0)); // amountOut + + // expectedEvents[0] = ExpectedEvent({ + // checkTopic1: true, + // checkTopic2: true, + // checkTopic3: true, + // checkData: false, + // eventSelector: keccak256("Route(address,address,address,address,uint256,uint256,uint256)"), + // eventParams: eventParams + // }); - coreRouteFacet.processRoute( - address(tokenIn), - AMOUNT_USDC, - address(tokenMid), - 0, - USER_RECEIVER, - route + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + route, + expectedEvents ); vm.stopPrank(); } function testRevert_IzumiV3SwapUnexpected() public { - deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); @@ -232,12 +261,15 @@ contract IzumiV3FacetTest is BaseDexFacetTest { ); // create a route with an invalid pool - bytes memory invalidRoute = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(tokenIn), - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix + bytes memory invalidRoute = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), swapData ); @@ -252,7 +284,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: AMOUNT_USDC, + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -266,7 +298,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { } function testRevert_UnexpectedCallbackSender() public { - deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); // Set up the expected callback sender through the diamond vm.store( @@ -289,7 +321,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { } function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { - deal(address(tokenIn), USER_SENDER, AMOUNT_USDC); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); // Set the expected callback sender through the diamond storage vm.store( @@ -321,12 +353,15 @@ contract IzumiV3FacetTest is BaseDexFacetTest { }) ); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(tokenMid), - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenMid), + tokenOut: address(tokenIn), + amountIn: type(uint216).max, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), swapData ); @@ -346,108 +381,6 @@ contract IzumiV3FacetTest is BaseDexFacetTest { vm.stopPrank(); } - function _testSwap(IzumiV3SwapTestParams memory params) internal { - // Fund the sender with tokens if not the contract itself - if (params.from != address(coreRouteFacet)) { - deal(address(params.tokenIn), params.from, params.amountIn); - } - - // Capture initial token balances - uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( - params.from - ); - uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( - params.to - ); - - // Build the route based on the command type - CommandType commandCode = params.from == address(coreRouteFacet) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; - - bytes memory swapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: poolInMid, - direction: params.direction == SwapDirection.Token0ToToken1 - ? SwapDirection.Token0ToToken1 - : SwapDirection.Token1ToToken0, - recipient: params.to - }) - ); - - bytes memory route = abi.encodePacked( - uint8(commandCode), - params.tokenIn, - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix - swapData - ); - - // Approve tokens if necessary - if (params.from == USER_SENDER) { - vm.startPrank(USER_SENDER); - IERC20(address(params.tokenIn)).approve( - address(ldaDiamond), - params.amountIn - ); - } - - // Expect the Route event emission - address from = params.from == address(coreRouteFacet) - ? USER_SENDER - : params.from; - - vm.expectEmit(true, true, true, false); - emit Route( - from, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - 0, // No minimum amount enforced in test - 0 // Actual amount will be checked after the swap - ); - - // Execute the swap - uint256 amountOut = coreRouteFacet.processRoute( - address(params.tokenIn), - params.amountIn, - address(params.tokenOut), - 0, // No minimum amount for testing - params.to, - route - ); - - if (params.from == USER_SENDER) { - vm.stopPrank(); - } - - // Verify balances have changed correctly - uint256 finalBalanceIn = IERC20(address(params.tokenIn)).balanceOf( - params.from - ); - uint256 finalBalanceOut = IERC20(address(params.tokenOut)).balanceOf( - params.to - ); - - assertApproxEqAbs( - initialBalanceIn - finalBalanceIn, - params.amountIn, - 1, // 1 wei tolerance because of undrain protection for dex aggregator - "TokenIn amount mismatch" - ); - assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); - assertEq( - amountOut, - finalBalanceOut - initialBalanceOut, - "AmountOut mismatch" - ); - - emit log_named_uint("Amount In", params.amountIn); - emit log_named_uint("Amount Out", amountOut); - } - struct IzumiV3SwapParams { address pool; SwapDirection direction; diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 2f841b81e..8306512bf 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -25,10 +25,10 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { } function testRevert_RabbitSwapInvalidPool() public { - deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), _getDefaultAmount()); + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( @@ -44,7 +44,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -56,7 +56,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -69,10 +69,10 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { } function testRevert_RabbitSwapInvalidRecipient() public { - deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), _getDefaultAmount()); + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( @@ -88,7 +88,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -100,7 +100,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 52b098eac..ed803b2e0 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -50,7 +50,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { /// @notice Single‐pool swap: USER sends WETH → receives USDC function test_CanSwap() public override { // Transfer 1 000 WETH from whale to USER_SENDER - deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); @@ -68,7 +68,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -80,7 +80,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -93,7 +93,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_PoolV2() public { // Transfer 1 000 WETH from whale to USER_SENDER - deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); @@ -111,7 +111,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -123,7 +123,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -136,7 +136,11 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator() public override { // Fund the aggregator with 1 000 WETH - deal(address(tokenIn), address(ldaDiamond), _getDefaultAmount()); + deal( + address(tokenIn), + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); @@ -154,7 +158,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount() - 1, // Account for slot-undrain + amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -166,7 +170,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount() - 1, // Account for slot-undrain + amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -179,7 +183,11 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator_PoolV2() public { // Fund the aggregator with 1 000 WETH - deal(address(tokenIn), address(ldaDiamond), _getDefaultAmount()); + deal( + address(tokenIn), + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); @@ -197,7 +205,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount() - 1, // Account for slot-undrain + amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -209,7 +217,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount() - 1, // Account for slot-undrain + amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -221,10 +229,10 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { } function test_CanSwap_MultiHop() public override { - deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), _getDefaultAmount()); + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); // Build swap data for both hops bytes memory firstSwapData = _buildSyncSwapV2SwapData( @@ -255,7 +263,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { params[0] = SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: SYNC_SWAP_VAULT, commandType: CommandType.ProcessUserERC20 @@ -280,7 +288,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -293,7 +301,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function testRevert_V1PoolMissingVaultAddress() public { // Transfer 1 000 WETH from whale to USER_SENDER - deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); @@ -311,7 +319,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -323,7 +331,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -337,7 +345,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { function testRevert_InvalidPoolOrRecipient() public { // Transfer 1 000 WETH from whale to USER_SENDER - deal(address(tokenIn), USER_SENDER, _getDefaultAmount()); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); @@ -355,7 +363,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -367,7 +375,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -390,7 +398,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -402,7 +410,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: _getDefaultAmount(), + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 8c341f4c7..ad143dd0b 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -652,12 +652,15 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { }) ); // build the route. - bytes memory route = abi.encodePacked( - uint8(commandCode), - params.tokenIn, - uint8(1), // num splits - FULL_SHARE, - uint16(swapData.length), // <--- Add length prefix + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: params.tokenIn, + tokenOut: params.tokenOut, + amountIn: params.amountIn, + sender: params.from, + recipient: params.to, + commandType: commandCode + }), swapData ); diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 42f184444..7f7fb64d7 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -20,7 +20,11 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { uniV3Pool = 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; // pool } - function _getDefaultAmount() internal override returns (uint256) { + function _getDefaultAmountForTokenIn() + internal + override + returns (uint256) + { return 1_000 * 1e6; } } From cd176e6cd8c8116e40689e28780358fb660d304b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 15 Aug 2025 15:13:02 +0200 Subject: [PATCH 028/220] changes --- test/solidity/Periphery/Lda/BaseDexFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 1c6a564ed..6c7c8116c 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -388,7 +388,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { } function _getDefaultAmountForTokenIn() internal virtual returns (uint256) { - return 1_000 * 1e18; // Default, can be overridden + return 1_000 * 1e18; } function _executeAndVerifySwap( diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 94c9be1cd..d8cd85f69 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -773,7 +773,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: 1 * 1e18, + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -785,7 +785,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: 1 * 1e18, + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -880,7 +880,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: 1 * 1e18, + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -892,7 +892,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), - amountIn: 1 * 1e18, + amountIn: _getDefaultAmountForTokenIn(), sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 From fedf71c11610cf4007d3be0fac14c25833343acf Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 15 Aug 2025 19:24:26 +0200 Subject: [PATCH 029/220] emit Route event --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 149 ++++++++++++++---- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 23 +-- .../Lda/Facets/VelodromeV2Facet.t.sol | 16 +- 3 files changed, 122 insertions(+), 66 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 6c7c8116c..1d34c3306 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -73,6 +73,9 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { error NoHopsProvided(); error InvalidForkConfig(string reason); error UnknownNetwork(string name); + error InvalidTopicLength(); + error TooManyIndexedParams(); + error DynamicParamsNotSupported(); // Add this struct to hold event expectations struct ExpectedEvent { @@ -82,6 +85,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { bool checkData; bytes32 eventSelector; // The event selector (keccak256 hash of the event signature) bytes[] eventParams; // The event parameters, each encoded separately + uint8[] indexedParamIndices; // indices of params that are indexed (→ topics 1..3) } // At top-level state @@ -235,6 +239,10 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { address sender; address recipient; CommandType commandType; + // // New optional fields for expected output verification + // uint256 expectedMinOut; // Optional: if non-zero, used as minOut in Route event + // uint256 expectedExactOut; // Optional: if non-zero, used as amountOut in Route event + // bool checkRouteData; // Optional: whether to check Route event data (defaults to false) } // Add this struct for route building @@ -316,39 +324,22 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { inBefore = IERC20(params.tokenIn).balanceOf(params.sender); } - // Set up additional event expectations first - for (uint256 i = 0; i < additionalEvents.length; i++) { - vm.expectEmit( - additionalEvents[i].checkTopic1, - additionalEvents[i].checkTopic2, - additionalEvents[i].checkTopic3, - additionalEvents[i].checkData - ); - - // Encode event parameters - bytes memory encodedParams; - for ( - uint256 j = 0; - j < additionalEvents[i].eventParams.length; - j++ - ) { - encodedParams = bytes.concat( - encodedParams, - additionalEvents[i].eventParams[j] - ); - } + address fromAddress = params.sender == address(ldaDiamond) + ? USER_SENDER + : params.sender; - // Emit the event with the correct selector and parameters - assembly { - let selector := mload( - add(mload(add(additionalEvents, 0x20)), 0x80) - ) // access eventSelector - mstore(0x00, selector) - mstore(0x04, encodedParams) - log1(0x00, add(0x04, mload(encodedParams)), selector) - } - } + _expectEvents(additionalEvents); + vm.expectEmit(true, true, true, false); + emit Route( + fromAddress, + params.recipient, + params.tokenIn, + params.tokenOut, + params.amountIn, + 0, + 0 + ); coreRouteFacet.processRoute( params.tokenIn, params.amountIn, @@ -431,4 +422,100 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { route ); } + + // helper: load a 32-byte topic from a 32-byte abi.encode(param) + function _asTopic(bytes memory enc) internal pure returns (bytes32 topic) { + if (enc.length != 32) revert InvalidTopicLength(); + assembly { + topic := mload(add(enc, 32)) + } + } + + /** + * @notice Sets up event expectations for a list of events + * @param events Array of events to expect + */ + function _expectEvents(ExpectedEvent[] memory events) internal { + for (uint256 i = 0; i < events.length; i++) { + _expectEvent(events[i]); + } + } + + /** + * @notice Sets up expectation for a single event + * @param evt The event to expect with its check parameters and data + */ + function _expectEvent(ExpectedEvent memory evt) internal { + vm.expectEmit( + evt.checkTopic1, + evt.checkTopic2, + evt.checkTopic3, + evt.checkData + ); + + // Build topics (topic0 = selector; topics1..3 from indexedParamIndices) + bytes32 topic0 = evt.eventSelector; + uint8[] memory idx = evt.indexedParamIndices; + + bytes32 t1; + bytes32 t2; + bytes32 t3; + + uint256 topicsCount = idx.length; + if (topicsCount > 3) { + revert TooManyIndexedParams(); + } + if (topicsCount >= 1) { + t1 = _asTopic(evt.eventParams[idx[0]]); + } + if (topicsCount >= 2) { + t2 = _asTopic(evt.eventParams[idx[1]]); + } + if (topicsCount == 3) { + t3 = _asTopic(evt.eventParams[idx[2]]); + } + + // Build data (non-indexed params in event order) + bytes memory data; + if (evt.checkData) { + // Only support static params for now (each abi.encode(param) must be 32 bytes) + uint256 total = evt.eventParams.length; + bool[8] memory isIndexed; // up to 8 params; expand if needed + for (uint256 k = 0; k < topicsCount; k++) { + uint8 pos = idx[k]; + if (pos < isIndexed.length) isIndexed[pos] = true; + } + + for (uint256 p = 0; p < total; p++) { + if (!isIndexed[p]) { + bytes memory enc = evt.eventParams[p]; + if (enc.length != 32) { + revert DynamicParamsNotSupported(); + } + data = bytes.concat(data, enc); + } + } + } else { + data = ""; + } + + // Emit raw log with correct number of topics + assembly { + let ptr := add(data, 0x20) + let len := mload(data) + switch topicsCount + case 0 { + log1(ptr, len, topic0) + } + case 1 { + log2(ptr, len, topic0, t1) + } + case 2 { + log3(ptr, len, topic0, t1, t2) + } + case 3 { + log4(ptr, len, topic0, t1, t2, t3) + } + } + } } diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index bedcf403d..04d65935a 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -208,26 +208,6 @@ contract IzumiV3FacetTest is BaseDexFacetTest { swapData ); - // Create expected event - ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](0); - // bytes[] memory eventParams = new bytes[](7); - // eventParams[0] = abi.encode(USER_SENDER); - // eventParams[1] = abi.encode(USER_RECEIVER); - // eventParams[2] = abi.encode(address(tokenIn)); - // eventParams[3] = abi.encode(address(tokenMid)); - // eventParams[4] = abi.encode(AMOUNT_USDC); - // eventParams[5] = abi.encode(uint256(0)); // minOut - // eventParams[6] = abi.encode(uint256(0)); // amountOut - - // expectedEvents[0] = ExpectedEvent({ - // checkTopic1: true, - // checkTopic2: true, - // checkTopic3: true, - // checkData: false, - // eventSelector: keccak256("Route(address,address,address,address,uint256,uint256,uint256)"), - // eventParams: eventParams - // }); - _executeAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), @@ -237,8 +217,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { recipient: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), - route, - expectedEvents + route ); vm.stopPrank(); diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index ad143dd0b..6c88770c6 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -680,30 +680,20 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { eventParams[3] = abi.encode(abi.encode(params.tokenIn)); expectedEvents[0] = ExpectedEvent({ - checkTopic1: true, + checkTopic1: false, checkTopic2: false, checkTopic3: false, checkData: false, eventSelector: keccak256( "HookCalled(address,uint256,uint256,bytes)" ), - eventParams: eventParams + eventParams: eventParams, + indexedParamIndices: new uint8[](0) }); } else { expectedEvents = new ExpectedEvent[](0); } - // vm.expectEmit(true, true, true, true); - // emit Route( - // from, - // params.to, - // params.tokenIn, - // params.tokenOut, - // params.amountIn, - // amounts[1], - // amounts[1] - // ); - // execute the swap _executeAndVerifySwap( SwapTestParams({ From 8ec7a5e6583e414b089ff06bfbf5a43a886850c8 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 15 Aug 2025 20:16:56 +0200 Subject: [PATCH 030/220] Add minOut parameter to swap tests across multiple facets --- src/Periphery/Lda/LdaDiamond.sol | 63 +++++++++---------- src/Periphery/LiFiDEXAggregator.sol | 15 ----- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 41 ++++++++---- .../Lda/BaseUniV3StyleDexFacet.t.sol | 2 + .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 13 ++++ .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 11 ++++ .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 4 ++ .../Lda/Facets/SyncSwapV2Facet.t.sol | 23 ++++++- .../Lda/Facets/VelodromeV2Facet.t.sol | 21 ++++++- test/solidity/utils/TestBase.sol | 11 +++- 10 files changed, 138 insertions(+), 66 deletions(-) diff --git a/src/Periphery/Lda/LdaDiamond.sol b/src/Periphery/Lda/LdaDiamond.sol index 4a4180e0f..0abac3a1d 100644 --- a/src/Periphery/Lda/LdaDiamond.sol +++ b/src/Periphery/Lda/LdaDiamond.sol @@ -5,7 +5,6 @@ import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; // solhint-disable-next-line no-unused-import import { LibUtil } from "lifi/Libraries/LibUtil.sol"; -import { console2 } from "forge-std/console2.sol"; /// @title LDA Diamond /// @author LI.FI (https://li.fi) @@ -30,47 +29,47 @@ contract LdaDiamond { // Find facet for function that is called and execute the // function if a facet is found and return any value. // solhint-disable-next-line no-complex-fallback -fallback() external payable { - LibDiamond.DiamondStorage storage ds; - bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; + fallback() external payable { + LibDiamond.DiamondStorage storage ds; + bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; - // get diamond storage - assembly { - ds.slot := position - } + // get diamond storage + assembly { + ds.slot := position + } - // get facet from function selector - address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; + // get facet from function selector + address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; - if (facet == address(0)) { - revert LibDiamond.FunctionDoesNotExist(); - } + if (facet == address(0)) { + revert LibDiamond.FunctionDoesNotExist(); + } - // Execute external function from facet using delegatecall and return any value. - assembly { - // Forward all calldata to the facet - calldatacopy(0, 0, calldatasize()) + // Execute external function from facet using delegatecall and return any value. + assembly { + // Forward all calldata to the facet + calldatacopy(0, 0, calldatasize()) - // Perform the delegatecall - let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + // Perform the delegatecall + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) - // Copy the returned data - returndatacopy(0, 0, returndatasize()) + // Copy the returned data + returndatacopy(0, 0, returndatasize()) - // Bubble up the result using the correct opcode - switch result - case 0 { - // Revert with the data if the call failed - revert(0, returndatasize()) - } - default { - // Return the data if the call succeeded - return(0, returndatasize()) + // Bubble up the result using the correct opcode + switch result + case 0 { + // Revert with the data if the call failed + revert(0, returndatasize()) + } + default { + // Return the data if the call succeeded + return(0, returndatasize()) + } } } -} // Able to receive ether // solhint-disable-next-line no-empty-blocks receive() external payable {} -} \ No newline at end of file +} diff --git a/src/Periphery/LiFiDEXAggregator.sol b/src/Periphery/LiFiDEXAggregator.sol index da43d5414..92e5e1e45 100644 --- a/src/Periphery/LiFiDEXAggregator.sol +++ b/src/Periphery/LiFiDEXAggregator.sol @@ -11,7 +11,6 @@ import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { ISyncSwapVault } from "lifi/Interfaces/ISyncSwapVault.sol"; import { ISyncSwapPool } from "lifi/Interfaces/ISyncSwapPool.sol"; import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { console2 } from "forge-std/console2.sol"; address constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; @@ -1071,23 +1070,9 @@ contract LiFiDEXAggregator is WithdrawablePeriphery { address tokenIn, uint256 amountIn ) private { - console2.log("swapVelodromeV2"); - console2.log("stream before reading:"); - console2.logBytes(abi.encode(stream)); // Add this to see the raw stream data - - console2.log("from"); - console2.logAddress(from); - console2.log("tokenIn"); - console2.logAddress(tokenIn); address pool = stream.readAddress(); - console2.log("pool"); - console2.logAddress(pool); uint8 direction = stream.readUint8(); - console2.log("direction"); - console2.log(direction); address to = stream.readAddress(); - console2.log("to"); - console2.logAddress(to); if (pool == address(0) || to == address(0)) revert InvalidCallData(); // solhint-disable-next-line max-line-length bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 1d34c3306..b78e750fb 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -236,13 +236,10 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { address tokenIn; address tokenOut; uint256 amountIn; + uint256 minOut; // Added here - it's a swap parameter address sender; address recipient; CommandType commandType; - // // New optional fields for expected output verification - // uint256 expectedMinOut; // Optional: if non-zero, used as minOut in Route event - // uint256 expectedExactOut; // Optional: if non-zero, used as amountOut in Route event - // bool checkRouteData; // Optional: whether to check Route event data (defaults to false) } // Add this struct for route building @@ -299,11 +296,17 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { return route; } + struct RouteEventVerification { + uint256 expectedExactOut; // Only for event verification + bool checkData; + } + function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, ExpectedEvent[] memory additionalEvents, - bool isFeeOnTransferToken + bool isFeeOnTransferToken, + RouteEventVerification memory routeEventVerification ) internal { if (params.commandType != CommandType.ProcessMyERC20) { IERC20(params.tokenIn).approve( @@ -330,21 +333,22 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { _expectEvents(additionalEvents); - vm.expectEmit(true, true, true, false); + vm.expectEmit(true, true, true, routeEventVerification.checkData); emit Route( fromAddress, params.recipient, params.tokenIn, params.tokenOut, params.amountIn, - 0, - 0 + params.minOut, // Use minOut from SwapTestParams + routeEventVerification.expectedExactOut ); + coreRouteFacet.processRoute( params.tokenIn, params.amountIn, params.tokenOut, - 0, // minOut = 0 for tests + params.minOut, // Use minOut from SwapTestParams params.recipient, route ); @@ -385,16 +389,29 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, - ExpectedEvent[] memory additionalEvents + ExpectedEvent[] memory additionalEvents, + bool isFeeOnTransferToken ) internal { - _executeAndVerifySwap(params, route, additionalEvents, false); + _executeAndVerifySwap( + params, + route, + additionalEvents, + isFeeOnTransferToken, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); } function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route ) internal { - _executeAndVerifySwap(params, route, new ExpectedEvent[](0), false); + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + false, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); } // Keep the revert case separate diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 8b56a42bf..6c485bd02 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -146,6 +146,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: amountIn, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: params.commandType @@ -158,6 +159,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: amountIn, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: params.commandType diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index d8cd85f69..28766838d 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -212,6 +212,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: RANDOM_APE_ETH_HOLDER_APECHAIN, recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, commandType: CommandType.ProcessUserERC20 @@ -224,6 +225,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: RANDOM_APE_ETH_HOLDER_APECHAIN, recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, commandType: CommandType.ProcessUserERC20 @@ -340,6 +342,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -361,6 +364,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -479,6 +483,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(state.tokenA), tokenOut: address(state.tokenB), amountIn: state.amountIn, + minOut: 0, sender: USER_SENDER, recipient: address(ldaDiamond), // Send to aggregator for next hop commandType: CommandType.ProcessUserERC20 @@ -500,6 +505,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(state.tokenB), tokenOut: address(state.tokenC), amountIn: 0, // Not used for ProcessMyERC20 + minOut: 0, sender: address(ldaDiamond), recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -523,6 +529,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(state.tokenA), tokenOut: address(state.tokenC), amountIn: state.amountIn, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -696,6 +703,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, + minOut: 0, sender: params.from, recipient: params.to, commandType: params.from == address(coreRouteFacet) @@ -710,6 +718,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, + minOut: 0, sender: params.from, recipient: params.to, commandType: params.from == address(ldaDiamond) @@ -774,6 +783,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -786,6 +796,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -881,6 +892,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -893,6 +905,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 04d65935a..cc23651c4 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -89,6 +89,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // -1 for undrain protection + minOut: 0, sender: address(coreRouteFacet), recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -101,6 +102,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // -1 for undrain protection + minOut: 0, sender: address(coreRouteFacet), recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -143,6 +145,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: amountIn, + minOut: 0, sender: USER_SENDER, recipient: address(coreRouteFacet), commandType: CommandType.ProcessUserERC20 @@ -154,6 +157,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenMid), tokenOut: address(tokenOut), amountIn: 0, // Will be determined by first swap + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -169,6 +173,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: amountIn, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -201,6 +206,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 @@ -213,6 +219,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 @@ -245,6 +252,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -264,6 +272,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -337,6 +346,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenMid), tokenOut: address(tokenIn), amountIn: type(uint216).max, + minOut: 0, sender: USER_SENDER, recipient: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 @@ -349,6 +359,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { tokenIn: address(tokenMid), tokenOut: address(tokenIn), amountIn: type(uint216).max, + minOut: 0, sender: USER_SENDER, recipient: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 8306512bf..bd812e99b 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -45,6 +45,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -57,6 +58,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -89,6 +91,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -101,6 +104,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index ed803b2e0..2c9b17a25 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -69,6 +69,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -81,6 +82,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -112,6 +114,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -124,6 +127,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -159,6 +163,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -171,6 +176,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -206,6 +212,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -218,6 +225,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessMyERC20 @@ -264,6 +272,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: SYNC_SWAP_VAULT, commandType: CommandType.ProcessUserERC20 @@ -275,6 +284,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenMid), tokenOut: address(tokenOut), amountIn: 0, // Not used in ProcessOnePool + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessOnePool @@ -289,6 +299,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -320,6 +331,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -332,6 +344,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -364,6 +377,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -376,7 +390,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), - sender: USER_SENDER, + minOut: 0, + sender: USER_SENDER, // Send to next pool recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), @@ -399,7 +414,8 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), - sender: USER_SENDER, + minOut: 0, + sender: USER_SENDER, // Send to next pool recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), @@ -411,6 +427,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -441,6 +458,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: 1, // Arbitrary amount for this test + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -453,6 +471,7 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenOut), amountIn: 1, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 6c88770c6..118836488 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -276,6 +276,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenMid, amountIn: 1000 * 1e6, + minOut: 0, sender: USER_SENDER, recipient: params.pool2, // Send to next pool commandType: CommandType.ProcessUserERC20 @@ -297,7 +298,8 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: params.tokenOut, amountIn: params.amounts1[1], // Use output from first hop sender: params.pool2, - recipient: USER_SENDER, + minOut: 0, + recipient: USER_SENDER, // Send to next pool commandType: CommandType.ProcessOnePool }); @@ -333,6 +335,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: 1000 * 1e6, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -380,6 +383,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenMid, amountIn: 1000 * 1e6, + minOut: 0, sender: USER_SENDER, recipient: params.pool2, // Send to next pool commandType: CommandType.ProcessUserERC20 @@ -400,6 +404,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenOut: params.tokenOut, amountIn: params.amounts1[1], // Use output from first hop sender: params.pool2, + minOut: 0, recipient: USER_SENDER, commandType: CommandType.ProcessOnePool }); @@ -434,6 +439,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: 1000 * 1e6, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -482,6 +488,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: 1000 * 1e6, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -513,6 +520,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenIn), tokenOut: address(tokenMid), amountIn: 1000 * 1e6, + minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 @@ -545,6 +553,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenMid, amountIn: 1000 * 1e6, + minOut: 0, sender: USER_SENDER, recipient: params.pool2, // Send to next pool commandType: CommandType.ProcessUserERC20 @@ -564,6 +573,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenMid, tokenOut: params.tokenOut, amountIn: 0, // Not used in ProcessOnePool + minOut: 0, sender: params.pool2, recipient: USER_SENDER, commandType: CommandType.ProcessOnePool @@ -657,6 +667,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, + minOut: 0, sender: params.from, recipient: params.to, commandType: commandCode @@ -700,6 +711,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, + minOut: amounts[1], sender: params.from, recipient: params.to, commandType: params.from == address(ldaDiamond) @@ -707,7 +719,12 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { : CommandType.ProcessUserERC20 }), route, - expectedEvents + expectedEvents, + false, + RouteEventVerification({ + expectedExactOut: amounts[1], + checkData: true + }) ); } diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 2eeac233e..88501aef8 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -7,13 +7,11 @@ import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; import { LibSwap } from "lifi/Libraries/LibSwap.sol"; import { UniswapV2Router02 } from "../utils/Interfaces.sol"; import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; -import { LdaDiamondTest, LdaDiamond } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; import { ReentrancyError, ETHTransferFailed } from "src/Errors/GenericErrors.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { console2 } from "forge-std/console2.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; @@ -94,7 +92,14 @@ contract ReentrancyChecker is DSTest { //common utilities for forge tests // solhint-disable max-states-count -abstract contract TestBase is Test, TestBaseForksConstants, TestBaseRandomConstants, TestHelpers, DiamondTest, ILiFi { +abstract contract TestBase is + Test, + TestBaseForksConstants, + TestBaseRandomConstants, + TestHelpers, + DiamondTest, + ILiFi +{ address internal _facetTestContractAddress; uint64 internal currentTxId; bytes32 internal nextUser = keccak256(abi.encodePacked("user address")); From b9952958f5c40a208c19fe2d121a31733f1c6baf Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 18 Aug 2025 11:54:30 +0200 Subject: [PATCH 031/220] Refactor swap tests in AlgebraFacet and VelodromeV2Facet to utilize _getDefaultAmountForTokenIn --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 14 +++ .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 21 ---- .../Lda/Facets/VelodromeV2Facet.t.sol | 116 ++++++++++-------- 3 files changed, 77 insertions(+), 74 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index b78e750fb..97f2010b7 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -414,6 +414,20 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { ); } + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + bool isFeeOnTransferToken + ) internal { + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + isFeeOnTransferToken, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + // Keep the revert case separate function _executeAndVerifySwap( SwapTestParams memory params, diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 28766838d..4ce68c30d 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -677,26 +677,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { }) ); - // 2. Approve tokens - IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - - // 3. Set up event expectations - // address fromAddress = params.from == address(coreRouteFacet) - // ? USER_SENDER - // : params.from; - - ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](0); - // vm.expectEmit(true, true, true, false); - // emit Route( - // fromAddress, - // params.to, - // params.tokenIn, - // params.tokenOut, - // params.amountIn, - // expectedOutput, - // expectedOutput - // ); - // 4. Build the route inline and execute the swap to save stack space bytes memory route = _buildBaseRoute( SwapTestParams({ @@ -726,7 +706,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { : CommandType.ProcessUserERC20 }), route, - expectedEvents, true // This is a fee-on-transfer token ); } diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 118836488..889fa1f59 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -109,11 +109,24 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // pools vary by test; set per-test as locals or use POOL_IN_OUT for the default path } + function _getDefaultAmountForTokenIn() + internal + pure + override + returns (uint256) + { + return 1_000 * 1e6; + } + // ============================ Velodrome V2 Tests ============================ // no stable swap function test_CanSwap() public override { - deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); + deal( + address(tokenIn), + address(USER_SENDER), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); @@ -122,7 +135,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { from: address(USER_SENDER), to: address(USER_SENDER), tokenIn: address(tokenIn), - amountIn: 1_000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), tokenOut: address(tokenOut), stable: false, direction: SwapDirection.Token0ToToken1, @@ -156,7 +169,11 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } function test_CanSwap_Stable() public { - deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); + deal( + address(tokenIn), + address(USER_SENDER), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); _testSwap( @@ -164,7 +181,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { from: USER_SENDER, to: USER_SENDER, tokenIn: address(tokenIn), - amountIn: 1_000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), tokenOut: address(tokenOut), stable: true, direction: SwapDirection.Token0ToToken1, @@ -185,7 +202,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { from: USER_SENDER, to: USER_SENDER, tokenIn: address(tokenOut), - amountIn: 500 * 1e6, + amountIn: _getDefaultAmountForTokenIn() / 2, tokenOut: address(tokenIn), stable: false, direction: SwapDirection.Token1ToToken0, @@ -197,7 +214,11 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { function test_CanSwap_FromDexAggregator() public override { // // fund dex aggregator contract so that the contract holds USDC - deal(address(tokenIn), address(ldaDiamond), 100_000 * 1e6); + deal( + address(tokenIn), + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); _testSwap( @@ -219,7 +240,11 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } function test_CanSwap_FlashloanCallback() public { - deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); + deal( + address(tokenIn), + address(USER_SENDER), + _getDefaultAmountForTokenIn() + ); mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); @@ -229,7 +254,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { from: address(USER_SENDER), to: address(mockFlashloanCallbackReceiver), tokenIn: address(tokenIn), - amountIn: 1_000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), tokenOut: address(tokenOut), stable: false, direction: SwapDirection.Token0ToToken1, @@ -241,7 +266,11 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // Override the abstract test with VelodromeV2 implementation function test_CanSwap_MultiHop() public override { - deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); + deal( + address(tokenIn), + address(USER_SENDER), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); @@ -275,7 +304,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { swapParams[0] = SwapTestParams({ tokenIn: params.tokenIn, tokenOut: params.tokenMid, - amountIn: 1000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, recipient: params.pool2, // Send to next pool @@ -316,25 +345,11 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // Use the base _buildMultiHopRoute bytes memory route = _buildMultiHopRoute(swapParams, swapData); - // Approve and execute - IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - - // vm.expectEmit(true, true, true, true); - // emit Route( - // USER_SENDER, - // USER_SENDER, - // params.tokenIn, - // params.tokenOut, - // 1000 * 1e6, - // params.amounts2[1], - // params.amounts2[1] - // ); - _executeAndVerifySwap( SwapTestParams({ tokenIn: params.tokenIn, tokenOut: params.tokenOut, - amountIn: 1000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, @@ -348,7 +363,11 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } function test_CanSwap_MultiHop_WithStable() public { - deal(address(tokenIn), address(USER_SENDER), 1_000 * 1e6); + deal( + address(tokenIn), + address(USER_SENDER), + _getDefaultAmountForTokenIn() + ); vm.startPrank(USER_SENDER); @@ -382,7 +401,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { hopParams[0] = SwapTestParams({ tokenIn: params.tokenIn, tokenOut: params.tokenMid, - amountIn: 1000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, recipient: params.pool2, // Send to next pool @@ -420,25 +439,11 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { bytes memory route = _buildMultiHopRoute(hopParams, hopData); - // Approve and execute - IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - - // vm.expectEmit(true, true, true, true); - // emit Route( - // USER_SENDER, - // USER_SENDER, - // params.tokenIn, - // params.tokenOut, - // 1000 * 1e6, - // params.amounts2[1], - // params.amounts2[1] - // ); - _executeAndVerifySwap( SwapTestParams({ tokenIn: params.tokenIn, tokenOut: params.tokenOut, - amountIn: 1000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, @@ -481,13 +486,16 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { swapDataZeroPool ); - IERC20(address(tokenIn)).approve(address(ldaDiamond), 1000 * 1e6); + IERC20(address(tokenIn)).approve( + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); _executeAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: 1000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, @@ -519,7 +527,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), - amountIn: 1000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, recipient: USER_SENDER, @@ -552,7 +560,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { hopParams[0] = SwapTestParams({ tokenIn: params.tokenIn, tokenOut: params.tokenMid, - amountIn: 1000 * 1e6, + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, recipient: params.pool2, // Send to next pool @@ -590,9 +598,12 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { bytes memory route = _buildMultiHopRoute(hopParams, hopData); - deal(address(tokenIn), USER_SENDER, 1000 * 1e6); + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); - IERC20(address(tokenIn)).approve(address(ldaDiamond), 1000 * 1e6); + IERC20(address(tokenIn)).approve( + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves vm.mockCall( @@ -605,7 +616,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { coreRouteFacet.processRoute( address(tokenIn), - 1000 * 1e6, + _getDefaultAmountForTokenIn(), address(tokenOut), 0, USER_SENDER, @@ -750,7 +761,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { factory: address(VELODROME_V2_FACTORY_REGISTRY) }); params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( - 1000 * 1e6, + _getDefaultAmountForTokenIn(), routes1 ); @@ -827,9 +838,8 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); // Calculate exact expected changes - uint256 amountInAfterFees = 1000 * - 1e6 - - ((1000 * 1e6 * params.pool1Fee) / 10000); + uint256 amountInAfterFees = _getDefaultAmountForTokenIn() - + ((_getDefaultAmountForTokenIn() * params.pool1Fee) / 10000); // Assert exact reserve changes for Pool1 if (token0Pool1 == params.tokenIn) { From 6f66efaa42f738998d38d873616d8a26c1779a49 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 18 Aug 2025 12:06:41 +0200 Subject: [PATCH 032/220] Add IUniV3LikePool interface and refactor pool variable usage in swap tests --- src/Interfaces/IUniV3StylePool.sol | 11 +++++++++++ .../Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 14 +++----------- .../Periphery/Lda/Facets/EnosysDexV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/HyperswapV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 4 ++-- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 2 +- 7 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 src/Interfaces/IUniV3StylePool.sol diff --git a/src/Interfaces/IUniV3StylePool.sol b/src/Interfaces/IUniV3StylePool.sol new file mode 100644 index 000000000..aa3246b98 --- /dev/null +++ b/src/Interfaces/IUniV3StylePool.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +/// @title IUniV3LikePool +/// @author LI.FI (https://li.fi) +/// @notice Interface for UniV3-style pools +/// @custom:version 1.0.0 +interface IUniV3LikePool { + function token0() external view returns (address); + function token1() external view returns (address); +} diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 6c485bd02..d57754006 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -2,20 +2,12 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { IUniV3LikePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; -// Minimal UniV3-like pool interface for direction detection -interface IUniV3LikePool { - function token0() external view returns (address); - function token1() external view returns (address); -} - abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { UniV3StyleFacet internal uniV3Facet; - // Single-pool slot for UniV3-style tests - address internal uniV3Pool; - struct UniV3SwapParams { address pool; SwapDirection direction; @@ -131,10 +123,10 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.startPrank(USER_SENDER); - SwapDirection direction = _getDirection(uniV3Pool, address(tokenIn)); + SwapDirection direction = _getDirection(poolInOut, address(tokenIn)); bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ - pool: uniV3Pool, + pool: poolInOut, direction: direction, recipient: USER_SENDER }) diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 786997a54..5df12347a 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -20,6 +20,6 @@ contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupDexEnv() internal override { tokenIn = IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); // HLN tokenOut = IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); // USDT0 - uniV3Pool = 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; // ENOSYS_V3_POOL + poolInOut = 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; // ENOSYS_V3_POOL } } diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 812600c05..eb14f9d9a 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -21,7 +21,7 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupDexEnv() internal override { tokenIn = IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); // USDT0 tokenOut = IERC20(0x5555555555555555555555555555555555555555); // WHYPE - uniV3Pool = IHyperswapV3Factory( + poolInOut = IHyperswapV3Factory( 0xB1c0fa0B789320044A6F623cFe5eBda9562602E3 ).getPool(address(tokenIn), address(tokenOut), 3000); } diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 43a95ca3d..cb298e8c8 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -20,6 +20,6 @@ contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupDexEnv() internal override { tokenIn = IERC20(0x5555555555555555555555555555555555555555); // WHYPE tokenOut = IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); // LHYPE - uniV3Pool = 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; // WHYPE_LHYPE_POOL + poolInOut = 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; // WHYPE_LHYPE_POOL } } diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index bd812e99b..03111495f 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -21,7 +21,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupDexEnv() internal override { tokenIn = IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); // SOROS tokenOut = IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); // C98 - uniV3Pool = 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; // pool + poolInOut = 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; // pool } function testRevert_RabbitSwapInvalidPool() public { @@ -79,7 +79,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ - pool: uniV3Pool, + pool: poolInOut, direction: SwapDirection.Token1ToToken0, recipient: address(0) // Invalid recipient address }) diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 7f7fb64d7..bd7527abe 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -17,7 +17,7 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function _setupDexEnv() internal override { tokenIn = IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); // USDC.e tokenOut = IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); // WXDC - uniV3Pool = 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; // pool + poolInOut = 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; // pool } function _getDefaultAmountForTokenIn() From 51581a14daaed712d0a5deb29e5d85cba1f77db4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 18 Aug 2025 12:44:11 +0200 Subject: [PATCH 033/220] implement onlyExpectedPool modifier in UniV3StyleFacet for callback verification --- src/Periphery/Lda/Facets/AlgebraFacet.sol | 4 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 75 ++++++------------- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 2 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 2 +- 4 files changed, 27 insertions(+), 56 deletions(-) diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index dbf474e80..b4ec87b7f 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -9,7 +9,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { InvalidCallData } from "../../../Errors/GenericErrors.sol"; -/// @title Algebra Facet +/// @title AlgebraFacet /// @author LI.FI (https://li.fi) /// @notice Handles Algebra swaps with callback management /// @custom:version 1.0.0 @@ -73,7 +73,7 @@ contract AlgebraFacet { revert AlgebraSwapUnexpected(); } - return 0; // Actual output amount tracked via balance checks in CoreFacet + return 0; } function algebraSwapCallback( diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 230fa358c..bdba7c279 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -27,8 +27,6 @@ contract UniV3StyleFacet { using LibInputStream2 for uint256; /// Constants /// - address internal constant IMPOSSIBLE_POOL_ADDRESS = - 0x0000000000000000000000000000000000000001; uint160 internal constant MIN_SQRT_RATIO = 4295128739; uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; @@ -36,6 +34,13 @@ contract UniV3StyleFacet { /// Errors /// error UniV3SwapUnexpected(); + /// Modifiers /// + modifier onlyExpectedPool() { + LibCallbackManager.verifyCallbackSender(); + _; + LibCallbackManager.clear(); + } + /// @notice Executes a UniswapV3 swap /// @param swapData The input stream containing swap parameters /// @param from Where to take liquidity for swap @@ -52,11 +57,7 @@ contract UniV3StyleFacet { bool direction = stream.readUint8() > 0; address recipient = stream.readAddress(); - if ( - pool == address(0) || - pool == IMPOSSIBLE_POOL_ADDRESS || - recipient == address(0) - ) { + if (pool == address(0) || recipient == address(0)) { revert InvalidCallData(); } @@ -94,149 +95,119 @@ contract UniV3StyleFacet { int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function pancakeV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function ramsesV2SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function xeiV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function dragonswapV2SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function agniSwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function fusionXV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function vvsV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function supV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function zebraV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function hyperswapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function laminarV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function xswapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function rabbitSwapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } function enosysdexV3SwapCallback( int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); } } diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 516967da4..a76f63a53 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -7,7 +7,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -/// @title VelodromeV2 Facet +/// @title VelodromeV2Facet /// @author LI.FI (https://li.fi) /// @notice Handles VelodromeV2 swaps with callback management /// @custom:version 1.0.0 diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 889fa1f59..13dd9ad87 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -106,7 +106,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn = IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); // USDC tokenMid = IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); // STG tokenOut = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); // STG - // pools vary by test; set per-test as locals or use POOL_IN_OUT for the default path + // pools vary by test; and they are fetched inside tests } function _getDefaultAmountForTokenIn() From 3bd29d8519074d0cff4597d60e77ae6bf45e0fbb Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 18 Aug 2025 12:44:24 +0200 Subject: [PATCH 034/220] implement onlyExpectedPool modifier in UniV3StyleFacet for callback verification --- .../Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index d57754006..93273d551 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { IUniV3LikePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { UniV3StyleFacet internal uniV3Facet; @@ -179,4 +180,21 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { }) ); } + + function testRevert_CallbackFromUnexpectedSender() public { + // No swap has armed the guard; expected == address(0) + vm.startPrank(USER_SENDER); + vm.expectRevert(LibCallbackManager.UnexpectedCallbackSender.selector); + // Call the facet’s callback directly on the diamond + (bool ok, ) = address(ldaDiamond).call( + abi.encodeWithSelector( + _getCallbackSelector(), + int256(1), + int256(1), + bytes("") + ) + ); + ok; + vm.stopPrank(); + } } From 1308e4c5263057a5985022f707d0a19ade6463a7 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 18 Aug 2025 15:39:31 +0200 Subject: [PATCH 035/220] Rename IUniV3LikePool to IUniV3StylePool, update imports accordingly, and add MockNoCallbackPool for testing swap behavior without callbacks --- src/Interfaces/IUniV3StylePool.sol | 4 +- .../Lda/BaseUniV3StyleDexFacet.t.sol | 58 +++++++++++++++++-- test/solidity/utils/MockNoCallbackPool.sol | 29 ++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 test/solidity/utils/MockNoCallbackPool.sol diff --git a/src/Interfaces/IUniV3StylePool.sol b/src/Interfaces/IUniV3StylePool.sol index aa3246b98..e02ab7e47 100644 --- a/src/Interfaces/IUniV3StylePool.sol +++ b/src/Interfaces/IUniV3StylePool.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -/// @title IUniV3LikePool +/// @title IUniV3StylePool /// @author LI.FI (https://li.fi) /// @notice Interface for UniV3-style pools /// @custom:version 1.0.0 -interface IUniV3LikePool { +interface IUniV3StylePool { function token0() external view returns (address); function token1() external view returns (address); } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 93273d551..15f8c29b7 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { IUniV3LikePool } from "lifi/Interfaces/IUniV3StylePool.sol"; -import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; +import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; +import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { UniV3StyleFacet internal uniV3Facet; @@ -96,8 +97,8 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { address pool, address tokenIn ) internal view returns (SwapDirection) { - address t0 = IUniV3LikePool(pool).token0(); - address t1 = IUniV3LikePool(pool).token1(); + address t0 = IUniV3StylePool(pool).token0(); + address t1 = IUniV3StylePool(pool).token1(); if (tokenIn == t0) return SwapDirection.Token0ToToken1; if (tokenIn == t1) return SwapDirection.Token1ToToken0; revert TokenNotInPool(tokenIn, pool); @@ -197,4 +198,53 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { ok; vm.stopPrank(); } + + function testRevert_SwapWithoutCallback() public { + // Deploy mock pool that doesn't call back + MockNoCallbackPool mockPool = new MockNoCallbackPool(); + + // Setup test params + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); + + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: address(mockPool), + direction: SwapDirection.Token0ToToken1, + recipient: USER_SENDER + }) + ); + + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + // Should revert because pool doesn't call back, leaving armed state + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route, + UniV3StyleFacet.UniV3SwapUnexpected.selector + ); + + vm.stopPrank(); + } } diff --git a/test/solidity/utils/MockNoCallbackPool.sol b/test/solidity/utils/MockNoCallbackPool.sol new file mode 100644 index 000000000..763098579 --- /dev/null +++ b/test/solidity/utils/MockNoCallbackPool.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; + +/// @title MockNoCallbackPool +/// @author LI.FI (https://li.fi) +/// @notice Mock pool that doesn't call back +/// @custom:version 1.0.0 +contract MockNoCallbackPool is IUniV3StylePool { + function token0() external pure returns (address) { + return address(1); + } + + function token1() external pure returns (address) { + return address(2); + } + + function swap( + address, + bool, + int256, + uint160, + bytes calldata + ) external pure returns (int256, int256) { + // Do nothing - don't call the callback + return (0, 0); + } +} From f417527fad99967a8b8eaed3a7e8ea1ee243d7fb Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 18 Aug 2025 19:59:11 +0200 Subject: [PATCH 036/220] Remove LdaDiamond contract and update CoreRouteFacet to utilize LibAsset for native asset handling. Add comprehensive unit tests for CoreRouteFacet functionality --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 22 +- src/Periphery/Lda/LdaDiamond.sol | 75 --- .../Periphery/Lda/CoreRouteFacet.t.sol | 452 ++++++++++++++++++ .../Periphery/Lda/utils/LdaDiamondTest.sol | 9 +- 4 files changed, 469 insertions(+), 89 deletions(-) delete mode 100644 src/Periphery/Lda/LdaDiamond.sol create mode 100644 test/solidity/Periphery/Lda/CoreRouteFacet.t.sol diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index e41f8e9a2..1d2bfe08d 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -7,8 +7,9 @@ import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; import { LibUtil } from "lifi/Libraries/LibUtil.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -/// @title Core Route Facet +/// @title CoreRouteFacet /// @author LI.FI (https://li.fi) /// @notice Handles route processing and selector-based swap dispatching for LDA 2.0 /// @custom:version 1.0.0 @@ -18,8 +19,6 @@ contract CoreRouteFacet is ReentrancyGuard { using LibInputStream2 for uint256; /// Constants /// - address internal constant NATIVE_ADDRESS = - 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address internal constant INTERNAL_INPUT_SOURCE = address(0); /// Events /// @@ -79,10 +78,10 @@ contract CoreRouteFacet is ReentrancyGuard { address to, bytes calldata route ) private returns (uint256 amountOut) { - uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS + uint256 balanceInInitial = LibAsset.isNativeAsset(tokenIn) ? 0 : IERC20(tokenIn).balanceOf(msg.sender); - uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS + uint256 balanceOutInitial = LibAsset.isNativeAsset(tokenOut) ? address(to).balance : IERC20(tokenOut).balanceOf(to); @@ -102,7 +101,7 @@ contract CoreRouteFacet is ReentrancyGuard { if (step == 0) realAmountIn = usedAmount; } else if (commandCode == 4) { _processOnePool(stream); - } else if (commandCode == 6) { + } else if (commandCode == 5) { _applyPermit(tokenIn, stream); } else { revert UnknownCommandCode(); @@ -111,7 +110,7 @@ contract CoreRouteFacet is ReentrancyGuard { } } - uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS + uint256 balanceInFinal = LibAsset.isNativeAsset(tokenIn) ? 0 : IERC20(tokenIn).balanceOf(msg.sender); if (balanceInFinal + amountIn < balanceInInitial) { @@ -121,7 +120,7 @@ contract CoreRouteFacet is ReentrancyGuard { ); } - uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS + uint256 balanceOutFinal = LibAsset.isNativeAsset(tokenOut) ? address(to).balance : IERC20(tokenOut).balanceOf(to); if (balanceOutFinal < balanceOutInitial + amountOutMin) { @@ -166,7 +165,12 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 stream ) private returns (uint256 amountTotal) { amountTotal = address(this).balance; - _distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal); + _distributeAndSwap( + stream, + address(this), + INTERNAL_INPUT_SOURCE, + amountTotal + ); } /// @notice Processes ERC20 token from this contract balance diff --git a/src/Periphery/Lda/LdaDiamond.sol b/src/Periphery/Lda/LdaDiamond.sol deleted file mode 100644 index 0abac3a1d..000000000 --- a/src/Periphery/Lda/LdaDiamond.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; -// solhint-disable-next-line no-unused-import -import { LibUtil } from "lifi/Libraries/LibUtil.sol"; - -/// @title LDA Diamond -/// @author LI.FI (https://li.fi) -/// @notice EIP-2535 Diamond Proxy Contract for LiFi DEX Aggregator using selector-based dispatch. -/// @custom:version 2.0.0 -contract LdaDiamond { - constructor(address _contractOwner, address _diamondCutFacet) payable { - LibDiamond.setContractOwner(_contractOwner); - - // Add the diamondCut external function from the diamondCutFacet - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = IDiamondCut.diamondCut.selector; - cut[0] = LibDiamond.FacetCut({ - facetAddress: _diamondCutFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }); - LibDiamond.diamondCut(cut, address(0), ""); - } - - // Find facet for function that is called and execute the - // function if a facet is found and return any value. - // solhint-disable-next-line no-complex-fallback - fallback() external payable { - LibDiamond.DiamondStorage storage ds; - bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; - - // get diamond storage - assembly { - ds.slot := position - } - - // get facet from function selector - address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; - - if (facet == address(0)) { - revert LibDiamond.FunctionDoesNotExist(); - } - - // Execute external function from facet using delegatecall and return any value. - assembly { - // Forward all calldata to the facet - calldatacopy(0, 0, calldatasize()) - - // Perform the delegatecall - let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) - - // Copy the returned data - returndatacopy(0, 0, returndatasize()) - - // Bubble up the result using the correct opcode - switch result - case 0 { - // Revert with the data if the call failed - revert(0, returndatasize()) - } - default { - // Return the data if the call succeeded - return(0, returndatasize()) - } - } - } - - // Able to receive ether - // solhint-disable-next-line no-empty-blocks - receive() external payable {} -} diff --git a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol new file mode 100644 index 000000000..a03ed71f5 --- /dev/null +++ b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract MockNativeFacet { + using SafeTransferLib for address; + + function handleNative( + bytes memory payload, + address /*from*/, + address /*tokenIn*/, + uint256 amountIn + ) external payable returns (uint256) { + address recipient = abi.decode(payload, (address)); + recipient.safeTransferETH(amountIn); + return amountIn; + } +} + +contract MockPullERC20Facet { + using SafeERC20 for IERC20; + + // Pulls `amountIn` from msg.sender if `from == msg.sender` + function pull( + bytes memory /*payload*/, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom( + msg.sender, + address(this), + amountIn + ); + } + return amountIn; + } +} + +contract CoreRouteFacetTest is LdaDiamondTest { + using SafeTransferLib for address; + + CoreRouteFacet internal coreRouteFacet; + + uint16 internal constant FULL_SHARE = 65535; + bytes4 internal pullSel; + + function setUp() public override { + LdaDiamondTest.setUp(); + + // CoreRouteFacet + coreRouteFacet = new CoreRouteFacet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = CoreRouteFacet.processRoute.selector; + addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); + coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); + + // Register mock pull facet once and store selector + MockPullERC20Facet mockPull = new MockPullERC20Facet(); + bytes4[] memory sel = new bytes4[](1); + sel[0] = MockPullERC20Facet.pull.selector; + addFacet(address(ldaDiamond), address(mockPull), sel); + pullSel = sel[0]; + } + + // --- Helpers --- + + function _addMockNativeFacet() internal { + MockNativeFacet mock = new MockNativeFacet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockNativeFacet.handleNative.selector; + addFacet(address(ldaDiamond), address(mock), selectors); + } + + function _addMockPullFacet() internal returns (bytes4 sel) { + MockPullERC20Facet mock = new MockPullERC20Facet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockPullERC20Facet.pull.selector; + addFacet(address(ldaDiamond), address(mock), selectors); + return selectors[0]; + } + + function _signPermit( + ERC20PermitMock token, + uint256 ownerPk, + address owner, + address spender, + uint256 value, + uint256 deadline + ) internal view returns (uint8 v, bytes32 r, bytes32 s) { + bytes32 typehash = keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ); + bytes32 structHash = keccak256( + abi.encode( + typehash, + owner, + spender, + value, + token.nonces(owner), + deadline + ) + ); + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash) + ); + (v, r, s) = vm.sign(ownerPk, digest); + } + + // --- Tests --- + + function test_ProcessNativeCommandSendsEthToRecipient() public { + _addMockNativeFacet(); + + address recipient = USER_RECEIVER; + uint256 amount = 1 ether; + + // Fund the actual caller (USER_SENDER) + vm.deal(USER_SENDER, amount); + + // swapData: selector + abi.encode(recipient) + bytes memory swapData = abi.encodePacked( + MockNativeFacet.handleNative.selector, + abi.encode(recipient) + ); + + // route: [3][num=1][share=FULL_SHARE][len][swapData] + bytes memory route = abi.encodePacked( + uint8(3), + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ); + + uint256 beforeBal = recipient.balance; + + vm.prank(USER_SENDER); + coreRouteFacet.processRoute{ value: amount }( + address(0), // tokenIn: native + 0, + address(0), // tokenOut: native + 0, + recipient, + route + ); + + assertEq( + recipient.balance - beforeBal, + amount, + "recipient should receive full amount" + ); + } + + function test_ApplyPermitCommandSetsAllowanceOnDiamond() public { + uint256 ownerPk = 0xA11CE; + address owner = vm.addr(ownerPk); + uint256 init = 1_000_000e18; + ERC20PermitMock token = new ERC20PermitMock( + "Mock", + "MCK", + owner, + init + ); + + uint256 value = 500_000e18; + uint256 deadline = block.timestamp + 1 days; + + (uint8 v, bytes32 r, bytes32 s) = _signPermit( + token, + ownerPk, + owner, + address(ldaDiamond), + value, + deadline + ); + + // route: [5][value][deadline][v][r][s] + bytes memory route = abi.encodePacked( + uint8(5), + value, + deadline, + v, + r, + s + ); + + vm.prank(owner); + coreRouteFacet.processRoute( + address(token), // tokenIn used by _applyPermit + 0, + address(token), // tokenOut unused; must be a contract for balanceOf read + 0, + owner, + route + ); + + assertEq( + IERC20(address(token)).allowance(owner, address(ldaDiamond)), + value, + "permit allowance not set" + ); + } + + // Revert tests - use testRevert_ prefix + function testRevert_WhenCommandCodeIsUnknown() public { + bytes memory route = abi.encodePacked(uint8(9)); + + vm.prank(USER_SENDER); + vm.expectRevert(CoreRouteFacet.UnknownCommandCode.selector); + coreRouteFacet.processRoute( + address(0), + 0, + address(0), + 0, + USER_RECEIVER, + route + ); + } + + function testRevert_WhenSelectorIsUnknown() public { + ERC20PermitMock token = new ERC20PermitMock( + "Mock2", + "MCK2", + USER_SENDER, + 1e18 + ); + + // ProcessUserERC20: [2][tokenIn][num=1][share=FULL_SHARE][len=4][selector=0xdeadbeef] + bytes memory route = abi.encodePacked( + uint8(2), + address(token), + uint8(1), + FULL_SHARE, + uint16(4), + bytes4(0xdeadbeef) + ); + + vm.prank(USER_SENDER); + vm.expectRevert(CoreRouteFacet.UnknownSelector.selector); + coreRouteFacet.processRoute( + address(token), + 0, + address(token), + 0, + USER_RECEIVER, + route + ); + } + + // MinimalInputBalanceViolation: trigger by charging the user twice via two ProcessUserERC20 steps. + function testRevert_WhenInputBalanceIsInsufficientForTwoSteps() public { + // Prepare token and approvals + uint256 amountIn = 1e18; + ERC20PermitMock token = new ERC20PermitMock( + "Pull", + "PULL", + USER_SENDER, + 2 * amountIn + ); + + vm.startPrank(USER_SENDER); + IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); + + // Build one step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] + bytes memory step = abi.encodePacked( + uint8(2), + address(token), + uint8(1), + FULL_SHARE, + uint16(4), + pullSel + ); + + // Route with two identical steps → actual deduction = 2 * amountIn, but amountIn param = amountIn + bytes memory route = bytes.concat(step, step); + + // Expect MinimalInputBalanceViolation + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalInputBalanceViolation.selector, + amountIn, // available = final(0) + amountIn + 2 * amountIn // required = initial (we minted 2*amountIn) + ) + ); + coreRouteFacet.processRoute( + address(token), // tokenIn for balance checks + amountIn, // only 1e18 declared + address(0), // no tokenOut change + 0, + USER_RECEIVER, + route + ); + vm.stopPrank(); + } + + // Same as above but with tokenOut set to an ERC20, to ensure path-independent behavior. + function testRevert_WhenInputBalanceIsInsufficientForTwoStepsWithERC20Out() + public + { + uint256 amountIn = 1e18; + ERC20PermitMock token = new ERC20PermitMock( + "Pull2", + "PULL2", + USER_SENDER, + 2 * amountIn + ); + ERC20PermitMock tokenOut = new ERC20PermitMock( + "Out", + "OUT", + address(this), + 0 + ); + + vm.startPrank(USER_SENDER); + IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); + + bytes memory step = abi.encodePacked( + uint8(2), + address(token), + uint8(1), + FULL_SHARE, + uint16(4), + pullSel + ); + bytes memory route = bytes.concat(step, step); + + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalInputBalanceViolation.selector, + amountIn, // available = final(0) + amountIn + 2 * amountIn // required = initial (we minted 2*amountIn) + ) + ); + coreRouteFacet.processRoute( + address(token), + amountIn, + address(tokenOut), + 0, + USER_RECEIVER, + route + ); + vm.stopPrank(); + } + + function testRevert_WhenOutputBalanceIsZeroForERC20() public { + // Register the mock pull facet; it pulls tokenIn but never transfers tokenOut to `to` + bytes4 sel = pullSel; + + uint256 amountIn = 1e18; + ERC20PermitMock tokenIn = new ERC20PermitMock( + "IN", + "IN", + USER_SENDER, + amountIn + ); + ERC20PermitMock tokenOut = new ERC20PermitMock( + "OUT", + "OUT", + USER_RECEIVER, + 0 + ); // recipient starts at 0 + + // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] + bytes memory step = abi.encodePacked( + uint8(2), + address(tokenIn), + uint8(1), + FULL_SHARE, + uint16(4), + sel + ); + + bytes memory route = step; // single step; no tokenOut will be sent to recipient + + // Approve and call + vm.startPrank(USER_SENDER); + IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); + + // Expect MinimalOutputBalanceViolation with deltaOut = 0 + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalOutputBalanceViolation.selector, + uint256(0) + ) + ); + + coreRouteFacet.processRoute( + address(tokenIn), + amountIn, + address(tokenOut), // tokenOut is ERC20 + 1, // amountOutMin > 0 to trigger the revert when no output arrives + USER_RECEIVER, + route + ); + vm.stopPrank(); + } + + function testRevert_WhenOutputBalanceIsZeroForNative() public { + // Register the mock pull facet; it pulls tokenIn but never transfers native to `to` + bytes4 sel = pullSel; + + uint256 amountIn = 1e18; + ERC20PermitMock tokenIn = new ERC20PermitMock( + "IN2", + "IN2", + USER_SENDER, + amountIn + ); + + // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] + bytes memory step = abi.encodePacked( + uint8(2), + address(tokenIn), + uint8(1), + FULL_SHARE, + uint16(4), + sel + ); + + bytes memory route = step; // no native will be sent to recipient + + // Approve and call + vm.startPrank(USER_SENDER); + IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); + + // Expect MinimalOutputBalanceViolation with deltaOut = 0 (no ETH sent) + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalOutputBalanceViolation.selector, + uint256(0) + ) + ); + + coreRouteFacet.processRoute( + address(tokenIn), + amountIn, + address(0), // tokenOut is native + 1, // amountOutMin > 0 + USER_RECEIVER, + route + ); + vm.stopPrank(); + } +} diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 5dbcc2114..1f56b1958 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -1,16 +1,15 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LdaDiamond } from "lifi/Periphery/Lda/LdaDiamond.sol"; +import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; -import { TestHelpers } from "../../../utils/TestHelpers.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { - LdaDiamond internal ldaDiamond; + LiFiDiamond internal ldaDiamond; function setUp() public virtual { ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER); @@ -18,12 +17,12 @@ contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { function createLdaDiamond( address _diamondOwner - ) internal returns (LdaDiamond) { + ) internal returns (LiFiDiamond) { vm.startPrank(_diamondOwner); DiamondCutFacet diamondCut = new DiamondCutFacet(); DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); OwnershipFacet ownership = new OwnershipFacet(); - LdaDiamond diamond = new LdaDiamond( + LiFiDiamond diamond = new LiFiDiamond( _diamondOwner, address(diamondCut) ); From 1650f6a17b26f114852f990c6744a3c6d16b24ad Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 19 Aug 2025 11:15:17 +0200 Subject: [PATCH 037/220] Add LibPackedStream library for compact calldata handling and refactor AlgebraFacet, CoreRouteFacet, and other facets to utilize it. Updated tests accordingly --- ...ibInputStream2.sol => LibPackedStream.sol} | 57 +- src/Periphery/Lda/Facets/AlgebraFacet.sol | 6 +- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 294 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 6 +- src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 6 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 6 +- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 6 +- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 353 +- .../Periphery/Lda/CoreRouteFacet.t.sol | 349 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 6 + .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 7886 ++++++++--------- 11 files changed, 4498 insertions(+), 4477 deletions(-) rename src/Libraries/{LibInputStream2.sol => LibPackedStream.sol} (71%) diff --git a/src/Libraries/LibInputStream2.sol b/src/Libraries/LibPackedStream.sol similarity index 71% rename from src/Libraries/LibInputStream2.sol rename to src/Libraries/LibPackedStream.sol index 5769101ed..97fea31ea 100644 --- a/src/Libraries/LibInputStream2.sol +++ b/src/Libraries/LibPackedStream.sol @@ -1,25 +1,33 @@ // SPDX-License-Identifier: LGPL-3.0-only -/// @custom:version 1.0.2 pragma solidity ^0.8.17; -/// @title InputStream Library (Corrected) -/// @author LI.FI (https://li.fi) -/// @notice Provides functionality for reading data from packed byte streams. -library LibInputStream2 { +/// @title LibPackedStream +/// @author LI.FI +/// @notice Minimal byte-stream reader for compact calldata formats used by LDA v2. +/// @dev Public API is intentionally identical to the previous stream library. +library LibPackedStream { + /// @dev Returns the start and finish pointers for a bytes array. + function _bounds( + bytes memory data + ) private pure returns (uint256 start, uint256 finish) { + assembly { + start := add(data, 32) + finish := add(start, mload(data)) + } + } + /** @notice Creates stream from data * @param data data */ function createStream( bytes memory data ) internal pure returns (uint256 stream) { + (uint256 start, uint256 finish) = _bounds(data); assembly { stream := mload(0x40) + mstore(stream, start) + mstore(add(stream, 32), finish) mstore(0x40, add(stream, 64)) - let dataContentPtr := add(data, 32) - mstore(stream, dataContentPtr) - let length := mload(data) - let endPtr := add(dataContentPtr, length) - mstore(add(stream, 32), endPtr) } } @@ -58,32 +66,10 @@ library LibInputStream2 { } } - /** @notice Reads uint32 from the stream - * @param stream stream - */ - function readUint32(uint256 stream) internal pure returns (uint32 res) { - assembly { - let pos := mload(stream) - res := shr(224, mload(pos)) - mstore(stream, add(pos, 4)) - } - } - - /** @notice Reads bytes4 from the stream (for function selectors) - * @param stream stream - */ - function readBytes4(uint256 stream) internal pure returns (bytes4 res) { - assembly { - let pos := mload(stream) - res := mload(pos) - mstore(stream, add(pos, 4)) - } - } - /** @notice Reads uint256 from the stream * @param stream stream */ - function readUint(uint256 stream) internal pure returns (uint256 res) { + function readUint256(uint256 stream) internal pure returns (uint256 res) { assembly { let pos := mload(stream) res := mload(pos) @@ -108,10 +94,7 @@ library LibInputStream2 { function readAddress(uint256 stream) internal pure returns (address res) { assembly { let pos := mload(stream) - // CORRECT: Load a 32-byte word. The address is the first 20 bytes. - // To get it, we must shift the word right by (32-20)*8 = 96 bits. res := shr(96, mload(pos)) - // Then, advance the pointer by the size of an address mstore(stream, add(pos, 20)) } } @@ -132,7 +115,7 @@ library LibInputStream2 { uint256 stream ) internal view returns (bytes memory res) { // Read the 2-byte length prefix to know how many bytes to read next. - uint16 len = LibInputStream2.readUint16(stream); + uint16 len = LibPackedStream.readUint16(stream); if (len > 0) { uint256 pos; diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index b4ec87b7f..6419c9912 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibInputStream2 } from "../../../Libraries/LibInputStream2.sol"; +import { LibPackedStream } from "../../../Libraries/LibPackedStream.sol"; import { LibCallbackManager } from "../../../Libraries/LibCallbackManager.sol"; import { LibUniV3Logic } from "../../../Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "../../../Interfaces/IAlgebraPool.sol"; @@ -14,7 +14,7 @@ import { InvalidCallData } from "../../../Errors/GenericErrors.sol"; /// @notice Handles Algebra swaps with callback management /// @custom:version 1.0.0 contract AlgebraFacet { - using LibInputStream2 for uint256; + using LibPackedStream for uint256; using SafeERC20 for IERC20; /// Constants /// @@ -31,7 +31,7 @@ contract AlgebraFacet { address tokenIn, uint256 amountIn ) external returns (uint256) { - uint256 stream = LibInputStream2.createStream(swapData); + uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); bool direction = stream.readUint8() > 0; address recipient = stream.readAddress(); diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 1d2bfe08d..53d277642 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; -import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibUtil } from "lifi/Libraries/LibUtil.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; @@ -11,14 +11,15 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; /// @title CoreRouteFacet /// @author LI.FI (https://li.fi) -/// @notice Handles route processing and selector-based swap dispatching for LDA 2.0 +/// @notice Orchestrates LDA route execution by interpreting a compact byte stream. +/// @dev Public surface (ABI) is preserved; internals are reorganized for clarity. /// @custom:version 1.0.0 contract CoreRouteFacet is ReentrancyGuard { using SafeERC20 for IERC20; using SafeERC20 for IERC20Permit; - using LibInputStream2 for uint256; + using LibPackedStream for uint256; - /// Constants /// + /// @dev sentinel used to indicate that the input is already at the destination pool address internal constant INTERNAL_INPUT_SOURCE = address(0); /// Events /// @@ -39,15 +40,6 @@ contract CoreRouteFacet is ReentrancyGuard { error SwapFailed(); error UnknownSelector(); - /// External Methods /// - - /// @notice Processes a route for swapping tokens using selector-based dispatch - /// @param tokenIn Token to swap from - /// @param amountIn Amount of tokenIn to swap - /// @param tokenOut Token to swap to - /// @param amountOutMin Minimum amount of tokenOut expected - /// @param to Recipient of the final tokens - /// @param route Encoded route data containing swap instructions function processRoute( address tokenIn, uint256 amountIn, @@ -57,7 +49,7 @@ contract CoreRouteFacet is ReentrancyGuard { bytes calldata route ) external payable nonReentrant returns (uint256 amountOut) { return - _processRouteInternal( + _executeRoute( tokenIn, amountIn, tokenOut, @@ -67,10 +59,7 @@ contract CoreRouteFacet is ReentrancyGuard { ); } - /// Internal Methods /// - - /// @notice Internal route processing logic - function _processRouteInternal( + function _executeRoute( address tokenIn, uint256 amountIn, address tokenOut, @@ -78,77 +67,123 @@ contract CoreRouteFacet is ReentrancyGuard { address to, bytes calldata route ) private returns (uint256 amountOut) { - uint256 balanceInInitial = LibAsset.isNativeAsset(tokenIn) + (uint256 balInStart, uint256 balOutStart) = _precheck( + tokenIn, + tokenOut, + to + ); + + uint256 realAmountIn = _runRoute(tokenIn, amountIn, route); + + amountOut = _postcheck( + tokenIn, + tokenOut, + to, + amountIn, + amountOutMin, + balInStart, + balOutStart + ); + + emit Route( + msg.sender, + to, + tokenIn, + tokenOut, + realAmountIn, + amountOutMin, + amountOut + ); + } + + /// @notice Capture initial balances for input/output accounting. + function _precheck( + address tokenIn, + address tokenOut, + address to + ) private view returns (uint256 balInStart, uint256 balOutStart) { + balInStart = LibAsset.isNativeAsset(tokenIn) ? 0 : IERC20(tokenIn).balanceOf(msg.sender); - uint256 balanceOutInitial = LibAsset.isNativeAsset(tokenOut) + + balOutStart = LibAsset.isNativeAsset(tokenOut) ? address(to).balance : IERC20(tokenOut).balanceOf(to); + } + + /// @notice Interpret the `route` byte stream and perform all commanded actions. + /// @return realAmountIn The actual first-hop amount determined by the route. + function _runRoute( + address tokenIn, + uint256 declaredAmountIn, + bytes calldata route + ) private returns (uint256 realAmountIn) { + realAmountIn = declaredAmountIn; + uint256 step = 0; - uint256 realAmountIn = amountIn; - { - uint256 step = 0; - uint256 stream = LibInputStream2.createStream(route); - while (stream.isNotEmpty()) { - uint8 commandCode = stream.readUint8(); - if (commandCode == 1) { - uint256 usedAmount = _processMyERC20(stream); - if (step == 0) realAmountIn = usedAmount; - } else if (commandCode == 2) { - _processUserERC20(stream, amountIn); - } else if (commandCode == 3) { - uint256 usedAmount = _processNative(stream); - if (step == 0) realAmountIn = usedAmount; - } else if (commandCode == 4) { - _processOnePool(stream); - } else if (commandCode == 5) { - _applyPermit(tokenIn, stream); - } else { - revert UnknownCommandCode(); - } + uint256 cur = LibPackedStream.createStream(route); + while (cur.isNotEmpty()) { + uint8 opcode = cur.readUint8(); + if (opcode == 1) { + uint256 used = _handleSelfERC20(cur); + if (step == 0) realAmountIn = used; + } else if (opcode == 2) { + _handleUserERC20(cur, declaredAmountIn); + } else if (opcode == 3) { + uint256 usedNative = _handleNative(cur); + if (step == 0) realAmountIn = usedNative; + } else if (opcode == 4) { + _handleSinglePool(cur); + } else if (opcode == 5) { + _applyPermit(tokenIn, cur); + } else { + revert UnknownCommandCode(); + } + unchecked { ++step; } } + } - uint256 balanceInFinal = LibAsset.isNativeAsset(tokenIn) + /// @notice Validate post-conditions and determine `amountOut`. + function _postcheck( + address tokenIn, + address tokenOut, + address to, + uint256 declaredAmountIn, + uint256 minAmountOut, + uint256 balInStart, + uint256 balOutStart + ) private view returns (uint256 amountOut) { + uint256 balInFinal = LibAsset.isNativeAsset(tokenIn) ? 0 : IERC20(tokenIn).balanceOf(msg.sender); - if (balanceInFinal + amountIn < balanceInInitial) { + if (balInFinal + declaredAmountIn < balInStart) { revert MinimalInputBalanceViolation( - balanceInFinal + amountIn, - balanceInInitial + balInFinal + declaredAmountIn, + balInStart ); } - uint256 balanceOutFinal = LibAsset.isNativeAsset(tokenOut) + uint256 balOutFinal = LibAsset.isNativeAsset(tokenOut) ? address(to).balance : IERC20(tokenOut).balanceOf(to); - if (balanceOutFinal < balanceOutInitial + amountOutMin) { - revert MinimalOutputBalanceViolation( - balanceOutFinal - balanceOutInitial - ); + if (balOutFinal < balOutStart + minAmountOut) { + revert MinimalOutputBalanceViolation(balOutFinal - balOutStart); } - amountOut = balanceOutFinal - balanceOutInitial; - - emit Route( - msg.sender, - to, - tokenIn, - tokenOut, - realAmountIn, - amountOutMin, - amountOut - ); + amountOut = balOutFinal - balOutStart; } - /// @notice Applies ERC-2612 permit - function _applyPermit(address tokenIn, uint256 stream) private { - uint256 value = stream.readUint(); - uint256 deadline = stream.readUint(); - uint8 v = stream.readUint8(); - bytes32 r = stream.readBytes32(); - bytes32 s = stream.readBytes32(); + /// ===== Command handlers (renamed/reorganized) ===== + + /// @notice ERC-2612 permit application for `tokenIn`. + function _applyPermit(address tokenIn, uint256 cur) private { + uint256 value = cur.readUint256(); + uint256 deadline = cur.readUint256(); + uint8 v = cur.readUint8(); + bytes32 r = cur.readBytes32(); + bytes32 s = cur.readBytes32(); IERC20Permit(tokenIn).safePermit( msg.sender, address(this), @@ -160,99 +195,100 @@ contract CoreRouteFacet is ReentrancyGuard { ); } - /// @notice Processes native coin - function _processNative( - uint256 stream - ) private returns (uint256 amountTotal) { - amountTotal = address(this).balance; - _distributeAndSwap( - stream, - address(this), - INTERNAL_INPUT_SOURCE, - amountTotal - ); + /// @notice Handle native coin inputs (assumes value already present on this contract). + function _handleNative(uint256 cur) private returns (uint256 total) { + total = address(this).balance; + _distributeAndSwap(cur, address(this), INTERNAL_INPUT_SOURCE, total); } - /// @notice Processes ERC20 token from this contract balance - function _processMyERC20( - uint256 stream - ) private returns (uint256 amountTotal) { - address token = stream.readAddress(); - amountTotal = IERC20(token).balanceOf(address(this)); + /// @notice Pull ERC20 from this contract’s balance and process it. + function _handleSelfERC20(uint256 cur) private returns (uint256 total) { + address token = cur.readAddress(); + total = IERC20(token).balanceOf(address(this)); unchecked { - if (amountTotal > 0) amountTotal -= 1; // slot undrain protection + if (total > 0) total -= 1; // slot undrain protection } - _distributeAndSwap(stream, address(this), token, amountTotal); + _distributeAndSwap(cur, address(this), token, total); } - /// @notice Processes ERC20 token from msg.sender balance - function _processUserERC20(uint256 stream, uint256 amountTotal) private { - address token = stream.readAddress(); - _distributeAndSwap(stream, msg.sender, token, amountTotal); + /// @notice Pull ERC20 from the caller and process it. + function _handleUserERC20(uint256 cur, uint256 total) private { + address token = cur.readAddress(); + _distributeAndSwap(cur, msg.sender, token, total); } - /// @notice Processes single pool (tokens already at pool) - function _processOnePool(uint256 stream) private { - address token = stream.readAddress(); - _dispatchSwap(stream, INTERNAL_INPUT_SOURCE, token, 0); + /// @notice Process a “single pool” hop where inputs are already resident in the pool. + function _handleSinglePool(uint256 cur) private { + address token = cur.readAddress(); + _dispatchSwap(cur, INTERNAL_INPUT_SOURCE, token, 0); } - /// @notice Distributes amount to pools and calls swap for each + /// @notice Split an amount across N pools and trigger swaps. function _distributeAndSwap( - uint256 stream, + uint256 cur, address from, address tokenIn, - uint256 amountTotal + uint256 total ) private { - uint8 num = stream.readUint8(); + uint8 n = cur.readUint8(); unchecked { - for (uint256 i = 0; i < num; ++i) { - uint16 share = stream.readUint16(); - uint256 amount = (amountTotal * share) / type(uint16).max; - amountTotal -= amount; - _dispatchSwap(stream, from, tokenIn, amount); + for (uint256 i = 0; i < n; ++i) { + uint16 share = cur.readUint16(); + uint256 amt = (total * share) / type(uint16).max; + total -= amt; + _dispatchSwap(cur, from, tokenIn, amt); } } } - /// @notice Dispatches swap using selector-based approach - /// @dev This is the core of the selector-based dispatch system + /// @notice Extract selector and payload and delegate the call to the facet that implements it. function _dispatchSwap( - uint256 stream, + uint256 cur, address from, address tokenIn, uint256 amountIn ) private { - // Read the length-prefixed data blob for this specific swap step. - // This data blob contains the target function selector and its arguments. - bytes memory swapDataForCurrentHop = stream.readBytesWithLength(); - - bytes4 selector; - // We need to slice the data to separate the selector from the rest of the payload - bytes memory payload; + bytes memory data = cur.readBytesWithLength(); - assembly { - // Read the selector from the first 4 bytes of the data - selector := mload(add(swapDataForCurrentHop, 32)) - - // Create a new memory slice for the payload that starts 4 bytes after the - // beginning of swapDataForCurrentHop's content. - // It points to the same underlying data, just with a different start and length. - payload := add(swapDataForCurrentHop, 4) - // Adjust the length of the new slice - mstore(payload, sub(mload(swapDataForCurrentHop), 4)) - } + bytes4 selector = _readSelector(data); + bytes memory payload = _payloadFrom(data); address facet = LibDiamondLoupe.facetAddress(selector); if (facet == address(0)) revert UnknownSelector(); - (bool success, bytes memory returnData) = facet.delegatecall( + (bool ok, bytes memory ret) = facet.delegatecall( abi.encodeWithSelector(selector, payload, from, tokenIn, amountIn) ); + if (!ok) { + LibUtil.revertWith(ret); + } + } - if (!success) { - // If the call failed, revert with the EXACT error from the facet. - LibUtil.revertWith(returnData); + /// ===== Helpers ===== + + /// @dev Extracts the first 4 bytes as a selector. + function _readSelector( + bytes memory blob + ) private pure returns (bytes4 sel) { + assembly { + sel := mload(add(blob, 32)) + } + } + + /// @dev Returns a fresh bytes containing the original blob without the first 4 bytes. + function _payloadFrom( + bytes memory blob + ) private view returns (bytes memory out) { + uint256 len = blob.length; + if (len <= 4) return new bytes(0); + + uint256 newLen = len - 4; + assembly { + out := mload(0x40) + mstore(0x40, add(out, add(newLen, 32))) + mstore(out, newLen) + let src := add(blob, 36) // skip length(32) + 4 + pop(staticcall(gas(), 4, src, newLen, add(out, 32), newLen)) } } } diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 0d59c3a92..8c7450bf3 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; -import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; @@ -12,7 +12,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @notice Handles IzumiV3 swaps with callback management /// @custom:version 1.0.0 contract IzumiV3Facet { - using LibInputStream2 for uint256; + using LibPackedStream for uint256; using LibCallbackManager for *; using SafeERC20 for IERC20; @@ -37,7 +37,7 @@ contract IzumiV3Facet { address tokenIn, uint256 amountIn ) external returns (uint256) { - uint256 stream = LibInputStream2.createStream(swapData); + uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); uint8 direction = stream.readUint8(); // 0 = Y2X, 1 = X2Y address recipient = stream.readAddress(); diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index 30402d255..d88ba9344 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { ISyncSwapPool } from "lifi/Interfaces/ISyncSwapPool.sol"; import { ISyncSwapVault } from "lifi/Interfaces/ISyncSwapVault.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; @@ -12,7 +12,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @notice Handles SyncSwap swaps with callback management /// @custom:version 1.0.0 contract SyncSwapV2Facet { - using LibInputStream2 for uint256; + using LibPackedStream for uint256; using SafeERC20 for IERC20; /// @notice Performs a swap through SyncSwapV2 pools @@ -28,7 +28,7 @@ contract SyncSwapV2Facet { address tokenIn, uint256 amountIn ) external returns (uint256) { - uint256 stream = LibInputStream2.createStream(swapData); + uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); address to = stream.readAddress(); diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index bdba7c279..f859fb56d 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; -import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; interface IUniV3StylePool { @@ -24,7 +24,7 @@ interface IUniV3StylePool { contract UniV3StyleFacet { using SafeERC20 for IERC20; using LibCallbackManager for *; - using LibInputStream2 for uint256; + using LibPackedStream for uint256; /// Constants /// uint160 internal constant MIN_SQRT_RATIO = 4295128739; @@ -52,7 +52,7 @@ contract UniV3StyleFacet { address tokenIn, uint256 amountIn ) external { - uint256 stream = LibInputStream2.createStream(swapData); + uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); bool direction = stream.readUint8() > 0; address recipient = stream.readAddress(); diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index a76f63a53..92bec7ca3 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibInputStream2 } from "lifi/Libraries/LibInputStream2.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -12,7 +12,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @notice Handles VelodromeV2 swaps with callback management /// @custom:version 1.0.0 contract VelodromeV2Facet { - using LibInputStream2 for uint256; + using LibPackedStream for uint256; using SafeERC20 for IERC20; uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; @@ -33,7 +33,7 @@ contract VelodromeV2Facet { address tokenIn, uint256 amountIn ) external returns (uint256) { - uint256 stream = LibInputStream2.createStream(swapData); + uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); uint8 direction = stream.readUint8(); address to = stream.readAddress(); diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 97f2010b7..4dbe7f250 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -1,63 +1,32 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; -import { TestHelpers } from "../../utils/TestHelpers.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { CoreRouteTestBase } from "./CoreRouteFacet.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; /** * @title BaseDexFacetTest * @notice Base test contract with common functionality and abstractions for DEX-specific tests */ -abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { +abstract contract BaseDexFacetTest is CoreRouteTestBase { using SafeERC20 for IERC20; struct ForkConfig { - string networkName; // e.g. "taiko" (not "ETH_NODE_URI_TAIKO") + string networkName; uint256 blockNumber; } - // Command codes for route processing - enum CommandType { - None, // 0 - not used - ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) - ProcessUserERC20, // 2 - processUserERC20 (User's funds) - ProcessNative, // 3 - processNative - ProcessOnePool, // 4 - processOnePool (Pool's funds) - ApplyPermit // 6 - applyPermit - } - // Direction constants enum SwapDirection { Token1ToToken0, // 0 Token0ToToken1 // 1 } - // Callback constants - enum CallbackStatus { - Disabled, // 0 - Enabled // 1 - } - - CoreRouteFacet internal coreRouteFacet; ForkConfig internal forkConfig; - // Other constants - uint16 internal constant FULL_SHARE = 65535; // 100% share for single pool swaps - // Common events and errors - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); event HookCalled( address sender, uint256 amount0, @@ -73,20 +42,6 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { error NoHopsProvided(); error InvalidForkConfig(string reason); error UnknownNetwork(string name); - error InvalidTopicLength(); - error TooManyIndexedParams(); - error DynamicParamsNotSupported(); - - // Add this struct to hold event expectations - struct ExpectedEvent { - bool checkTopic1; - bool checkTopic2; - bool checkTopic3; - bool checkData; - bytes32 eventSelector; // The event selector (keccak256 hash of the event signature) - bytes[] eventParams; // The event parameters, each encoded separately - uint8[] indexedParamIndices; // indices of params that are indexed (→ topics 1..3) - } // At top-level state IERC20 internal tokenIn; @@ -180,25 +135,11 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { customBlockNumberForForking = forkConfig.blockNumber; fork(); - LdaDiamondTest.setUp(); - _addCoreRouteFacet(); + CoreRouteTestBase.setUp(); _setupDexEnv(); // populate tokens/pools _addDexFacet(); } - function _addCoreRouteFacet() internal { - coreRouteFacet = new CoreRouteFacet(); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = CoreRouteFacet.processRoute.selector; - addFacet( - address(ldaDiamond), - address(coreRouteFacet), - functionSelectors - ); - - coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); - } - function _setupForkConfig() internal virtual; // ============================ Abstract DEX Tests ============================ @@ -232,16 +173,6 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { revert("test_CanSwap_MultiHop: Not implemented"); } - struct SwapTestParams { - address tokenIn; - address tokenOut; - uint256 amountIn; - uint256 minOut; // Added here - it's a swap parameter - address sender; - address recipient; - CommandType commandType; - } - // Add this struct for route building struct RouteParams { CommandType commandType; @@ -251,32 +182,6 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { bytes swapData; } - // Helper to build common route parts - function _buildBaseRoute( - SwapTestParams memory params, - bytes memory swapData - ) internal pure returns (bytes memory) { - if (params.commandType == CommandType.ProcessOnePool) { - return - abi.encodePacked( - uint8(params.commandType), - params.tokenIn, - uint16(swapData.length), - swapData - ); - } else { - return - abi.encodePacked( - uint8(params.commandType), - params.tokenIn, - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), - swapData - ); - } - } - // Helper for building multi-hop route function _buildMultiHopRoute( SwapTestParams[] memory hopParams, @@ -296,257 +201,7 @@ abstract contract BaseDexFacetTest is LdaDiamondTest, TestHelpers { return route; } - struct RouteEventVerification { - uint256 expectedExactOut; // Only for event verification - bool checkData; - } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - ExpectedEvent[] memory additionalEvents, - bool isFeeOnTransferToken, - RouteEventVerification memory routeEventVerification - ) internal { - if (params.commandType != CommandType.ProcessMyERC20) { - IERC20(params.tokenIn).approve( - address(ldaDiamond), - params.amountIn - ); - } - - uint256 inBefore; - uint256 outBefore = IERC20(params.tokenOut).balanceOf( - params.recipient - ); - - // For aggregator funds, check the diamond's balance - if (params.commandType == CommandType.ProcessMyERC20) { - inBefore = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); - } else { - inBefore = IERC20(params.tokenIn).balanceOf(params.sender); - } - - address fromAddress = params.sender == address(ldaDiamond) - ? USER_SENDER - : params.sender; - - _expectEvents(additionalEvents); - - vm.expectEmit(true, true, true, routeEventVerification.checkData); - emit Route( - fromAddress, - params.recipient, - params.tokenIn, - params.tokenOut, - params.amountIn, - params.minOut, // Use minOut from SwapTestParams - routeEventVerification.expectedExactOut - ); - - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - params.minOut, // Use minOut from SwapTestParams - params.recipient, - route - ); - - uint256 inAfter; - uint256 outAfter = IERC20(params.tokenOut).balanceOf(params.recipient); - - // Check balance change on the correct address - if (params.commandType == CommandType.ProcessMyERC20) { - inAfter = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); - } else { - inAfter = IERC20(params.tokenIn).balanceOf(params.sender); - } - - // Use assertEq or assertApproxEqAbs based on isFeeOnTransferToken - if (isFeeOnTransferToken) { - assertApproxEqAbs( - inBefore - inAfter, - params.amountIn, - 1, // Allow 1 wei difference for fee-on-transfer tokens - "Token spent mismatch" - ); - } else { - assertEq( - inBefore - inAfter, - params.amountIn, - "Token spent mismatch" - ); - } - - assertGt(outAfter - outBefore, 0, "Should receive tokens"); - } - function _getDefaultAmountForTokenIn() internal virtual returns (uint256) { return 1_000 * 1e18; } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - ExpectedEvent[] memory additionalEvents, - bool isFeeOnTransferToken - ) internal { - _executeAndVerifySwap( - params, - route, - additionalEvents, - isFeeOnTransferToken, - RouteEventVerification({ expectedExactOut: 0, checkData: false }) - ); - } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route - ) internal { - _executeAndVerifySwap( - params, - route, - new ExpectedEvent[](0), - false, - RouteEventVerification({ expectedExactOut: 0, checkData: false }) - ); - } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - bool isFeeOnTransferToken - ) internal { - _executeAndVerifySwap( - params, - route, - new ExpectedEvent[](0), - isFeeOnTransferToken, - RouteEventVerification({ expectedExactOut: 0, checkData: false }) - ); - } - - // Keep the revert case separate - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - bytes4 expectedRevert - ) internal { - if (params.commandType != CommandType.ProcessMyERC20) { - IERC20(params.tokenIn).approve( - address(ldaDiamond), - params.amountIn - ); - } - - vm.expectRevert(expectedRevert); - coreRouteFacet.processRoute( - params.tokenIn, - params.commandType == CommandType.ProcessMyERC20 - ? params.amountIn - : params.amountIn - 1, - params.tokenOut, - 0, // minOut = 0 for tests - params.recipient, - route - ); - } - - // helper: load a 32-byte topic from a 32-byte abi.encode(param) - function _asTopic(bytes memory enc) internal pure returns (bytes32 topic) { - if (enc.length != 32) revert InvalidTopicLength(); - assembly { - topic := mload(add(enc, 32)) - } - } - - /** - * @notice Sets up event expectations for a list of events - * @param events Array of events to expect - */ - function _expectEvents(ExpectedEvent[] memory events) internal { - for (uint256 i = 0; i < events.length; i++) { - _expectEvent(events[i]); - } - } - - /** - * @notice Sets up expectation for a single event - * @param evt The event to expect with its check parameters and data - */ - function _expectEvent(ExpectedEvent memory evt) internal { - vm.expectEmit( - evt.checkTopic1, - evt.checkTopic2, - evt.checkTopic3, - evt.checkData - ); - - // Build topics (topic0 = selector; topics1..3 from indexedParamIndices) - bytes32 topic0 = evt.eventSelector; - uint8[] memory idx = evt.indexedParamIndices; - - bytes32 t1; - bytes32 t2; - bytes32 t3; - - uint256 topicsCount = idx.length; - if (topicsCount > 3) { - revert TooManyIndexedParams(); - } - if (topicsCount >= 1) { - t1 = _asTopic(evt.eventParams[idx[0]]); - } - if (topicsCount >= 2) { - t2 = _asTopic(evt.eventParams[idx[1]]); - } - if (topicsCount == 3) { - t3 = _asTopic(evt.eventParams[idx[2]]); - } - - // Build data (non-indexed params in event order) - bytes memory data; - if (evt.checkData) { - // Only support static params for now (each abi.encode(param) must be 32 bytes) - uint256 total = evt.eventParams.length; - bool[8] memory isIndexed; // up to 8 params; expand if needed - for (uint256 k = 0; k < topicsCount; k++) { - uint8 pos = idx[k]; - if (pos < isIndexed.length) isIndexed[pos] = true; - } - - for (uint256 p = 0; p < total; p++) { - if (!isIndexed[p]) { - bytes memory enc = evt.eventParams[p]; - if (enc.length != 32) { - revert DynamicParamsNotSupported(); - } - data = bytes.concat(data, enc); - } - } - } else { - data = ""; - } - - // Emit raw log with correct number of topics - assembly { - let ptr := add(data, 0x20) - let len := mload(data) - switch topicsCount - case 0 { - log1(ptr, len, topic0) - } - case 1 { - log2(ptr, len, topic0, t1) - } - case 2 { - log3(ptr, len, topic0, t1, t2) - } - case 3 { - log4(ptr, len, topic0, t1, t2, t3) - } - } - } } diff --git a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol index a03ed71f5..f7340ca4d 100644 --- a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; -import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { TestHelpers } from "../../utils/TestHelpers.sol"; +import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; contract MockNativeFacet { using SafeTransferLib for address; @@ -44,6 +45,346 @@ contract MockPullERC20Facet { } } +abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { + using SafeERC20 for IERC20; + + // Command codes for route processing + enum CommandType { + None, // 0 - not used + ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) + ProcessUserERC20, // 2 - processUserERC20 (User's funds) + ProcessNative, // 3 - processNative + ProcessOnePool, // 4 - processOnePool (Pool's funds) + ApplyPermit // 6 - applyPermit + } + + uint16 internal constant FULL_SHARE = 65535; + + CoreRouteFacet internal coreRouteFacet; + + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + + struct ExpectedEvent { + bool checkTopic1; + bool checkTopic2; + bool checkTopic3; + bool checkData; + bytes32 eventSelector; // The event selector (keccak256 hash of the event signature) + bytes[] eventParams; // The event parameters, each encoded separately + uint8[] indexedParamIndices; // indices of params that are indexed (→ topics 1..3) + } + + struct RouteEventVerification { + uint256 expectedExactOut; // Only for event verification + bool checkData; + } + + struct SwapTestParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint256 minOut; + address sender; + address recipient; + CommandType commandType; + } + + error InvalidTopicLength(); + error TooManyIndexedParams(); + error DynamicParamsNotSupported(); + + function setUp() public virtual override { + LdaDiamondTest.setUp(); + _addCoreRouteFacet(); + } + + function _addCoreRouteFacet() internal { + coreRouteFacet = new CoreRouteFacet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = CoreRouteFacet.processRoute.selector; + addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); + coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); + } + + function _buildBaseRoute( + SwapTestParams memory params, + bytes memory swapData + ) internal pure returns (bytes memory) { + if (params.commandType == CommandType.ProcessOnePool) { + return + abi.encodePacked( + uint8(params.commandType), + params.tokenIn, + uint16(swapData.length), + swapData + ); + } else { + return + abi.encodePacked( + uint8(params.commandType), + params.tokenIn, + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ); + } + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + ExpectedEvent[] memory additionalEvents, + bool isFeeOnTransferToken, + RouteEventVerification memory routeEventVerification + ) internal { + if (params.commandType != CommandType.ProcessMyERC20) { + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + } + + uint256 inBefore; + uint256 outBefore = IERC20(params.tokenOut).balanceOf( + params.recipient + ); + + // For aggregator funds, check the diamond's balance + if (params.commandType == CommandType.ProcessMyERC20) { + inBefore = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + } else { + inBefore = IERC20(params.tokenIn).balanceOf(params.sender); + } + + address fromAddress = params.sender == address(ldaDiamond) + ? USER_SENDER + : params.sender; + + _expectEvents(additionalEvents); + + vm.expectEmit(true, true, true, routeEventVerification.checkData); + emit Route( + fromAddress, + params.recipient, + params.tokenIn, + params.tokenOut, + params.amountIn, + params.minOut, // Use minOut from SwapTestParams + routeEventVerification.expectedExactOut + ); + + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + params.minOut, // Use minOut from SwapTestParams + params.recipient, + route + ); + + uint256 inAfter; + uint256 outAfter = IERC20(params.tokenOut).balanceOf(params.recipient); + + // Check balance change on the correct address + if (params.commandType == CommandType.ProcessMyERC20) { + inAfter = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + } else { + inAfter = IERC20(params.tokenIn).balanceOf(params.sender); + } + + // Use assertEq or assertApproxEqAbs based on isFeeOnTransferToken + if (isFeeOnTransferToken) { + assertApproxEqAbs( + inBefore - inAfter, + params.amountIn, + 1, // Allow 1 wei difference for fee-on-transfer tokens + "Token spent mismatch" + ); + } else { + assertEq( + inBefore - inAfter, + params.amountIn, + "Token spent mismatch" + ); + } + + assertGt(outAfter - outBefore, 0, "Should receive tokens"); + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + ExpectedEvent[] memory additionalEvents, + bool isFeeOnTransferToken + ) internal { + _executeAndVerifySwap( + params, + route, + additionalEvents, + isFeeOnTransferToken, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route + ) internal { + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + false, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + bool isFeeOnTransferToken + ) internal { + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + isFeeOnTransferToken, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + + // Keep the revert case separate + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + bytes4 expectedRevert + ) internal { + if (params.commandType != CommandType.ProcessMyERC20) { + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + } + + vm.expectRevert(expectedRevert); + coreRouteFacet.processRoute( + params.tokenIn, + params.commandType == CommandType.ProcessMyERC20 + ? params.amountIn + : params.amountIn - 1, + params.tokenOut, + 0, // minOut = 0 for tests + params.recipient, + route + ); + } + + // helper: load a 32-byte topic from a 32-byte abi.encode(param) + function _asTopic(bytes memory enc) internal pure returns (bytes32 topic) { + if (enc.length != 32) revert InvalidTopicLength(); + assembly { + topic := mload(add(enc, 32)) + } + } + + /** + * @notice Sets up event expectations for a list of events + * @param events Array of events to expect + */ + function _expectEvents(ExpectedEvent[] memory events) internal { + for (uint256 i = 0; i < events.length; i++) { + _expectEvent(events[i]); + } + } + + /** + * @notice Sets up expectation for a single event + * @param evt The event to expect with its check parameters and data + */ + function _expectEvent(ExpectedEvent memory evt) internal { + vm.expectEmit( + evt.checkTopic1, + evt.checkTopic2, + evt.checkTopic3, + evt.checkData + ); + + // Build topics (topic0 = selector; topics1..3 from indexedParamIndices) + bytes32 topic0 = evt.eventSelector; + uint8[] memory idx = evt.indexedParamIndices; + + bytes32 t1; + bytes32 t2; + bytes32 t3; + + uint256 topicsCount = idx.length; + if (topicsCount > 3) { + revert TooManyIndexedParams(); + } + if (topicsCount >= 1) { + t1 = _asTopic(evt.eventParams[idx[0]]); + } + if (topicsCount >= 2) { + t2 = _asTopic(evt.eventParams[idx[1]]); + } + if (topicsCount == 3) { + t3 = _asTopic(evt.eventParams[idx[2]]); + } + + // Build data (non-indexed params in event order) + bytes memory data; + if (evt.checkData) { + // Only support static params for now (each abi.encode(param) must be 32 bytes) + uint256 total = evt.eventParams.length; + bool[8] memory isIndexed; // up to 8 params; expand if needed + for (uint256 k = 0; k < topicsCount; k++) { + uint8 pos = idx[k]; + if (pos < isIndexed.length) isIndexed[pos] = true; + } + + for (uint256 p = 0; p < total; p++) { + if (!isIndexed[p]) { + bytes memory enc = evt.eventParams[p]; + if (enc.length != 32) { + revert DynamicParamsNotSupported(); + } + data = bytes.concat(data, enc); + } + } + } else { + data = ""; + } + + // Emit raw log with correct number of topics + assembly { + let ptr := add(data, 0x20) + let len := mload(data) + switch topicsCount + case 0 { + log1(ptr, len, topic0) + } + case 1 { + log2(ptr, len, topic0, t1) + } + case 2 { + log3(ptr, len, topic0, t1, t2) + } + case 3 { + log4(ptr, len, topic0, t1, t2, t3) + } + } + } +} + contract CoreRouteFacetTest is LdaDiamondTest { using SafeTransferLib for address; diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 13dd9ad87..d99ceedd3 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -40,6 +40,12 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { MockVelodromeV2FlashLoanCallbackReceiver internal mockFlashloanCallbackReceiver; + // Callback constants + enum CallbackStatus { + Disabled, // 0 + Enabled // 1 + } + // Velodrome V2 structs struct VelodromeV2SwapTestParams { address from; diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol index 526954979..0a2745917 100644 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol @@ -1,3946 +1,3946 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; -import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; -import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; -import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; -import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; -import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; - -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; -import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; -import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; -import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; - -import { TestToken as ERC20 } from "../../utils/TestToken.sol"; -import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; -import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; -import { TestHelpers } from "../../utils/TestHelpers.sol"; - -// Command codes for route processing -enum CommandType { - None, // 0 - not used - ProcessMyERC20, // 1 - processMyERC20 - ProcessUserERC20, // 2 - processUserERC20 - ProcessNative, // 3 - processNative - ProcessOnePool, // 4 - processOnePool - ProcessInsideBento, // 5 - processInsideBento - ApplyPermit // 6 - applyPermit -} -// Direction constants -enum SwapDirection { - Token1ToToken0, // 0 - Token0ToToken1 // 1 -} - -// Callback constants -enum CallbackStatus { - Disabled, // 0 - Enabled // 1 -} - -// Other constants -uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps - -contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - function hook( - address sender, - uint256 amount0, - uint256 amount1, - bytes calldata data - ) external { - emit HookCalled(sender, amount0, amount1, data); - } -} -/** - * @title LiFiDexAggregatorUpgradeTest - * @notice Base test contract with common functionality and abstractions for DEX-specific tests - */ -abstract contract LiFiDexAggregatorUpgradeTest is LdaDiamondTest, TestHelpers { - using SafeERC20 for IERC20; - - CoreRouteFacet internal coreRouteFacet; - - // Common events and errors - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - error WrongPoolReserves(); - error PoolDoesNotExist(); - - function _addDexFacet() internal virtual; - - // Setup function for Apechain tests - function setupApechain() internal { - customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; - customBlockNumberForForking = 12912470; - } - - function setupHyperEVM() internal { - customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; - customBlockNumberForForking = 4433562; - } - - function setupXDC() internal { - customRpcUrlForForking = "ETH_NODE_URI_XDC"; - customBlockNumberForForking = 89279495; - } - - function setupViction() internal { - customRpcUrlForForking = "ETH_NODE_URI_VICTION"; - customBlockNumberForForking = 94490946; - } - - function setupFlare() internal { - customRpcUrlForForking = "ETH_NODE_URI_FLARE"; - customBlockNumberForForking = 42652369; - } - - function setupLinea() internal { - customRpcUrlForForking = "ETH_NODE_URI_LINEA"; - customBlockNumberForForking = 20077881; - } - - function setUp() public virtual override { - fork(); - LdaDiamondTest.setUp(); - _addCoreRouteFacet(); - _addDexFacet(); - } - - function _addCoreRouteFacet() internal { - coreRouteFacet = new CoreRouteFacet(); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = CoreRouteFacet.processRoute.selector; - addFacet( - address(ldaDiamond), - address(coreRouteFacet), - functionSelectors - ); - - coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); - } - - // function test_ContractIsSetUpCorrectly() public { - // assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); - // assertEq( - // liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), - // true - // ); - // assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); - // } - - // function testRevert_FailsIfOwnerIsZeroAddress() public { - // vm.expectRevert(InvalidConfig.selector); - - // liFiDEXAggregator = new LiFiDEXAggregator( - // address(0xCAFE), - // privileged, - // address(0) - // ); - // } - - // ============================ Abstract DEX Tests ============================ - /** - * @notice Abstract test for basic token swapping functionality - * Each DEX implementation should override this - */ - function test_CanSwap() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap: Not implemented"); - } - - /** - * @notice Abstract test for swapping tokens from the DEX aggregator - * Each DEX implementation should override this - */ - function test_CanSwap_FromDexAggregator() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_FromDexAggregator: Not implemented"); - } - - /** - * @notice Abstract test for multi-hop swapping - * Each DEX implementation should override this - */ - function test_CanSwap_MultiHop() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_MultiHop: Not implemented"); - } -} - -/** - * @title VelodromeV2 tests - * @notice Tests specific to Velodrome V2 - */ -contract LiFiDexAggregatorVelodromeV2UpgradeTest is - LiFiDexAggregatorUpgradeTest -{ - VelodromeV2Facet internal velodromeV2Facet; - - // ==================== Velodrome V2 specific variables ==================== - IVelodromeV2Router internal constant VELODROME_V2_ROUTER = - IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router - address internal constant VELODROME_V2_FACTORY_REGISTRY = - 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; - IERC20 internal constant USDC_TOKEN = - IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); - IERC20 internal constant STG_TOKEN = - IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); - IERC20 internal constant USDC_E_TOKEN = - IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); - - MockVelodromeV2FlashLoanCallbackReceiver - internal mockFlashloanCallbackReceiver; - - // Velodrome V2 structs - struct VelodromeV2SwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; - bool stable; - SwapDirection direction; - bool callback; - } - - struct MultiHopTestParams { - address tokenIn; - address tokenMid; - address tokenOut; - address pool1; - address pool2; - uint256[] amounts1; - uint256[] amounts2; - uint256 pool1Fee; - uint256 pool2Fee; - } - - struct ReserveState { - uint256 reserve0Pool1; - uint256 reserve1Pool1; - uint256 reserve0Pool2; - uint256 reserve1Pool2; - } - - // Setup function for Optimism tests - function setupOptimism() internal { - customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; - customBlockNumberForForking = 133999121; - } - - function setUp() public override { - setupOptimism(); - super.setUp(); - - deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); - } - - function _addDexFacet() internal override { - velodromeV2Facet = new VelodromeV2Facet(); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; - addFacet( - address(ldaDiamond), - address(velodromeV2Facet), - functionSelectors - ); - - velodromeV2Facet = VelodromeV2Facet(payable(address(ldaDiamond))); - } - - // ============================ Velodrome V2 Tests ============================ - - // no stable swap - function test_CanSwap() public override { - vm.startPrank(USER_SENDER); - - _testSwap( - VelodromeV2SwapTestParams({ - from: address(USER_SENDER), - to: address(USER_SENDER), - tokenIn: address(USDC_TOKEN), - amountIn: 1_000 * 1e6, - tokenOut: address(STG_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callback: false - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_NoStable_Reverse() public { - // first perform the forward swap. - test_CanSwap(); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(STG_TOKEN), - amountIn: 500 * 1e18, - tokenOut: address(USDC_TOKEN), - stable: false, - direction: SwapDirection.Token1ToToken0, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_Stable() public { - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(USDC_TOKEN), - amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), - stable: true, - direction: SwapDirection.Token0ToToken1, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_Stable_Reverse() public { - // first perform the forward stable swap. - test_CanSwap_Stable(); - - vm.startPrank(USER_SENDER); - - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(USDC_E_TOKEN), - amountIn: 500 * 1e6, - tokenOut: address(USDC_TOKEN), - stable: false, - direction: SwapDirection.Token1ToToken0, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - // fund dex aggregator contract so that the contract holds USDC - deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: address(ldaDiamond), - to: address(USER_SENDER), - tokenIn: address(USDC_TOKEN), - amountIn: IERC20(address(USDC_TOKEN)).balanceOf( - address(ldaDiamond) - ) - 1, // adjust for slot undrain protection: subtract 1 token so that the - // aggregator's balance isn't completely drained, matching the contract's safeguard - tokenOut: address(USDC_E_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_FlashloanCallback() public { - mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: address(USER_SENDER), - to: address(mockFlashloanCallbackReceiver), - tokenIn: address(USDC_TOKEN), - amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callback: true - }) - ); - vm.stopPrank(); - } - - // Override the abstract test with VelodromeV2 implementation - function test_CanSwap_MultiHop() public override { - vm.startPrank(USER_SENDER); - - // Setup routes and get amounts - MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(STG_TOKEN), - address(USDC_E_TOKEN), - false, - false - ); - - // Get initial reserves BEFORE the swap - ReserveState memory initialReserves; - ( - initialReserves.reserve0Pool1, - initialReserves.reserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - initialReserves.reserve0Pool2, - initialReserves.reserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - - // Build route and execute swap - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); - - // Approve and execute - IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - params.tokenIn, - params.tokenOut, - 1000 * 1e6, - params.amounts2[1], - params.amounts2[1] - ); - - coreRouteFacet.processRoute( - params.tokenIn, - 1000 * 1e6, - params.tokenOut, - params.amounts2[1], - USER_SENDER, - route - ); - - _verifyUserBalances(params, initialBalance1, initialBalance2); - _verifyReserves(params, initialReserves); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop_WithStable() public { - vm.startPrank(USER_SENDER); - - // Setup routes and get amounts for stable->volatile path - MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(USDC_E_TOKEN), - address(STG_TOKEN), - true, // stable pool for first hop - false // volatile pool for second hop - ); - - // Get initial reserves BEFORE the swap - ReserveState memory initialReserves; - ( - initialReserves.reserve0Pool1, - initialReserves.reserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - initialReserves.reserve0Pool2, - initialReserves.reserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - // Record initial balances - uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - - // Build route and execute swap - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - - // Approve and execute - IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - - // vm.expectEmit(true, true, true, true); - // emit Route( - // USER_SENDER, - // USER_SENDER, - // params.tokenIn, - // params.tokenOut, - // 1000 * 1e6, - // params.amounts2[1], - // params.amounts2[1] - // ); - - coreRouteFacet.processRoute( - params.tokenIn, - 1000 * 1e6, - params.tokenOut, - params.amounts2[1], - USER_SENDER, - route - ); - - _verifyUserBalances(params, initialBalance1, initialBalance2); - _verifyReserves(params, initialReserves); - - vm.stopPrank(); - } - - function testRevert_InvalidPoolOrRecipient() public { - vm.startPrank(USER_SENDER); - - // Get a valid pool address first for comparison - address validPool = VELODROME_V2_ROUTER.poolFor( - address(USDC_TOKEN), - address(STG_TOKEN), - false, - VELODROME_V2_FACTORY_REGISTRY - ); - - // --- Test case 1: Zero pool address --- - // 1. Create the specific swap data blob - bytes memory swapDataZeroPool = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - address(0), // Invalid pool - uint8(SwapDirection.Token1ToToken0), - USER_SENDER, - uint8(CallbackStatus.Disabled) - ); - - // 2. Create the full route with the length-prefixed swap data - bytes memory routeWithZeroPool = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDC_TOKEN), - uint8(1), - FULL_SHARE, - uint16(swapDataZeroPool.length), // Length prefix - swapDataZeroPool - ); - - IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); - - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(USDC_TOKEN), - 1000 * 1e6, - address(STG_TOKEN), - 0, - USER_SENDER, - routeWithZeroPool - ); - - // --- Test case 2: Zero recipient address --- - bytes memory swapDataZeroRecipient = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - validPool, - uint8(SwapDirection.Token1ToToken0), - address(0), // Invalid recipient - uint8(CallbackStatus.Disabled) - ); - - bytes memory routeWithZeroRecipient = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDC_TOKEN), - uint8(1), - FULL_SHARE, - uint16(swapDataZeroRecipient.length), // Length prefix - swapDataZeroRecipient - ); - - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(USDC_TOKEN), - 1000 * 1e6, - address(STG_TOKEN), - 0, - USER_SENDER, - routeWithZeroRecipient - ); - - vm.stopPrank(); - } - - function testRevert_WrongPoolReserves() public { - vm.startPrank(USER_SENDER); - - // Setup multi-hop route: USDC -> STG -> USDC.e - MultiHopTestParams memory params = _setupRoutes( - address(USDC_TOKEN), - address(STG_TOKEN), - address(USDC_E_TOKEN), - false, - false - ); - - // Build multi-hop route - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - - deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); - - IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); - - // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves - vm.mockCall( - params.pool2, - abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), - abi.encode(0, 0, block.timestamp) - ); - - vm.expectRevert(WrongPoolReserves.selector); - - coreRouteFacet.processRoute( - address(USDC_TOKEN), - 1000 * 1e6, - address(USDC_E_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - // ============================ Velodrome V2 Helper Functions ============================ - - /** - * @dev Helper function to test a VelodromeV2 swap. - * Uses a struct to group parameters and reduce stack depth. - */ - function _testSwap(VelodromeV2SwapTestParams memory params) internal { - // get expected output amounts from the router. - IVelodromeV2Router.Route[] - memory routes = new IVelodromeV2Router.Route[](1); - routes[0] = IVelodromeV2Router.Route({ - from: params.tokenIn, - to: params.tokenOut, - stable: params.stable, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( - params.amountIn, - routes - ); - emit log_named_uint("Expected amount out", amounts[1]); - - // Retrieve the pool address. - address pool = VELODROME_V2_ROUTER.poolFor( - params.tokenIn, - params.tokenOut, - params.stable, - VELODROME_V2_FACTORY_REGISTRY - ); - emit log_named_uint("Pool address:", uint256(uint160(pool))); - - // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. - CommandType commandCode = params.from == address(ldaDiamond) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; - - // 1. Pack the data for the specific swap FIRST - bytes memory swapData = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - pool, - params.direction, - params.to, - params.callback - ? uint8(CallbackStatus.Enabled) - : uint8(CallbackStatus.Disabled) - ); - // build the route. - bytes memory route = abi.encodePacked( - uint8(commandCode), - params.tokenIn, - uint8(1), // num splits - FULL_SHARE, - uint16(swapData.length), // <--- Add length prefix - swapData - ); - - // approve the aggregator to spend tokenIn. - IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - - // capture initial token balances. - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - emit log_named_uint("Initial tokenIn balance", initialTokenIn); - - address from = params.from == address(ldaDiamond) - ? USER_SENDER - : params.from; - if (params.callback == true) { - vm.expectEmit(true, false, false, false); - emit HookCalled( - address(ldaDiamond), - 0, - 0, - abi.encode(params.tokenIn) - ); - } - vm.expectEmit(true, true, true, true); - emit Route( - from, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - amounts[1], - amounts[1] - ); - - // execute the swap - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - amounts[1], - params.to, - route - ); - - uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); - emit log_named_uint( - "TokenOut received", - finalTokenOut - initialTokenOut - ); - - assertApproxEqAbs( - initialTokenIn - finalTokenIn, - params.amountIn, - 1, // 1 wei tolerance - "TokenIn amount mismatch" - ); - assertEq( - finalTokenOut - initialTokenOut, - amounts[1], - "TokenOut amount mismatch" - ); - } - - // Helper function to set up routes and get amounts - function _setupRoutes( - address tokenIn, - address tokenMid, - address tokenOut, - bool isStableFirst, - bool isStableSecond - ) private view returns (MultiHopTestParams memory params) { - params.tokenIn = tokenIn; - params.tokenMid = tokenMid; - params.tokenOut = tokenOut; - - // Setup first hop route - IVelodromeV2Router.Route[] - memory routes1 = new IVelodromeV2Router.Route[](1); - routes1[0] = IVelodromeV2Router.Route({ - from: tokenIn, - to: tokenMid, - stable: isStableFirst, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( - 1000 * 1e6, - routes1 - ); - - // Setup second hop route - IVelodromeV2Router.Route[] - memory routes2 = new IVelodromeV2Router.Route[](1); - routes2[0] = IVelodromeV2Router.Route({ - from: tokenMid, - to: tokenOut, - stable: isStableSecond, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( - params.amounts1[1], - routes2 - ); - - // Get pool addresses - params.pool1 = VELODROME_V2_ROUTER.poolFor( - tokenIn, - tokenMid, - isStableFirst, - VELODROME_V2_FACTORY_REGISTRY - ); - - params.pool2 = VELODROME_V2_ROUTER.poolFor( - tokenMid, - tokenOut, - isStableSecond, - VELODROME_V2_FACTORY_REGISTRY - ); - - // Get pool fees info - params.pool1Fee = IVelodromeV2PoolFactory( - VELODROME_V2_FACTORY_REGISTRY - ).getFee(params.pool1, isStableFirst); - params.pool2Fee = IVelodromeV2PoolFactory( - VELODROME_V2_FACTORY_REGISTRY - ).getFee(params.pool2, isStableSecond); - - return params; - } - - // function to build first hop of the route - function _buildFirstHop( - address pool1, - address pool2, // The recipient of the first hop is the next pool - uint8 direction - ) private pure returns (bytes memory) { - return - abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - pool1, - direction, - pool2, // Send intermediate tokens to the next pool for the second hop - uint8(CallbackStatus.Disabled) - ); - } - - // function to build second hop of the route - function _buildSecondHop( - address pool2, - address recipient, - uint8 direction - ) private pure returns (bytes memory) { - return - abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - pool2, - direction, - recipient, // Final recipient - uint8(CallbackStatus.Disabled) - ); - } - - // route building function - function _buildMultiHopRoute( - MultiHopTestParams memory params, - address recipient, - uint8 firstHopDirection, - uint8 secondHopDirection - ) private pure returns (bytes memory) { - // 1. Get the specific data for each hop - bytes memory firstHopData = _buildFirstHop( - params.pool1, - params.pool2, - firstHopDirection - ); - - bytes memory secondHopData = _buildSecondHop( - params.pool2, - recipient, - secondHopDirection - ); - - // 2. Assemble the first command - bytes memory firstCommand = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - params.tokenIn, - uint8(1), // num splits - FULL_SHARE, - uint16(firstHopData.length), // <--- Add length prefix - firstHopData - ); - - // 3. Assemble the second command - // The second hop takes tokens already held by the diamond, so we use ProcessOnePool - bytes memory secondCommand = abi.encodePacked( - uint8(CommandType.ProcessOnePool), - params.tokenMid, - uint16(secondHopData.length), // <--- Add length prefix - secondHopData - ); - - // 4. Concatenate the commands to create the final route - return bytes.concat(firstCommand, secondCommand); - } - - function _verifyUserBalances( - MultiHopTestParams memory params, - uint256 initialBalance1, - uint256 initialBalance2 - ) private { - // Verify token balances - uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); - uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); - - assertApproxEqAbs( - initialBalance1 - finalBalance1, - 1000 * 1e6, - 1, // 1 wei tolerance - "Token1 spent amount mismatch" - ); - assertEq( - finalBalance2 - initialBalance2, - params.amounts2[1], - "Token2 received amount mismatch" - ); - } - - function _verifyReserves( - MultiHopTestParams memory params, - ReserveState memory initialReserves - ) private { - // Get reserves after swap - ( - uint256 finalReserve0Pool1, - uint256 finalReserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - uint256 finalReserve0Pool2, - uint256 finalReserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); - address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); - - // Calculate exact expected changes - uint256 amountInAfterFees = 1000 * - 1e6 - - ((1000 * 1e6 * params.pool1Fee) / 10000); - - // Assert exact reserve changes for Pool1 - if (token0Pool1 == params.tokenIn) { - // tokenIn is token0, so reserve0 should increase and reserve1 should decrease - assertEq( - finalReserve0Pool1 - initialReserves.reserve0Pool1, - amountInAfterFees, - "Pool1 reserve0 (tokenIn) change incorrect" - ); - assertEq( - initialReserves.reserve1Pool1 - finalReserve1Pool1, - params.amounts1[1], - "Pool1 reserve1 (tokenMid) change incorrect" - ); - } else { - // tokenIn is token1, so reserve1 should increase and reserve0 should decrease - assertEq( - finalReserve1Pool1 - initialReserves.reserve1Pool1, - amountInAfterFees, - "Pool1 reserve1 (tokenIn) change incorrect" - ); - assertEq( - initialReserves.reserve0Pool1 - finalReserve0Pool1, - params.amounts1[1], - "Pool1 reserve0 (tokenMid) change incorrect" - ); - } - - // Assert exact reserve changes for Pool2 - if (token0Pool2 == params.tokenMid) { - // tokenMid is token0, so reserve0 should increase and reserve1 should decrease - assertEq( - finalReserve0Pool2 - initialReserves.reserve0Pool2, - params.amounts1[1] - - ((params.amounts1[1] * params.pool2Fee) / 10000), - "Pool2 reserve0 (tokenMid) change incorrect" - ); - assertEq( - initialReserves.reserve1Pool2 - finalReserve1Pool2, - params.amounts2[1], - "Pool2 reserve1 (tokenOut) change incorrect" - ); - } else { - // tokenMid is token1, so reserve1 should increase and reserve0 should decrease - assertEq( - finalReserve1Pool2 - initialReserves.reserve1Pool2, - params.amounts1[1] - - ((params.amounts1[1] * params.pool2Fee) / 10000), - "Pool2 reserve1 (tokenMid) change incorrect" - ); - assertEq( - initialReserves.reserve0Pool2 - finalReserve0Pool2, - params.amounts2[1], - "Pool2 reserve0 (tokenOut) change incorrect" - ); - } - } -} - -contract AlgebraLiquidityAdderHelper { - address public immutable TOKEN_0; - address public immutable TOKEN_1; - - constructor(address _token0, address _token1) { - TOKEN_0 = _token0; - TOKEN_1 = _token1; - } - - function addLiquidity( - address pool, - int24 bottomTick, - int24 topTick, - uint128 amount - ) - external - returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) - { - // Get balances before - uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); - - // Call mint - (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( - address(this), - address(this), - bottomTick, - topTick, - amount, - abi.encode(TOKEN_0, TOKEN_1) - ); - - // Get balances after to account for fees - uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); - - // Calculate actual amounts transferred accounting for fees - amount0 = balance0Before - balance0After; - amount1 = balance1Before - balance1After; - - return (amount0, amount1, liquidityActual); - } - - function algebraMintCallback( - uint256 amount0Owed, - uint256 amount1Owed, - bytes calldata - ) external { - // Check token balances - uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); - - // Transfer what we can, limited by actual balance - if (amount0Owed > 0) { - uint256 amount0ToSend = amount0Owed > balance0 - ? balance0 - : amount0Owed; - uint256 balance0Before = IERC20(TOKEN_0).balanceOf( - address(msg.sender) - ); - IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); - uint256 balance0After = IERC20(TOKEN_0).balanceOf( - address(msg.sender) - ); - // solhint-disable-next-line gas-custom-errors - require(balance0After > balance0Before, "Transfer failed"); - } - - if (amount1Owed > 0) { - uint256 amount1ToSend = amount1Owed > balance1 - ? balance1 - : amount1Owed; - uint256 balance1Before = IERC20(TOKEN_1).balanceOf( - address(msg.sender) - ); - IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); - uint256 balance1After = IERC20(TOKEN_1).balanceOf( - address(msg.sender) - ); - // solhint-disable-next-line gas-custom-errors - require(balance1After > balance1Before, "Transfer failed"); - } - } -} - -/** - * @title Algebra tests - * @notice Tests specific to Algebra - */ -contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { - AlgebraFacet private algebraFacet; - - address private constant APE_ETH_TOKEN = - 0xcF800F4948D16F23333508191B1B1591daF70438; - address private constant WETH_TOKEN = - 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; - address private constant ALGEBRA_FACTORY_APECHAIN = - 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; - address private constant ALGEBRA_QUOTER_V2_APECHAIN = - 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; - address private constant ALGEBRA_POOL_APECHAIN = - 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; - address private constant APE_ETH_HOLDER_APECHAIN = - address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); - - address private constant IMPOSSIBLE_POOL_ADDRESS = - 0x0000000000000000000000000000000000000001; - - struct AlgebraSwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; - SwapDirection direction; - bool supportsFeeOnTransfer; - } - - error AlgebraSwapUnexpected(); - - function setUp() public override { - setupApechain(); - super.setUp(); - } - - function _addDexFacet() internal override { - algebraFacet = new AlgebraFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = algebraFacet.swapAlgebra.selector; - functionSelectors[1] = algebraFacet.algebraSwapCallback.selector; - addFacet( - address(ldaDiamond), - address(algebraFacet), - functionSelectors - ); - - algebraFacet = AlgebraFacet(payable(address(ldaDiamond))); - } - - // Override the abstract test with Algebra implementation - function test_CanSwap_FromDexAggregator() public override { - // Fund LDA from whale address - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(address(coreRouteFacet), 1 * 1e18); - - vm.startPrank(USER_SENDER); - - _testAlgebraSwap( - AlgebraSwapTestParams({ - from: address(coreRouteFacet), - to: address(USER_SENDER), - tokenIn: APE_ETH_TOKEN, - amountIn: IERC20(APE_ETH_TOKEN).balanceOf( - address(coreRouteFacet) - ) - 1, - tokenOut: address(WETH_TOKEN), - direction: SwapDirection.Token0ToToken1, - supportsFeeOnTransfer: true - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_FeeOnTransferToken() public { - setupApechain(); - - uint256 amountIn = 534451326669177; - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); - - vm.startPrank(APE_ETH_HOLDER_APECHAIN); - - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), amountIn); - - // Build route for algebra swap with command code 2 (user funds) - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: APE_ETH_HOLDER_APECHAIN, - pool: ALGEBRA_POOL_APECHAIN, - supportsFeeOnTransfer: true - }) - ); - - // 2. Build the final route with the command and length-prefixed swapData - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix - swapData - ); - - // Track initial balance - uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( - APE_ETH_HOLDER_APECHAIN - ); - - // Execute the swap - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - amountIn, - WETH_TOKEN, - 0, // minOut = 0 for this test - APE_ETH_HOLDER_APECHAIN, - route - ); - - // Verify balances - uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( - APE_ETH_HOLDER_APECHAIN - ); - assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); - - vm.stopPrank(); - } - - function test_CanSwap() public override { - vm.startPrank(APE_ETH_HOLDER_APECHAIN); - - // Transfer tokens from whale to USER_SENDER - uint256 amountToTransfer = 100 * 1e18; - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); - - vm.stopPrank(); - - vm.startPrank(USER_SENDER); - - _testAlgebraSwap( - AlgebraSwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: APE_ETH_TOKEN, - amountIn: 10 * 1e18, - tokenOut: address(WETH_TOKEN), - direction: SwapDirection.Token0ToToken1, - supportsFeeOnTransfer: true - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_Reverse() public { - test_CanSwap(); - - vm.startPrank(USER_SENDER); - - _testAlgebraSwap( - AlgebraSwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(WETH_TOKEN), - amountIn: 5 * 1e18, - tokenOut: APE_ETH_TOKEN, - direction: SwapDirection.Token1ToToken0, - supportsFeeOnTransfer: false - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { - MultiHopTestState memory state; - state.isFeeOnTransfer = true; - - // Setup tokens and pools - state = _setupTokensAndPools(state); - - // Execute and verify swap - _executeAndVerifyMultiHopSwap(state); - } - - function test_CanSwap_MultiHop() public override { - MultiHopTestState memory state; - state.isFeeOnTransfer = false; - - // Setup tokens and pools - state = _setupTokensAndPools(state); - - // Execute and verify swap - _executeAndVerifyMultiHopSwap(state); - } - - // Test that the proper error is thrown when algebra swap fails - function testRevert_SwapUnexpected() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Create invalid pool address - address invalidPool = address(0x999); - - // Mock token0() call on invalid pool - vm.mockCall( - invalidPool, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) - ); - - // Create a route with an invalid pool - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: USER_SENDER, - pool: invalidPool, - supportsFeeOnTransfer: true - }) - ); - - bytes memory invalidRoute = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix - swapData - ); - - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // Mock the algebra pool to not reset lastCalledPool - vm.mockCall( - invalidPool, - abi.encodeWithSelector( - IAlgebraPool.swapSupportingFeeOnInputTokens.selector - ), - abi.encode(0, 0) - ); - - // Expect the AlgebraSwapUnexpected error - vm.expectRevert(AlgebraSwapUnexpected.selector); - - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - invalidRoute - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - // Helper function to setup tokens and pools - function _setupTokensAndPools( - MultiHopTestState memory state - ) private returns (MultiHopTestState memory) { - // Create tokens - ERC20 tokenA = new ERC20( - "Token A", - state.isFeeOnTransfer ? "FTA" : "TA", - 18 - ); - IERC20 tokenB; - ERC20 tokenC = new ERC20( - "Token C", - state.isFeeOnTransfer ? "FTC" : "TC", - 18 - ); - - if (state.isFeeOnTransfer) { - tokenB = IERC20( - address( - new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) - ) - ); - } else { - tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); - } - - state.tokenA = IERC20(address(tokenA)); - state.tokenB = tokenB; - state.tokenC = IERC20(address(tokenC)); - - // Label addresses - vm.label(address(state.tokenA), "Token A"); - vm.label(address(state.tokenB), "Token B"); - vm.label(address(state.tokenC), "Token C"); - - // Mint initial token supplies - tokenA.mint(address(this), 1_000_000 * 1e18); - if (!state.isFeeOnTransfer) { - ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); - } else { - MockFeeOnTransferToken(address(tokenB)).mint( - address(this), - 1_000_000 * 1e18 - ); - } - tokenC.mint(address(this), 1_000_000 * 1e18); - - // Create pools - state.pool1 = _createAlgebraPool( - address(state.tokenA), - address(state.tokenB) - ); - state.pool2 = _createAlgebraPool( - address(state.tokenB), - address(state.tokenC) - ); - - vm.label(state.pool1, "Pool 1"); - vm.label(state.pool2, "Pool 2"); - - // Add liquidity - _addLiquidityToPool( - state.pool1, - address(state.tokenA), - address(state.tokenB) - ); - _addLiquidityToPool( - state.pool2, - address(state.tokenB), - address(state.tokenC) - ); - - state.amountToTransfer = 100 * 1e18; - state.amountIn = 50 * 1e18; - - // Transfer tokens to USER_SENDER - IERC20(address(state.tokenA)).transfer( - USER_SENDER, - state.amountToTransfer - ); - - return state; - } - - // Helper function to execute and verify the swap - function _executeAndVerifyMultiHopSwap( - MultiHopTestState memory state - ) private { - vm.startPrank(USER_SENDER); - - uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( - USER_SENDER - ); - uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( - USER_SENDER - ); - - // Approve spending - IERC20(address(state.tokenA)).approve( - address(ldaDiamond), - state.amountIn - ); - - // Build route - bytes memory route = _buildMultiHopRouteForTest(state); - - // Execute swap - coreRouteFacet.processRoute( - address(state.tokenA), - state.amountIn, - address(state.tokenC), - 0, // No minimum amount out for testing - USER_SENDER, - route - ); - - // Verify results - _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); - - vm.stopPrank(); - } - - // Helper function to build the multi-hop route for test - function _buildMultiHopRouteForTest( - MultiHopTestState memory state - ) private view returns (bytes memory) { - // 1. Get the specific data payload for each hop - bytes memory firstHopData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: address(state.tokenA), - recipient: address(ldaDiamond), // Hop 1 sends to the contract itself - pool: state.pool1, - supportsFeeOnTransfer: false - }) - ); - - bytes memory secondHopData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessMyERC20, - tokenIn: address(state.tokenB), - recipient: USER_SENDER, // Hop 2 sends to the final user - pool: state.pool2, - supportsFeeOnTransfer: state.isFeeOnTransfer - }) - ); - - // 2. Assemble the first full command with its length prefix - bytes memory firstCommand = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - state.tokenA, - uint8(1), - FULL_SHARE, - uint16(firstHopData.length), - firstHopData - ); - - // 3. Assemble the second full command with its length prefix - bytes memory secondCommand = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - state.tokenB, - uint8(1), // num splits for the second hop - FULL_SHARE, // full share for the second hop - uint16(secondHopData.length), - secondHopData - ); - - // 4. Concatenate the commands to create the final route - return bytes.concat(firstCommand, secondCommand); - } - - // Helper function to verify multi-hop results - function _verifyMultiHopResults( - MultiHopTestState memory state, - uint256 initialBalanceA, - uint256 initialBalanceC - ) private { - uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( - USER_SENDER - ); - uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( - USER_SENDER - ); - - assertApproxEqAbs( - initialBalanceA - finalBalanceA, - state.amountIn, - 1, // 1 wei tolerance - "TokenA spent amount mismatch" - ); - assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); - - emit log_named_uint( - state.isFeeOnTransfer - ? "Output amount with fee tokens" - : "Output amount with regular tokens", - finalBalanceC - initialBalanceC - ); - } - - // Helper function to create an Algebra pool - function _createAlgebraPool( - address tokenA, - address tokenB - ) internal returns (address pool) { - // Call the actual Algebra factory to create a pool - pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( - tokenA, - tokenB - ); - return pool; - } - - // Helper function to add liquidity to a pool - function _addLiquidityToPool( - address pool, - address token0, - address token1 - ) internal { - // For fee-on-transfer tokens, we need to send more to account for the fee - // We'll use a small amount and send extra to cover fees - uint256 initialAmount0 = 1e17; // 0.1 token - uint256 initialAmount1 = 1e17; // 0.1 token - - // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) - uint256 transferAmount0 = (initialAmount0 * 110) / 100; - uint256 transferAmount1 = (initialAmount1 * 110) / 100; - - // Initialize with 1:1 price ratio (Q64.96 format) - uint160 initialPrice = uint160(1 << 96); - IAlgebraPool(pool).initialize(initialPrice); - - // Create AlgebraLiquidityAdderHelper with safe transfer logic - AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( - token0, - token1 - ); - - // Transfer tokens with extra amounts to account for fees - IERC20(token0).transfer( - address(algebraLiquidityAdderHelper), - transferAmount0 - ); - IERC20(token1).transfer( - address(algebraLiquidityAdderHelper), - transferAmount1 - ); - - // Get actual balances to use for liquidity, accounting for any fees - uint256 actualBalance0 = IERC20(token0).balanceOf( - address(algebraLiquidityAdderHelper) - ); - uint256 actualBalance1 = IERC20(token1).balanceOf( - address(algebraLiquidityAdderHelper) - ); - - // Use the smaller of the two balances for liquidity amount - uint128 liquidityAmount = uint128( - actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 - ); - - // Add liquidity using the actual token amounts we have - algebraLiquidityAdderHelper.addLiquidity( - pool, - -887220, - 887220, - liquidityAmount / 2 // Use half of available liquidity to ensure success - ); - } - - struct MultiHopTestState { - IERC20 tokenA; - IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken - IERC20 tokenC; - address pool1; - address pool2; - uint256 amountIn; - uint256 amountToTransfer; - bool isFeeOnTransfer; - } - - struct AlgebraRouteParams { - CommandType commandCode; // 1 for contract funds, 2 for user funds - address tokenIn; // Input token address - address recipient; // Address receiving the output tokens - address pool; // Algebra pool address - bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens - } - - // Helper function to build route for Apechain Algebra swap - function _buildAlgebraSwapData( - AlgebraRouteParams memory params - ) private view returns (bytes memory) { - address token0 = IAlgebraPool(params.pool).token0(); - bool zeroForOne = (params.tokenIn == token0); - SwapDirection direction = zeroForOne - ? SwapDirection.Token0ToToken1 - : SwapDirection.Token1ToToken0; - - // This data blob is what the AlgebraFacet will receive and parse - return - abi.encodePacked( - AlgebraFacet.swapAlgebra.selector, - params.pool, - uint8(direction), - params.recipient, - params.supportsFeeOnTransfer ? uint8(1) : uint8(0) - ); - } - - // Helper function to test an Algebra swap - function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { - // Find or create a pool - address pool = _getPool(params.tokenIn, params.tokenOut); - vm.label(pool, "AlgebraPool"); - - // Get token0 from pool for labeling - address token0 = IAlgebraPool(pool).token0(); - if (params.tokenIn == token0) { - vm.label( - params.tokenIn, - string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( - params.tokenOut, - string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") - ); - } else { - vm.label( - params.tokenIn, - string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( - params.tokenOut, - string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") - ); - } - - // Record initial balances - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - - // Get expected output from QuoterV2 - uint256 expectedOutput = _getQuoteExactInput( - params.tokenIn, - params.tokenOut, - params.amountIn - ); - - // 1. Pack the specific data for this swap - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper - tokenIn: params.tokenIn, - recipient: params.to, - pool: pool, - supportsFeeOnTransfer: params.supportsFeeOnTransfer - }) - ); - - // 2. Approve tokens - IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - - // 3. Set up event expectations - address fromAddress = params.from == address(coreRouteFacet) - ? USER_SENDER - : params.from; - - vm.expectEmit(true, true, true, false); - emit Route( - fromAddress, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - expectedOutput, - expectedOutput - ); - - // 4. Build the route inline and execute the swap to save stack space - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - (expectedOutput * 995) / 1000, // minOut calculated inline - params.to, - abi.encodePacked( - uint8( - params.from == address(coreRouteFacet) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 - ), - params.tokenIn, - uint8(1), - FULL_SHARE, - uint16(swapData.length), - swapData - ) - ); - - // 5. Verify final balances - uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - - assertApproxEqAbs( - initialTokenIn - finalTokenIn, - params.amountIn, - 1, - "TokenIn amount mismatch" - ); - assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); - } - - function _getPool( - address tokenA, - address tokenB - ) private view returns (address pool) { - pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( - tokenA, - tokenB - ); - if (pool == address(0)) revert PoolDoesNotExist(); - return pool; - } - - function _getQuoteExactInput( - address tokenIn, - address tokenOut, - uint256 amountIn - ) private returns (uint256 amountOut) { - (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) - .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); - return amountOut; - } - - function testRevert_AlgebraSwap_ZeroAddressPool() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Mock token0() call on address(0) - vm.mockCall( - address(0), - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) - ); - - // Build route with address(0) as pool - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: USER_SENDER, - pool: address(0), // Zero address pool - supportsFeeOnTransfer: true - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix - swapData - ); - - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { - // // Transfer tokens from whale to user - // vm.prank(APE_ETH_HOLDER_APECHAIN); - // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - // vm.startPrank(USER_SENDER); - - // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS - // vm.mockCall( - // IMPOSSIBLE_POOL_ADDRESS, - // abi.encodeWithSelector(IAlgebraPool.token0.selector), - // abi.encode(APE_ETH_TOKEN) - // ); - - // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool - // bytes memory swapData = _buildAlgebraSwapData( - // AlgebraRouteParams({ - // commandCode: CommandType.ProcessUserERC20, - // tokenIn: APE_ETH_TOKEN, - // recipient: USER_SENDER, - // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address - // supportsFeeOnTransfer: true - // }) - // ); - - // bytes memory route = abi.encodePacked( - // uint8(CommandType.ProcessUserERC20), - // APE_ETH_TOKEN, - // uint8(1), // number of pools/splits - // FULL_SHARE, // 100% share - // uint16(swapData.length), // <--- Add the length prefix - // swapData - // ); - - // // Approve tokens - // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // // Expect revert with InvalidCallData - // vm.expectRevert(InvalidCallData.selector); - - // coreRouteFacet.processRoute( - // APE_ETH_TOKEN, - // 1 * 1e18, - // address(WETH_TOKEN), - // 0, - // USER_SENDER, - // route - // ); - - // vm.stopPrank(); - // vm.clearMockedCalls(); - // } - - function testRevert_AlgebraSwap_ZeroAddressRecipient() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Mock token0() call on the pool - vm.mockCall( - ALGEBRA_POOL_APECHAIN, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) - ); - - // Build route with address(0) as recipient - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: address(0), // Zero address recipient - pool: ALGEBRA_POOL_APECHAIN, - supportsFeeOnTransfer: true - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - APE_ETH_TOKEN, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // <--- Add the length prefix - swapData - ); - - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - - coreRouteFacet.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } -} - -/** - * @title LiFiDexAggregatorIzumiV3UpgradeTest - * @notice Tests specific to iZiSwap V3 selector - */ -contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { - IzumiV3Facet private izumiV3Facet; - - // ==================== iZiSwap V3 specific variables ==================== - // Base constants - address internal constant USDC = - 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - address internal constant WETH = - 0x4200000000000000000000000000000000000006; - address internal constant USDB_C = - 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; - - // iZiSwap pools - address internal constant IZUMI_WETH_USDC_POOL = - 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; - address internal constant IZUMI_WETH_USDB_C_POOL = - 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; - - // Test parameters - uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals - uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals - - // structs - struct IzumiV3SwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; - SwapDirection direction; - } - - struct MultiHopTestParams { - address tokenIn; - address tokenMid; - address tokenOut; - address pool1; - address pool2; - uint256 amountIn; - SwapDirection direction1; - SwapDirection direction2; - } - - error IzumiV3SwapUnexpected(); - error IzumiV3SwapCallbackUnknownSource(); - error IzumiV3SwapCallbackNotPositiveAmount(); - - // Setup function for Base tests - function setupBase() internal { - customRpcUrlForForking = "ETH_NODE_URI_BASE"; - customBlockNumberForForking = 29831758; - } - - function setUp() public override { - setupBase(); - super.setUp(); - - deal(address(USDC), address(USER_SENDER), 1_000 * 1e6); - } - - function _addDexFacet() internal override { - izumiV3Facet = new IzumiV3Facet(); - bytes4[] memory functionSelectors = new bytes4[](3); - functionSelectors[0] = izumiV3Facet.swapIzumiV3.selector; - functionSelectors[1] = izumiV3Facet.swapX2YCallback.selector; - functionSelectors[2] = izumiV3Facet.swapY2XCallback.selector; - addFacet( - address(ldaDiamond), - address(izumiV3Facet), - functionSelectors - ); - - izumiV3Facet = IzumiV3Facet(payable(address(ldaDiamond))); - } - - function test_CanSwap_FromDexAggregator() public override { - // Test USDC -> WETH - deal(USDC, address(coreRouteFacet), AMOUNT_USDC); - - vm.startPrank(USER_SENDER); - _testSwap( - IzumiV3SwapTestParams({ - from: address(coreRouteFacet), - to: USER_SENDER, - tokenIn: USDC, - amountIn: AMOUNT_USDC, - tokenOut: WETH, - direction: SwapDirection.Token1ToToken0 - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - _testMultiHopSwap( - MultiHopTestParams({ - tokenIn: USDC, - tokenMid: WETH, - tokenOut: USDB_C, - pool1: IZUMI_WETH_USDC_POOL, - pool2: IZUMI_WETH_USDB_C_POOL, - amountIn: AMOUNT_USDC, - direction1: SwapDirection.Token1ToToken0, - direction2: SwapDirection.Token0ToToken1 - }) - ); - } - - function test_CanSwap() public override { - deal(address(USDC), USER_SENDER, AMOUNT_USDC); - - vm.startPrank(USER_SENDER); - IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); - - bytes memory swapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: IZUMI_WETH_USDC_POOL, - direction: SwapDirection.Token1ToToken0, - recipient: USER_RECEIVER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - USDC, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix - swapData - ); - - vm.expectEmit(true, true, true, false); - emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); - - coreRouteFacet.processRoute( - USDC, - AMOUNT_USDC, - WETH, - 0, - USER_RECEIVER, - route - ); - - vm.stopPrank(); - } - - function testRevert_IzumiV3SwapUnexpected() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); - - vm.startPrank(USER_SENDER); - - // create invalid pool address - address invalidPool = address(0x999); - - bytes memory swapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: invalidPool, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - // create a route with an invalid pool - bytes memory invalidRoute = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - USDC, - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix - swapData - ); - - IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); - - // mock the iZiSwap pool to return without updating lastCalledPool - vm.mockCall( - invalidPool, - abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), - abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool - ); - - vm.expectRevert(IzumiV3SwapUnexpected.selector); - - coreRouteFacet.processRoute( - USDC, - AMOUNT_USDC, - WETH, - 0, - USER_SENDER, - invalidRoute - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - function testRevert_UnexpectedCallbackSender() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); - - // Set up the expected callback sender through the diamond - vm.store( - address(ldaDiamond), - keccak256("com.lifi.lda.callbackmanager"), - bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) - ); - - // Try to call callback from a different address than expected - address unexpectedCaller = address(0xdead); - vm.prank(unexpectedCaller); - vm.expectRevert( - abi.encodeWithSelector( - LibCallbackManager.UnexpectedCallbackSender.selector, - unexpectedCaller, - IZUMI_WETH_USDC_POOL - ) - ); - izumiV3Facet.swapY2XCallback(1, 1, abi.encode(USDC)); - } - - function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); - - // Set the expected callback sender through the diamond storage - vm.store( - address(ldaDiamond), - keccak256("com.lifi.lda.callbackmanager"), - bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) - ); - - // try to call the callback with zero amount - vm.prank(IZUMI_WETH_USDC_POOL); - vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); - izumiV3Facet.swapY2XCallback( - 0, - 0, // zero amount should trigger the error - abi.encode(USDC) - ); - } - - function testRevert_FailsIfAmountInIsTooLarge() public { - deal(address(WETH), USER_SENDER, type(uint256).max); - - vm.startPrank(USER_SENDER); - IERC20(WETH).approve(address(ldaDiamond), type(uint256).max); - - bytes memory swapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: IZUMI_WETH_USDC_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_RECEIVER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - WETH, - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix - swapData - ); - - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - WETH, - type(uint216).max, - USDC, - 0, - USER_RECEIVER, - route - ); - - vm.stopPrank(); - } - - function _testSwap(IzumiV3SwapTestParams memory params) internal { - // Fund the sender with tokens if not the contract itself - if (params.from != address(coreRouteFacet)) { - deal(params.tokenIn, params.from, params.amountIn); - } - - // Capture initial token balances - uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( - params.from - ); - uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( - params.to - ); - - // Build the route based on the command type - CommandType commandCode = params.from == address(coreRouteFacet) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; - - bytes memory swapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: IZUMI_WETH_USDC_POOL, - direction: params.direction == SwapDirection.Token0ToToken1 - ? SwapDirection.Token0ToToken1 - : SwapDirection.Token1ToToken0, - recipient: params.to - }) - ); - - bytes memory route = abi.encodePacked( - uint8(commandCode), - params.tokenIn, - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix - swapData - ); - - // Approve tokens if necessary - if (params.from == USER_SENDER) { - vm.startPrank(USER_SENDER); - IERC20(params.tokenIn).approve( - address(ldaDiamond), - params.amountIn - ); - } - - // Expect the Route event emission - address from = params.from == address(coreRouteFacet) - ? USER_SENDER - : params.from; - - vm.expectEmit(true, true, true, false); - emit Route( - from, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - 0, // No minimum amount enforced in test - 0 // Actual amount will be checked after the swap - ); - - // Execute the swap - uint256 amountOut = coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - 0, // No minimum amount for testing - params.to, - route - ); - - if (params.from == USER_SENDER) { - vm.stopPrank(); - } - - // Verify balances have changed correctly - uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); - - assertApproxEqAbs( - initialBalanceIn - finalBalanceIn, - params.amountIn, - 1, // 1 wei tolerance because of undrain protection for dex aggregator - "TokenIn amount mismatch" - ); - assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); - assertEq( - amountOut, - finalBalanceOut - initialBalanceOut, - "AmountOut mismatch" - ); - - emit log_named_uint("Amount In", params.amountIn); - emit log_named_uint("Amount Out", amountOut); - } - - function _testMultiHopSwap(MultiHopTestParams memory params) internal { - // Fund the sender with tokens - deal(params.tokenIn, USER_SENDER, params.amountIn); - - // Capture initial token balances - uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - - // Build first swap data - bytes memory firstSwapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: params.pool1, - direction: params.direction1, - recipient: address(coreRouteFacet) - }) - ); - - bytes memory firstHop = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - params.tokenIn, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(firstSwapData.length), // length prefix - firstSwapData - ); - - // Build second swap data - bytes memory secondSwapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: params.pool2, - direction: params.direction2, - recipient: USER_SENDER - }) - ); - - bytes memory secondHop = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - params.tokenMid, - uint8(1), // number of pools/splits - FULL_SHARE, // 100% share - uint16(secondSwapData.length), // length prefix - secondSwapData - ); - - // Combine into route - bytes memory route = bytes.concat(firstHop, secondHop); - - // Approve tokens - vm.startPrank(USER_SENDER); - IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - - // Execute the swap - uint256 amountOut = coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - 0, // No minimum amount for testing - USER_SENDER, - route - ); - vm.stopPrank(); - - // Verify balances have changed correctly - uint256 finalBalanceIn; - uint256 finalBalanceOut; - - finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); - finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - - assertEq( - initialBalanceIn - finalBalanceIn, - params.amountIn, - "TokenIn amount mismatch" - ); - assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); - assertEq( - amountOut, - finalBalanceOut - initialBalanceOut, - "AmountOut mismatch" - ); - } - - function _buildIzumiV3Route( - CommandType commandCode, - address tokenIn, - uint8 direction, - address pool, - address recipient - ) internal pure returns (bytes memory) { - return - abi.encodePacked( - uint8(commandCode), - tokenIn, - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - IzumiV3Facet.swapIzumiV3.selector, - pool, - uint8(direction), - recipient - ); - } - - function _buildIzumiV3MultiHopRoute( - MultiHopTestParams memory params - ) internal view returns (bytes memory) { - // First hop: USER_ERC20 -> LDA - bytes memory firstHop = _buildIzumiV3Route( - CommandType.ProcessUserERC20, - params.tokenIn, - uint8(params.direction1), - params.pool1, - address(coreRouteFacet) - ); - - // Second hop: MY_ERC20 (LDA) -> pool2 - bytes memory secondHop = _buildIzumiV3Route( - CommandType.ProcessMyERC20, - params.tokenMid, - uint8(params.direction2), - params.pool2, - USER_SENDER // final recipient - ); - - // Combine the two hops - return bytes.concat(firstHop, secondHop); - } - - struct IzumiV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - - function _buildIzumiV3SwapData( - IzumiV3SwapParams memory params - ) internal view returns (bytes memory) { - return - abi.encodePacked( - izumiV3Facet.swapIzumiV3.selector, - params.pool, - uint8(params.direction), - params.recipient - ); - } -} - -// ----------------------------------------------------------------------------- -// HyperswapV3 on HyperEVM -// ----------------------------------------------------------------------------- -contract LiFiDexAggregatorHyperswapV3UpgradeTest is - LiFiDexAggregatorUpgradeTest -{ - using SafeERC20 for IERC20; - - UniV3StyleFacet internal uniV3StyleFacet; - - /// @dev HyperswapV3 router on HyperEVM chain - IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = - IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); - /// @dev HyperswapV3 quoter on HyperEVM chain - IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = - IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); - - /// @dev a liquid USDT on HyperEVM - IERC20 internal constant USDT0 = - IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); - /// @dev WHYPE on HyperEVM - IERC20 internal constant WHYPE = - IERC20(0x5555555555555555555555555555555555555555); - - struct HyperswapV3Params { - CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 - address tokenIn; // Input token address - address recipient; // Address receiving the output tokens - address pool; // HyperswapV3 pool address - bool zeroForOne; // Direction of the swap - } - - function setUp() public override { - setupHyperEVM(); - super.setUp(); - - deal(address(USDT0), address(USER_SENDER), 1_000 * 1e6); - } - - function _addDexFacet() internal override { - uniV3StyleFacet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; - functionSelectors[1] = uniV3StyleFacet - .hyperswapV3SwapCallback - .selector; - addFacet( - address(ldaDiamond), - address(uniV3StyleFacet), - functionSelectors - ); - - uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - - deal(address(USDT0), USER_SENDER, amountIn); - - // user approves - vm.prank(USER_SENDER); - USDT0.approve(address(ldaDiamond), amountIn); - - // fetch the real pool and quote - address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), - 3000 - ); - - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn, - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params - ); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: pool, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDT0), - uint8(1), // 1 pool - FULL_SHARE, // FULL_SHARE - uint16(swapData.length), // length prefix - swapData - ); - - // expect the Route event - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn, - quoted, - quoted - ); - - // execute - vm.prank(USER_SENDER); - coreRouteFacet.processRoute( - address(USDT0), - amountIn, - address(WHYPE), - quoted, - USER_SENDER, - route - ); - } - - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - - // Fund dex aggregator contract - deal(address(USDT0), address(ldaDiamond), amountIn); - - // fetch the real pool and quote - address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), - 3000 - ); - - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params - ); - - // Build route using our helper function - // bytes memory route = _buildHyperswapV3Route( - // HyperswapV3Params({ - // commandCode: CommandType.ProcessMyERC20, - // tokenIn: address(USDT0), - // recipient: USER_SENDER, - // pool: pool, - // zeroForOne: true // USDT0 < WHYPE - // }) - // ); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: pool, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(USDT0), - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint16(swapData.length), // length prefix - swapData - ); - - // expect the Route event - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn - 1, // Account for slot undrain protection - quoted, - quoted - ); - - // execute - vm.prank(USER_SENDER); - coreRouteFacet.processRoute( - address(USDT0), - amountIn - 1, // Account for slot undrain protection - address(WHYPE), - quoted, - USER_SENDER, - route - ); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. - // HyperswapV3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. HyperswapV3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } - - // function _buildHyperswapV3Route( - // HyperswapV3Params memory params - // ) internal pure returns (bytes memory route) { - // route = abi.encodePacked( - // uint8(params.commandCode), - // params.tokenIn, - // uint8(1), // 1 pool - // FULL_SHARE, // 65535 - 100% share - // uint8(PoolType.UniV3), // POOL_TYPE_UNIV3 = 1 - // params.pool, - // uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false - // params.recipient - // ); - - // return route; - // } - - struct UniV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - - function _buildUniV3SwapData( - UniV3SwapParams memory params - ) internal returns (bytes memory) { - return - abi.encodePacked( - uniV3StyleFacet.swapUniV3.selector, - params.pool, - uint8(params.direction), - params.recipient - ); - } -} - -// ----------------------------------------------------------------------------- -// LaminarV3 on HyperEVM -// ----------------------------------------------------------------------------- -contract LiFiDexAggregatorLaminarV3UpgradeTest is - LiFiDexAggregatorUpgradeTest -{ - UniV3StyleFacet internal uniV3StyleFacet; - using SafeERC20 for IERC20; - - IERC20 internal constant WHYPE = - IERC20(0x5555555555555555555555555555555555555555); - IERC20 internal constant LHYPE = - IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); - - address internal constant WHYPE_LHYPE_POOL = - 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; - - function setUp() public override { - setupHyperEVM(); - super.setUp(); - } - - function _addDexFacet() internal override { - uniV3StyleFacet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; - functionSelectors[1] = uniV3StyleFacet.laminarV3SwapCallback.selector; - addFacet( - address(ldaDiamond), - address(uniV3StyleFacet), - functionSelectors - ); - - uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e18; - - // Fund the user with WHYPE - deal(address(WHYPE), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WHYPE.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: WHYPE_LHYPE_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(WHYPE), - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances - uint256 inBefore = WHYPE.balanceOf(USER_SENDER); - uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - - // Execute swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(WHYPE), - amountIn, - address(LHYPE), - 0, - USER_SENDER, - route - ); - - // Verify - uint256 inAfter = WHYPE.balanceOf(USER_SENDER); - uint256 outAfter = LHYPE.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the aggregator directly - deal(address(WHYPE), address(uniV3StyleFacet), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: WHYPE_LHYPE_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(WHYPE), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData - ); - - uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - - // Withdraw 1 wei to avoid slot-undrain protection - coreRouteFacet.processRoute( - address(WHYPE), - amountIn - 1, - address(LHYPE), - 0, - USER_SENDER, - route - ); - - uint256 outAfter = LHYPE.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. - // Laminar V3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. Laminar V3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } - - struct UniV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - - function _buildUniV3SwapData( - UniV3SwapParams memory params - ) internal returns (bytes memory) { - return - abi.encodePacked( - uniV3StyleFacet.swapUniV3.selector, - params.pool, - uint8(params.direction), - params.recipient - ); - } -} - -contract LiFiDexAggregatorXSwapV3UpgradeTest is LiFiDexAggregatorUpgradeTest { - using SafeERC20 for IERC20; - - UniV3StyleFacet internal uniV3StyleFacet; - - address internal constant USDC_E_WXDC_POOL = - 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; - - /// @dev our two tokens: USDC.e and wrapped XDC - IERC20 internal constant USDC_E = - IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); - IERC20 internal constant WXDC = - IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); - - function setUp() public override { - setupXDC(); - super.setUp(); - } - - function _addDexFacet() internal override { - uniV3StyleFacet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; - functionSelectors[1] = uniV3StyleFacet.xswapCallback.selector; - addFacet( - address(ldaDiamond), - address(uniV3StyleFacet), - functionSelectors - ); - - uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e6; - deal(address(USDC_E), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - USDC_E.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: USDC_E_WXDC_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDC_E), - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances before swap - uint256 inBefore = USDC_E.balanceOf(USER_SENDER); - uint256 outBefore = WXDC.balanceOf(USER_SENDER); - - // Execute swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(USDC_E), - amountIn, - address(WXDC), - 0, - USER_SENDER, - route - ); - - // Verify balances after swap - uint256 inAfter = USDC_E.balanceOf(USER_SENDER); - uint256 outAfter = WXDC.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - - vm.stopPrank(); - } - - /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 5_000 * 1e6; - - // fund the aggregator - deal(address(USDC_E), address(uniV3StyleFacet), amountIn); - - vm.startPrank(USER_SENDER); - - // Account for slot-undrain protection - uint256 swapAmount = amountIn - 1; - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: USDC_E_WXDC_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(USDC_E), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances before swap - uint256 outBefore = WXDC.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(USDC_E), - swapAmount, - address(WXDC), - 0, - USER_SENDER, - route - ); - - // Verify balances after swap - uint256 outAfter = WXDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. - // XSwap V3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. XSwap V3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } - - struct UniV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - - function _buildUniV3SwapData( - UniV3SwapParams memory params - ) internal returns (bytes memory) { - return - abi.encodePacked( - uniV3StyleFacet.swapUniV3.selector, - params.pool, - uint8(params.direction), - params.recipient - ); - } -} - -// ----------------------------------------------------------------------------- -// RabbitSwap on Viction -// ----------------------------------------------------------------------------- -contract LiFiDexAggregatorRabbitSwapUpgradeTest is - LiFiDexAggregatorUpgradeTest -{ - using SafeERC20 for IERC20; - - UniV3StyleFacet internal uniV3StyleFacet; - - // Constants for RabbitSwap on Viction - IERC20 internal constant SOROS = - IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); - IERC20 internal constant C98 = - IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); - address internal constant SOROS_C98_POOL = - 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; - - function setUp() public override { - setupViction(); - super.setUp(); - } - - function _addDexFacet() internal override { - uniV3StyleFacet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; - functionSelectors[1] = uniV3StyleFacet - .rabbitSwapV3SwapCallback - .selector; - addFacet( - address(ldaDiamond), - address(uniV3StyleFacet), - functionSelectors - ); - - uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the user with SOROS - deal(address(SOROS), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - SOROS.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: SOROS_C98_POOL, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // record balances before swap - uint256 inBefore = SOROS.balanceOf(USER_SENDER); - uint256 outBefore = C98.balanceOf(USER_SENDER); - - // execute swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route - ); - - // verify balances after swap - uint256 inAfter = SOROS.balanceOf(USER_SENDER); - uint256 outAfter = C98.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive C98"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the aggregator directly - deal(address(SOROS), address(uniV3StyleFacet), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: SOROS_C98_POOL, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData - ); - - uint256 outBefore = C98.balanceOf(USER_SENDER); - - // withdraw 1 wei less to avoid slot-undrain protection - coreRouteFacet.processRoute( - address(SOROS), - amountIn - 1, - address(C98), - 0, - USER_SENDER, - route - ); - - uint256 outAfter = C98.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive C98"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. - // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, - // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. UniV3-style pools immediately revert on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } - - function testRevert_RabbitSwapInvalidPool() public { - uint256 amountIn = 1_000 * 1e18; - deal(address(SOROS), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - SOROS.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: address(0), - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData - ); - - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - } - - function testRevert_RabbitSwapInvalidRecipient() public { - uint256 amountIn = 1_000 * 1e18; - deal(address(SOROS), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - SOROS.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: SOROS_C98_POOL, - direction: SwapDirection.Token1ToToken0, - recipient: address(0) - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint16(swapData.length), // length prefix - swapData - ); - - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - } - - struct UniV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - - function _buildUniV3SwapData( - UniV3SwapParams memory params - ) internal returns (bytes memory) { - return - abi.encodePacked( - uniV3StyleFacet.swapUniV3.selector, - params.pool, - uint8(params.direction), - params.recipient - ); - } -} - -// ---------------------------------------------- -// EnosysDexV3 on Flare -// ---------------------------------------------- -contract LiFiDexAggregatorEnosysDexV3UpgradeTest is - LiFiDexAggregatorUpgradeTest -{ - using SafeERC20 for IERC20; - - UniV3StyleFacet internal uniV3StyleFacet; - - /// @dev HLN token on Flare - IERC20 internal constant HLN = - IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); - - /// @dev USDT0 token on Flare - IERC20 internal constant USDT0 = - IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); - - /// @dev The single EnosysDexV3 pool for HLN–USDT0 - address internal constant ENOSYS_V3_POOL = - 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; - - function setUp() public override { - setupFlare(); - super.setUp(); - } - - function _addDexFacet() internal override { - uniV3StyleFacet = new UniV3StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; - functionSelectors[1] = uniV3StyleFacet - .enosysdexV3SwapCallback - .selector; - addFacet( - address(ldaDiamond), - address(uniV3StyleFacet), - functionSelectors - ); - - uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); - } - - /// @notice Single‐pool swap: USER sends HLN → receives USDT0 - function test_CanSwap() public override { - // Mint 1 000 HLN to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(HLN), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - HLN.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: ENOSYS_V3_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(HLN), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances before swap - uint256 inBefore = HLN.balanceOf(USER_SENDER); - uint256 outBefore = USDT0.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(HLN), - amountIn, - address(USDT0), - 0, - USER_SENDER, - route - ); - - // Verify that HLN was spent and some USDT0 was received - uint256 inAfter = HLN.balanceOf(USER_SENDER); - uint256 outAfter = USDT0.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - - vm.stopPrank(); - } - - /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 - function test_CanSwap_FromDexAggregator() public override { - // Fund the aggregator with 1 000 HLN - uint256 amountIn = 1_000 * 1e18; - deal(address(HLN), address(coreRouteFacet), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: ENOSYS_V3_POOL, - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(HLN), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDT0.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(HLN), - swapAmount, - address(USDT0), - 0, - USER_SENDER, - route - ); - - // Verify that some USDT0 was received - uint256 outAfter = USDT0.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. - // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, - // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. UniV3-style pools immediately revert on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } - - struct UniV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - - function _buildUniV3SwapData( - UniV3SwapParams memory params - ) internal view returns (bytes memory) { - return - abi.encodePacked( - uniV3StyleFacet.swapUniV3.selector, - params.pool, - uint8(params.direction), - params.recipient - ); - } -} - -// ---------------------------------------------- -// SyncSwapV2 on Linea -// ---------------------------------------------- -contract LiFiDexAggregatorSyncSwapV2UpgradeTest is - LiFiDexAggregatorUpgradeTest -{ - using SafeERC20 for IERC20; - - SyncSwapV2Facet internal syncSwapV2Facet; - - IERC20 internal constant USDC = - IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); - IERC20 internal constant WETH = - IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); - address internal constant USDC_WETH_POOL_V1 = - address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); - address internal constant SYNC_SWAP_VAULT = - address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); - - address internal constant USDC_WETH_POOL_V2 = - address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); - - IERC20 internal constant USDT = - IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); - address internal constant USDC_USDT_POOL_V1 = - address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); - - function setUp() public override { - setupLinea(); - super.setUp(); - } - - function _addDexFacet() internal override { - syncSwapV2Facet = new SyncSwapV2Facet(); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = syncSwapV2Facet.swapSyncSwapV2.selector; - addFacet( - address(ldaDiamond), - address(syncSwapV2Facet), - functionSelectors - ); - - syncSwapV2Facet = SyncSwapV2Facet(payable(address(ldaDiamond))); - } - - /// @notice Single‐pool swap: USER sends WETH → receives USDC - function test_CanSwap() public override { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, - to: address(USER_SENDER), - withdrawMode: 2, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances before swap - uint256 inBefore = WETH.balanceOf(USER_SENDER); - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that WETH was spent and some USDC_C was received - uint256 inAfter = WETH.balanceOf(USER_SENDER); - uint256 outAfter = USDC.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_PoolV2() public { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V2, - to: address(USER_SENDER), - withdrawMode: 2, - isV1Pool: 0, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Record balances before swap - uint256 inBefore = WETH.balanceOf(USER_SENDER); - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that WETH was spent and some USDC_C was received - uint256 inAfter = WETH.balanceOf(USER_SENDER); - uint256 outAfter = USDC.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - // Fund the aggregator with 1 000 WETH - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), address(ldaDiamond), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, - to: address(USER_SENDER), - withdrawMode: 2, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(WETH), - swapAmount, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that some USDC was received - uint256 outAfter = USDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator_PoolV2() public { - // Fund the aggregator with 1 000 WETH - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), address(ldaDiamond), amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V2, - to: address(USER_SENDER), - withdrawMode: 2, - isV1Pool: 0, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - coreRouteFacet.processRoute( - address(WETH), - swapAmount, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that some USDC was received - uint256 outAfter = USDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - uint256 amountIn = 1_000e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); - - uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); - uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); - - // - // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) - // - bytes memory swapData = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, - to: SYNC_SWAP_VAULT, - withdrawMode: 2, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory routeHop1 = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // - // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 - // - bytes memory swapDataHop2 = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_USDT_POOL_V1, - to: address(USER_SENDER), - withdrawMode: 2, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory routeHop2 = abi.encodePacked( - uint8(CommandType.ProcessOnePool), - address(USDC), - uint16(swapDataHop2.length), // length prefix - swapDataHop2 - ); - - bytes memory route = bytes.concat(routeHop1, routeHop2); - - uint256 amountOut = coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDT), - 0, - USER_SENDER, - route - ); - - uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); - uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); - - assertEq( - initialBalanceIn - afterBalanceIn, - amountIn, - "WETH spent mismatch" - ); - assertEq( - amountOut, - afterBalanceOut - initialBalanceOut, - "USDT amountOut mismatch" - ); - vm.stopPrank(); - } - - function testRevert_V1PoolMissingVaultAddress() public { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, - to: address(USER_SENDER), - withdrawMode: 2, - isV1Pool: 1, - vault: address(0) - }) - ); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - } - - function testRevert_InvalidPoolOrRecipient() public { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(ldaDiamond), amountIn); - - bytes memory swapData = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: address(0), - to: address(USER_SENDER), - withdrawMode: 2, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory routeWithInvalidPool = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapData.length), // length prefix - swapData - ); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidPool - ); - - bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, - to: address(0), - withdrawMode: 2, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory routeWithInvalidRecipient = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapDataWithInvalidRecipient.length), // length prefix - swapDataWithInvalidRecipient - ); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidRecipient - ); - - vm.stopPrank(); - } - - function testRevert_InvalidWithdrawMode() public { - vm.startPrank(USER_SENDER); - - bytes - memory swapDataWithInvalidWithdrawMode = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: USDC_WETH_POOL_V1, - to: address(USER_SENDER), - withdrawMode: 3, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); - - bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint16(swapDataWithInvalidWithdrawMode.length), // length prefix - swapDataWithInvalidWithdrawMode - ); - - // Expect revert with InvalidCallData because withdrawMode is invalid - vm.expectRevert(InvalidCallData.selector); - coreRouteFacet.processRoute( - address(WETH), - 1, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidWithdrawMode - ); - - vm.stopPrank(); - } - - struct SyncSwapV2SwapParams { - address pool; - address to; - uint8 withdrawMode; - uint8 isV1Pool; - address vault; - } - - function _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams memory params - ) internal view returns (bytes memory) { - return - abi.encodePacked( - syncSwapV2Facet.swapSyncSwapV2.selector, - params.pool, - params.to, - params.withdrawMode, - params.isV1Pool, - params.vault - ); - } -} +// import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; +// import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; +// import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; +// import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; +// import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +// import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; +// import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; +// import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; +// import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; +// import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; +// import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +// import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; + +// import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +// import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +// import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +// import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; +// import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; +// import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; + +// import { TestToken as ERC20 } from "../../utils/TestToken.sol"; +// import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; +// import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; +// import { TestHelpers } from "../../utils/TestHelpers.sol"; + +// // Command codes for route processing +// enum CommandType { +// None, // 0 - not used +// ProcessMyERC20, // 1 - processMyERC20 +// ProcessUserERC20, // 2 - processUserERC20 +// ProcessNative, // 3 - processNative +// ProcessOnePool, // 4 - processOnePool +// ProcessInsideBento, // 5 - processInsideBento +// ApplyPermit // 6 - applyPermit +// } +// // Direction constants +// enum SwapDirection { +// Token1ToToken0, // 0 +// Token0ToToken1 // 1 +// } + +// // Callback constants +// enum CallbackStatus { +// Disabled, // 0 +// Enabled // 1 +// } + +// // Other constants +// uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps + +// contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { +// event HookCalled( +// address sender, +// uint256 amount0, +// uint256 amount1, +// bytes data +// ); + +// function hook( +// address sender, +// uint256 amount0, +// uint256 amount1, +// bytes calldata data +// ) external { +// emit HookCalled(sender, amount0, amount1, data); +// } +// } +// /** +// * @title LiFiDexAggregatorUpgradeTest +// * @notice Base test contract with common functionality and abstractions for DEX-specific tests +// */ +// abstract contract LiFiDexAggregatorUpgradeTest is LdaDiamondTest, TestHelpers { +// using SafeERC20 for IERC20; + +// CoreRouteFacet internal coreRouteFacet; + +// // Common events and errors +// event Route( +// address indexed from, +// address to, +// address indexed tokenIn, +// address indexed tokenOut, +// uint256 amountIn, +// uint256 amountOutMin, +// uint256 amountOut +// ); +// event HookCalled( +// address sender, +// uint256 amount0, +// uint256 amount1, +// bytes data +// ); + +// error WrongPoolReserves(); +// error PoolDoesNotExist(); + +// function _addDexFacet() internal virtual; + +// // Setup function for Apechain tests +// function setupApechain() internal { +// customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; +// customBlockNumberForForking = 12912470; +// } + +// function setupHyperEVM() internal { +// customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; +// customBlockNumberForForking = 4433562; +// } + +// function setupXDC() internal { +// customRpcUrlForForking = "ETH_NODE_URI_XDC"; +// customBlockNumberForForking = 89279495; +// } + +// function setupViction() internal { +// customRpcUrlForForking = "ETH_NODE_URI_VICTION"; +// customBlockNumberForForking = 94490946; +// } + +// function setupFlare() internal { +// customRpcUrlForForking = "ETH_NODE_URI_FLARE"; +// customBlockNumberForForking = 42652369; +// } + +// function setupLinea() internal { +// customRpcUrlForForking = "ETH_NODE_URI_LINEA"; +// customBlockNumberForForking = 20077881; +// } + +// function setUp() public virtual override { +// fork(); +// LdaDiamondTest.setUp(); +// _addCoreRouteFacet(); +// _addDexFacet(); +// } + +// function _addCoreRouteFacet() internal { +// coreRouteFacet = new CoreRouteFacet(); +// bytes4[] memory functionSelectors = new bytes4[](1); +// functionSelectors[0] = CoreRouteFacet.processRoute.selector; +// addFacet( +// address(ldaDiamond), +// address(coreRouteFacet), +// functionSelectors +// ); + +// coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); +// } + +// // function test_ContractIsSetUpCorrectly() public { +// // assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); +// // assertEq( +// // liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), +// // true +// // ); +// // assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); +// // } + +// // function testRevert_FailsIfOwnerIsZeroAddress() public { +// // vm.expectRevert(InvalidConfig.selector); + +// // liFiDEXAggregator = new LiFiDEXAggregator( +// // address(0xCAFE), +// // privileged, +// // address(0) +// // ); +// // } + +// // ============================ Abstract DEX Tests ============================ +// /** +// * @notice Abstract test for basic token swapping functionality +// * Each DEX implementation should override this +// */ +// function test_CanSwap() public virtual { +// // Each DEX implementation must override this +// // solhint-disable-next-line gas-custom-errors +// revert("test_CanSwap: Not implemented"); +// } + +// /** +// * @notice Abstract test for swapping tokens from the DEX aggregator +// * Each DEX implementation should override this +// */ +// function test_CanSwap_FromDexAggregator() public virtual { +// // Each DEX implementation must override this +// // solhint-disable-next-line gas-custom-errors +// revert("test_CanSwap_FromDexAggregator: Not implemented"); +// } + +// /** +// * @notice Abstract test for multi-hop swapping +// * Each DEX implementation should override this +// */ +// function test_CanSwap_MultiHop() public virtual { +// // Each DEX implementation must override this +// // solhint-disable-next-line gas-custom-errors +// revert("test_CanSwap_MultiHop: Not implemented"); +// } +// } + +// /** +// * @title VelodromeV2 tests +// * @notice Tests specific to Velodrome V2 +// */ +// contract LiFiDexAggregatorVelodromeV2UpgradeTest is +// LiFiDexAggregatorUpgradeTest +// { +// VelodromeV2Facet internal velodromeV2Facet; + +// // ==================== Velodrome V2 specific variables ==================== +// IVelodromeV2Router internal constant VELODROME_V2_ROUTER = +// IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router +// address internal constant VELODROME_V2_FACTORY_REGISTRY = +// 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; +// IERC20 internal constant USDC_TOKEN = +// IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); +// IERC20 internal constant STG_TOKEN = +// IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); +// IERC20 internal constant USDC_E_TOKEN = +// IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); + +// MockVelodromeV2FlashLoanCallbackReceiver +// internal mockFlashloanCallbackReceiver; + +// // Velodrome V2 structs +// struct VelodromeV2SwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// bool stable; +// SwapDirection direction; +// bool callback; +// } + +// struct MultiHopTestParams { +// address tokenIn; +// address tokenMid; +// address tokenOut; +// address pool1; +// address pool2; +// uint256[] amounts1; +// uint256[] amounts2; +// uint256 pool1Fee; +// uint256 pool2Fee; +// } + +// struct ReserveState { +// uint256 reserve0Pool1; +// uint256 reserve1Pool1; +// uint256 reserve0Pool2; +// uint256 reserve1Pool2; +// } + +// // Setup function for Optimism tests +// function setupOptimism() internal { +// customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; +// customBlockNumberForForking = 133999121; +// } + +// function setUp() public override { +// setupOptimism(); +// super.setUp(); + +// deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); +// } + +// function _addDexFacet() internal override { +// velodromeV2Facet = new VelodromeV2Facet(); +// bytes4[] memory functionSelectors = new bytes4[](1); +// functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; +// addFacet( +// address(ldaDiamond), +// address(velodromeV2Facet), +// functionSelectors +// ); + +// velodromeV2Facet = VelodromeV2Facet(payable(address(ldaDiamond))); +// } + +// // ============================ Velodrome V2 Tests ============================ + +// // no stable swap +// function test_CanSwap() public override { +// vm.startPrank(USER_SENDER); + +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(USER_SENDER), +// to: address(USER_SENDER), +// tokenIn: address(USDC_TOKEN), +// amountIn: 1_000 * 1e6, +// tokenOut: address(STG_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_NoStable_Reverse() public { +// // first perform the forward swap. +// test_CanSwap(); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(STG_TOKEN), +// amountIn: 500 * 1e18, +// tokenOut: address(USDC_TOKEN), +// stable: false, +// direction: SwapDirection.Token1ToToken0, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_Stable() public { +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(USDC_TOKEN), +// amountIn: 1_000 * 1e6, +// tokenOut: address(USDC_E_TOKEN), +// stable: true, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_Stable_Reverse() public { +// // first perform the forward stable swap. +// test_CanSwap_Stable(); + +// vm.startPrank(USER_SENDER); + +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(USDC_E_TOKEN), +// amountIn: 500 * 1e6, +// tokenOut: address(USDC_TOKEN), +// stable: false, +// direction: SwapDirection.Token1ToToken0, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // fund dex aggregator contract so that the contract holds USDC +// deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(ldaDiamond), +// to: address(USER_SENDER), +// tokenIn: address(USDC_TOKEN), +// amountIn: IERC20(address(USDC_TOKEN)).balanceOf( +// address(ldaDiamond) +// ) - 1, // adjust for slot undrain protection: subtract 1 token so that the +// // aggregator's balance isn't completely drained, matching the contract's safeguard +// tokenOut: address(USDC_E_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: false +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_FlashloanCallback() public { +// mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// VelodromeV2SwapTestParams({ +// from: address(USER_SENDER), +// to: address(mockFlashloanCallbackReceiver), +// tokenIn: address(USDC_TOKEN), +// amountIn: 1_000 * 1e6, +// tokenOut: address(USDC_E_TOKEN), +// stable: false, +// direction: SwapDirection.Token0ToToken1, +// callback: true +// }) +// ); +// vm.stopPrank(); +// } + +// // Override the abstract test with VelodromeV2 implementation +// function test_CanSwap_MultiHop() public override { +// vm.startPrank(USER_SENDER); + +// // Setup routes and get amounts +// MultiHopTestParams memory params = _setupRoutes( +// address(USDC_TOKEN), +// address(STG_TOKEN), +// address(USDC_E_TOKEN), +// false, +// false +// ); + +// // Get initial reserves BEFORE the swap +// ReserveState memory initialReserves; +// ( +// initialReserves.reserve0Pool1, +// initialReserves.reserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// initialReserves.reserve0Pool2, +// initialReserves.reserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( +// USER_SENDER +// ); + +// // Build route and execute swap +// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); + +// // Approve and execute +// IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); + +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// params.tokenIn, +// params.tokenOut, +// 1000 * 1e6, +// params.amounts2[1], +// params.amounts2[1] +// ); + +// coreRouteFacet.processRoute( +// params.tokenIn, +// 1000 * 1e6, +// params.tokenOut, +// params.amounts2[1], +// USER_SENDER, +// route +// ); + +// _verifyUserBalances(params, initialBalance1, initialBalance2); +// _verifyReserves(params, initialReserves); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop_WithStable() public { +// vm.startPrank(USER_SENDER); + +// // Setup routes and get amounts for stable->volatile path +// MultiHopTestParams memory params = _setupRoutes( +// address(USDC_TOKEN), +// address(USDC_E_TOKEN), +// address(STG_TOKEN), +// true, // stable pool for first hop +// false // volatile pool for second hop +// ); + +// // Get initial reserves BEFORE the swap +// ReserveState memory initialReserves; +// ( +// initialReserves.reserve0Pool1, +// initialReserves.reserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// initialReserves.reserve0Pool2, +// initialReserves.reserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// // Record initial balances +// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( +// USER_SENDER +// ); + +// // Build route and execute swap +// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + +// // Approve and execute +// IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); + +// // vm.expectEmit(true, true, true, true); +// // emit Route( +// // USER_SENDER, +// // USER_SENDER, +// // params.tokenIn, +// // params.tokenOut, +// // 1000 * 1e6, +// // params.amounts2[1], +// // params.amounts2[1] +// // ); + +// coreRouteFacet.processRoute( +// params.tokenIn, +// 1000 * 1e6, +// params.tokenOut, +// params.amounts2[1], +// USER_SENDER, +// route +// ); + +// _verifyUserBalances(params, initialBalance1, initialBalance2); +// _verifyReserves(params, initialReserves); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidPoolOrRecipient() public { +// vm.startPrank(USER_SENDER); + +// // Get a valid pool address first for comparison +// address validPool = VELODROME_V2_ROUTER.poolFor( +// address(USDC_TOKEN), +// address(STG_TOKEN), +// false, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// // --- Test case 1: Zero pool address --- +// // 1. Create the specific swap data blob +// bytes memory swapDataZeroPool = abi.encodePacked( +// VelodromeV2Facet.swapVelodromeV2.selector, +// address(0), // Invalid pool +// uint8(SwapDirection.Token1ToToken0), +// USER_SENDER, +// uint8(CallbackStatus.Disabled) +// ); + +// // 2. Create the full route with the length-prefixed swap data +// bytes memory routeWithZeroPool = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(USDC_TOKEN), +// uint8(1), +// FULL_SHARE, +// uint16(swapDataZeroPool.length), // Length prefix +// swapDataZeroPool +// ); + +// IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(USDC_TOKEN), +// 1000 * 1e6, +// address(STG_TOKEN), +// 0, +// USER_SENDER, +// routeWithZeroPool +// ); + +// // --- Test case 2: Zero recipient address --- +// bytes memory swapDataZeroRecipient = abi.encodePacked( +// VelodromeV2Facet.swapVelodromeV2.selector, +// validPool, +// uint8(SwapDirection.Token1ToToken0), +// address(0), // Invalid recipient +// uint8(CallbackStatus.Disabled) +// ); + +// bytes memory routeWithZeroRecipient = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(USDC_TOKEN), +// uint8(1), +// FULL_SHARE, +// uint16(swapDataZeroRecipient.length), // Length prefix +// swapDataZeroRecipient +// ); + +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(USDC_TOKEN), +// 1000 * 1e6, +// address(STG_TOKEN), +// 0, +// USER_SENDER, +// routeWithZeroRecipient +// ); + +// vm.stopPrank(); +// } + +// function testRevert_WrongPoolReserves() public { +// vm.startPrank(USER_SENDER); + +// // Setup multi-hop route: USDC -> STG -> USDC.e +// MultiHopTestParams memory params = _setupRoutes( +// address(USDC_TOKEN), +// address(STG_TOKEN), +// address(USDC_E_TOKEN), +// false, +// false +// ); + +// // Build multi-hop route +// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); + +// deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); + +// IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); + +// // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves +// vm.mockCall( +// params.pool2, +// abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), +// abi.encode(0, 0, block.timestamp) +// ); + +// vm.expectRevert(WrongPoolReserves.selector); + +// coreRouteFacet.processRoute( +// address(USDC_TOKEN), +// 1000 * 1e6, +// address(USDC_E_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// // ============================ Velodrome V2 Helper Functions ============================ + +// /** +// * @dev Helper function to test a VelodromeV2 swap. +// * Uses a struct to group parameters and reduce stack depth. +// */ +// function _testSwap(VelodromeV2SwapTestParams memory params) internal { +// // get expected output amounts from the router. +// IVelodromeV2Router.Route[] +// memory routes = new IVelodromeV2Router.Route[](1); +// routes[0] = IVelodromeV2Router.Route({ +// from: params.tokenIn, +// to: params.tokenOut, +// stable: params.stable, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( +// params.amountIn, +// routes +// ); +// emit log_named_uint("Expected amount out", amounts[1]); + +// // Retrieve the pool address. +// address pool = VELODROME_V2_ROUTER.poolFor( +// params.tokenIn, +// params.tokenOut, +// params.stable, +// VELODROME_V2_FACTORY_REGISTRY +// ); +// emit log_named_uint("Pool address:", uint256(uint160(pool))); + +// // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. +// CommandType commandCode = params.from == address(ldaDiamond) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; + +// // 1. Pack the data for the specific swap FIRST +// bytes memory swapData = abi.encodePacked( +// VelodromeV2Facet.swapVelodromeV2.selector, +// pool, +// params.direction, +// params.to, +// params.callback +// ? uint8(CallbackStatus.Enabled) +// : uint8(CallbackStatus.Disabled) +// ); +// // build the route. +// bytes memory route = abi.encodePacked( +// uint8(commandCode), +// params.tokenIn, +// uint8(1), // num splits +// FULL_SHARE, +// uint16(swapData.length), // <--- Add length prefix +// swapData +// ); + +// // approve the aggregator to spend tokenIn. +// IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); + +// // capture initial token balances. +// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); +// emit log_named_uint("Initial tokenIn balance", initialTokenIn); + +// address from = params.from == address(ldaDiamond) +// ? USER_SENDER +// : params.from; +// if (params.callback == true) { +// vm.expectEmit(true, false, false, false); +// emit HookCalled( +// address(ldaDiamond), +// 0, +// 0, +// abi.encode(params.tokenIn) +// ); +// } +// vm.expectEmit(true, true, true, true); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// amounts[1], +// amounts[1] +// ); + +// // execute the swap +// coreRouteFacet.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// amounts[1], +// params.to, +// route +// ); + +// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); +// emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); +// emit log_named_uint( +// "TokenOut received", +// finalTokenOut - initialTokenOut +// ); + +// assertApproxEqAbs( +// initialTokenIn - finalTokenIn, +// params.amountIn, +// 1, // 1 wei tolerance +// "TokenIn amount mismatch" +// ); +// assertEq( +// finalTokenOut - initialTokenOut, +// amounts[1], +// "TokenOut amount mismatch" +// ); +// } + +// // Helper function to set up routes and get amounts +// function _setupRoutes( +// address tokenIn, +// address tokenMid, +// address tokenOut, +// bool isStableFirst, +// bool isStableSecond +// ) private view returns (MultiHopTestParams memory params) { +// params.tokenIn = tokenIn; +// params.tokenMid = tokenMid; +// params.tokenOut = tokenOut; + +// // Setup first hop route +// IVelodromeV2Router.Route[] +// memory routes1 = new IVelodromeV2Router.Route[](1); +// routes1[0] = IVelodromeV2Router.Route({ +// from: tokenIn, +// to: tokenMid, +// stable: isStableFirst, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( +// 1000 * 1e6, +// routes1 +// ); + +// // Setup second hop route +// IVelodromeV2Router.Route[] +// memory routes2 = new IVelodromeV2Router.Route[](1); +// routes2[0] = IVelodromeV2Router.Route({ +// from: tokenMid, +// to: tokenOut, +// stable: isStableSecond, +// factory: address(VELODROME_V2_FACTORY_REGISTRY) +// }); +// params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( +// params.amounts1[1], +// routes2 +// ); + +// // Get pool addresses +// params.pool1 = VELODROME_V2_ROUTER.poolFor( +// tokenIn, +// tokenMid, +// isStableFirst, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// params.pool2 = VELODROME_V2_ROUTER.poolFor( +// tokenMid, +// tokenOut, +// isStableSecond, +// VELODROME_V2_FACTORY_REGISTRY +// ); + +// // Get pool fees info +// params.pool1Fee = IVelodromeV2PoolFactory( +// VELODROME_V2_FACTORY_REGISTRY +// ).getFee(params.pool1, isStableFirst); +// params.pool2Fee = IVelodromeV2PoolFactory( +// VELODROME_V2_FACTORY_REGISTRY +// ).getFee(params.pool2, isStableSecond); + +// return params; +// } + +// // function to build first hop of the route +// function _buildFirstHop( +// address pool1, +// address pool2, // The recipient of the first hop is the next pool +// uint8 direction +// ) private pure returns (bytes memory) { +// return +// abi.encodePacked( +// VelodromeV2Facet.swapVelodromeV2.selector, +// pool1, +// direction, +// pool2, // Send intermediate tokens to the next pool for the second hop +// uint8(CallbackStatus.Disabled) +// ); +// } + +// // function to build second hop of the route +// function _buildSecondHop( +// address pool2, +// address recipient, +// uint8 direction +// ) private pure returns (bytes memory) { +// return +// abi.encodePacked( +// VelodromeV2Facet.swapVelodromeV2.selector, +// pool2, +// direction, +// recipient, // Final recipient +// uint8(CallbackStatus.Disabled) +// ); +// } + +// // route building function +// function _buildMultiHopRoute( +// MultiHopTestParams memory params, +// address recipient, +// uint8 firstHopDirection, +// uint8 secondHopDirection +// ) private pure returns (bytes memory) { +// // 1. Get the specific data for each hop +// bytes memory firstHopData = _buildFirstHop( +// params.pool1, +// params.pool2, +// firstHopDirection +// ); + +// bytes memory secondHopData = _buildSecondHop( +// params.pool2, +// recipient, +// secondHopDirection +// ); + +// // 2. Assemble the first command +// bytes memory firstCommand = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// params.tokenIn, +// uint8(1), // num splits +// FULL_SHARE, +// uint16(firstHopData.length), // <--- Add length prefix +// firstHopData +// ); + +// // 3. Assemble the second command +// // The second hop takes tokens already held by the diamond, so we use ProcessOnePool +// bytes memory secondCommand = abi.encodePacked( +// uint8(CommandType.ProcessOnePool), +// params.tokenMid, +// uint16(secondHopData.length), // <--- Add length prefix +// secondHopData +// ); + +// // 4. Concatenate the commands to create the final route +// return bytes.concat(firstCommand, secondCommand); +// } + +// function _verifyUserBalances( +// MultiHopTestParams memory params, +// uint256 initialBalance1, +// uint256 initialBalance2 +// ) private { +// // Verify token balances +// uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// assertApproxEqAbs( +// initialBalance1 - finalBalance1, +// 1000 * 1e6, +// 1, // 1 wei tolerance +// "Token1 spent amount mismatch" +// ); +// assertEq( +// finalBalance2 - initialBalance2, +// params.amounts2[1], +// "Token2 received amount mismatch" +// ); +// } + +// function _verifyReserves( +// MultiHopTestParams memory params, +// ReserveState memory initialReserves +// ) private { +// // Get reserves after swap +// ( +// uint256 finalReserve0Pool1, +// uint256 finalReserve1Pool1, + +// ) = IVelodromeV2Pool(params.pool1).getReserves(); +// ( +// uint256 finalReserve0Pool2, +// uint256 finalReserve1Pool2, + +// ) = IVelodromeV2Pool(params.pool2).getReserves(); + +// address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); +// address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); + +// // Calculate exact expected changes +// uint256 amountInAfterFees = 1000 * +// 1e6 - +// ((1000 * 1e6 * params.pool1Fee) / 10000); + +// // Assert exact reserve changes for Pool1 +// if (token0Pool1 == params.tokenIn) { +// // tokenIn is token0, so reserve0 should increase and reserve1 should decrease +// assertEq( +// finalReserve0Pool1 - initialReserves.reserve0Pool1, +// amountInAfterFees, +// "Pool1 reserve0 (tokenIn) change incorrect" +// ); +// assertEq( +// initialReserves.reserve1Pool1 - finalReserve1Pool1, +// params.amounts1[1], +// "Pool1 reserve1 (tokenMid) change incorrect" +// ); +// } else { +// // tokenIn is token1, so reserve1 should increase and reserve0 should decrease +// assertEq( +// finalReserve1Pool1 - initialReserves.reserve1Pool1, +// amountInAfterFees, +// "Pool1 reserve1 (tokenIn) change incorrect" +// ); +// assertEq( +// initialReserves.reserve0Pool1 - finalReserve0Pool1, +// params.amounts1[1], +// "Pool1 reserve0 (tokenMid) change incorrect" +// ); +// } + +// // Assert exact reserve changes for Pool2 +// if (token0Pool2 == params.tokenMid) { +// // tokenMid is token0, so reserve0 should increase and reserve1 should decrease +// assertEq( +// finalReserve0Pool2 - initialReserves.reserve0Pool2, +// params.amounts1[1] - +// ((params.amounts1[1] * params.pool2Fee) / 10000), +// "Pool2 reserve0 (tokenMid) change incorrect" +// ); +// assertEq( +// initialReserves.reserve1Pool2 - finalReserve1Pool2, +// params.amounts2[1], +// "Pool2 reserve1 (tokenOut) change incorrect" +// ); +// } else { +// // tokenMid is token1, so reserve1 should increase and reserve0 should decrease +// assertEq( +// finalReserve1Pool2 - initialReserves.reserve1Pool2, +// params.amounts1[1] - +// ((params.amounts1[1] * params.pool2Fee) / 10000), +// "Pool2 reserve1 (tokenMid) change incorrect" +// ); +// assertEq( +// initialReserves.reserve0Pool2 - finalReserve0Pool2, +// params.amounts2[1], +// "Pool2 reserve0 (tokenOut) change incorrect" +// ); +// } +// } +// } + +// contract AlgebraLiquidityAdderHelper { +// address public immutable TOKEN_0; +// address public immutable TOKEN_1; + +// constructor(address _token0, address _token1) { +// TOKEN_0 = _token0; +// TOKEN_1 = _token1; +// } + +// function addLiquidity( +// address pool, +// int24 bottomTick, +// int24 topTick, +// uint128 amount +// ) +// external +// returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) +// { +// // Get balances before +// uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Call mint +// (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( +// address(this), +// address(this), +// bottomTick, +// topTick, +// amount, +// abi.encode(TOKEN_0, TOKEN_1) +// ); + +// // Get balances after to account for fees +// uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Calculate actual amounts transferred accounting for fees +// amount0 = balance0Before - balance0After; +// amount1 = balance1Before - balance1After; + +// return (amount0, amount1, liquidityActual); +// } + +// function algebraMintCallback( +// uint256 amount0Owed, +// uint256 amount1Owed, +// bytes calldata +// ) external { +// // Check token balances +// uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); +// uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); + +// // Transfer what we can, limited by actual balance +// if (amount0Owed > 0) { +// uint256 amount0ToSend = amount0Owed > balance0 +// ? balance0 +// : amount0Owed; +// uint256 balance0Before = IERC20(TOKEN_0).balanceOf( +// address(msg.sender) +// ); +// IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); +// uint256 balance0After = IERC20(TOKEN_0).balanceOf( +// address(msg.sender) +// ); +// // solhint-disable-next-line gas-custom-errors +// require(balance0After > balance0Before, "Transfer failed"); +// } + +// if (amount1Owed > 0) { +// uint256 amount1ToSend = amount1Owed > balance1 +// ? balance1 +// : amount1Owed; +// uint256 balance1Before = IERC20(TOKEN_1).balanceOf( +// address(msg.sender) +// ); +// IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); +// uint256 balance1After = IERC20(TOKEN_1).balanceOf( +// address(msg.sender) +// ); +// // solhint-disable-next-line gas-custom-errors +// require(balance1After > balance1Before, "Transfer failed"); +// } +// } +// } + +// /** +// * @title Algebra tests +// * @notice Tests specific to Algebra +// */ +// contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { +// AlgebraFacet private algebraFacet; + +// address private constant APE_ETH_TOKEN = +// 0xcF800F4948D16F23333508191B1B1591daF70438; +// address private constant WETH_TOKEN = +// 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; +// address private constant ALGEBRA_FACTORY_APECHAIN = +// 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; +// address private constant ALGEBRA_QUOTER_V2_APECHAIN = +// 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; +// address private constant ALGEBRA_POOL_APECHAIN = +// 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; +// address private constant APE_ETH_HOLDER_APECHAIN = +// address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); + +// address private constant IMPOSSIBLE_POOL_ADDRESS = +// 0x0000000000000000000000000000000000000001; + +// struct AlgebraSwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// SwapDirection direction; +// bool supportsFeeOnTransfer; +// } + +// error AlgebraSwapUnexpected(); + +// function setUp() public override { +// setupApechain(); +// super.setUp(); +// } + +// function _addDexFacet() internal override { +// algebraFacet = new AlgebraFacet(); +// bytes4[] memory functionSelectors = new bytes4[](2); +// functionSelectors[0] = algebraFacet.swapAlgebra.selector; +// functionSelectors[1] = algebraFacet.algebraSwapCallback.selector; +// addFacet( +// address(ldaDiamond), +// address(algebraFacet), +// functionSelectors +// ); + +// algebraFacet = AlgebraFacet(payable(address(ldaDiamond))); +// } + +// // Override the abstract test with Algebra implementation +// function test_CanSwap_FromDexAggregator() public override { +// // Fund LDA from whale address +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(address(coreRouteFacet), 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: address(coreRouteFacet), +// to: address(USER_SENDER), +// tokenIn: APE_ETH_TOKEN, +// amountIn: IERC20(APE_ETH_TOKEN).balanceOf( +// address(coreRouteFacet) +// ) - 1, +// tokenOut: address(WETH_TOKEN), +// direction: SwapDirection.Token0ToToken1, +// supportsFeeOnTransfer: true +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FeeOnTransferToken() public { +// setupApechain(); + +// uint256 amountIn = 534451326669177; +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); + +// vm.startPrank(APE_ETH_HOLDER_APECHAIN); + +// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), amountIn); + +// // Build route for algebra swap with command code 2 (user funds) +// bytes memory swapData = _buildAlgebraSwapData( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: APE_ETH_HOLDER_APECHAIN, +// pool: ALGEBRA_POOL_APECHAIN, +// supportsFeeOnTransfer: true +// }) +// ); + +// // 2. Build the final route with the command and length-prefixed swapData +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// APE_ETH_TOKEN, +// uint8(1), // number of pools/splits +// FULL_SHARE, // 100% share +// uint16(swapData.length), // <--- Add the length prefix +// swapData +// ); + +// // Track initial balance +// uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( +// APE_ETH_HOLDER_APECHAIN +// ); + +// // Execute the swap +// coreRouteFacet.processRoute( +// APE_ETH_TOKEN, +// amountIn, +// WETH_TOKEN, +// 0, // minOut = 0 for this test +// APE_ETH_HOLDER_APECHAIN, +// route +// ); + +// // Verify balances +// uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( +// APE_ETH_HOLDER_APECHAIN +// ); +// assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); + +// vm.stopPrank(); +// } + +// function test_CanSwap() public override { +// vm.startPrank(APE_ETH_HOLDER_APECHAIN); + +// // Transfer tokens from whale to USER_SENDER +// uint256 amountToTransfer = 100 * 1e18; +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); + +// vm.stopPrank(); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: APE_ETH_TOKEN, +// amountIn: 10 * 1e18, +// tokenOut: address(WETH_TOKEN), +// direction: SwapDirection.Token0ToToken1, +// supportsFeeOnTransfer: true +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_Reverse() public { +// test_CanSwap(); + +// vm.startPrank(USER_SENDER); + +// _testAlgebraSwap( +// AlgebraSwapTestParams({ +// from: USER_SENDER, +// to: USER_SENDER, +// tokenIn: address(WETH_TOKEN), +// amountIn: 5 * 1e18, +// tokenOut: APE_ETH_TOKEN, +// direction: SwapDirection.Token1ToToken0, +// supportsFeeOnTransfer: false +// }) +// ); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { +// MultiHopTestState memory state; +// state.isFeeOnTransfer = true; + +// // Setup tokens and pools +// state = _setupTokensAndPools(state); + +// // Execute and verify swap +// _executeAndVerifyMultiHopSwap(state); +// } + +// function test_CanSwap_MultiHop() public override { +// MultiHopTestState memory state; +// state.isFeeOnTransfer = false; + +// // Setup tokens and pools +// state = _setupTokensAndPools(state); + +// // Execute and verify swap +// _executeAndVerifyMultiHopSwap(state); +// } + +// // Test that the proper error is thrown when algebra swap fails +// function testRevert_SwapUnexpected() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Create invalid pool address +// address invalidPool = address(0x999); + +// // Mock token0() call on invalid pool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Create a route with an invalid pool +// bytes memory swapData = _buildAlgebraSwapData( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: invalidPool, +// supportsFeeOnTransfer: true +// }) +// ); + +// bytes memory invalidRoute = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// APE_ETH_TOKEN, +// uint8(1), // number of pools/splits +// FULL_SHARE, // 100% share +// uint16(swapData.length), // <--- Add the length prefix +// swapData +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + +// // Mock the algebra pool to not reset lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSelector( +// IAlgebraPool.swapSupportingFeeOnInputTokens.selector +// ), +// abi.encode(0, 0) +// ); + +// // Expect the AlgebraSwapUnexpected error +// vm.expectRevert(AlgebraSwapUnexpected.selector); + +// coreRouteFacet.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// invalidRoute +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// // Helper function to setup tokens and pools +// function _setupTokensAndPools( +// MultiHopTestState memory state +// ) private returns (MultiHopTestState memory) { +// // Create tokens +// ERC20 tokenA = new ERC20( +// "Token A", +// state.isFeeOnTransfer ? "FTA" : "TA", +// 18 +// ); +// IERC20 tokenB; +// ERC20 tokenC = new ERC20( +// "Token C", +// state.isFeeOnTransfer ? "FTC" : "TC", +// 18 +// ); + +// if (state.isFeeOnTransfer) { +// tokenB = IERC20( +// address( +// new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) +// ) +// ); +// } else { +// tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); +// } + +// state.tokenA = IERC20(address(tokenA)); +// state.tokenB = tokenB; +// state.tokenC = IERC20(address(tokenC)); + +// // Label addresses +// vm.label(address(state.tokenA), "Token A"); +// vm.label(address(state.tokenB), "Token B"); +// vm.label(address(state.tokenC), "Token C"); + +// // Mint initial token supplies +// tokenA.mint(address(this), 1_000_000 * 1e18); +// if (!state.isFeeOnTransfer) { +// ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); +// } else { +// MockFeeOnTransferToken(address(tokenB)).mint( +// address(this), +// 1_000_000 * 1e18 +// ); +// } +// tokenC.mint(address(this), 1_000_000 * 1e18); + +// // Create pools +// state.pool1 = _createAlgebraPool( +// address(state.tokenA), +// address(state.tokenB) +// ); +// state.pool2 = _createAlgebraPool( +// address(state.tokenB), +// address(state.tokenC) +// ); + +// vm.label(state.pool1, "Pool 1"); +// vm.label(state.pool2, "Pool 2"); + +// // Add liquidity +// _addLiquidityToPool( +// state.pool1, +// address(state.tokenA), +// address(state.tokenB) +// ); +// _addLiquidityToPool( +// state.pool2, +// address(state.tokenB), +// address(state.tokenC) +// ); + +// state.amountToTransfer = 100 * 1e18; +// state.amountIn = 50 * 1e18; + +// // Transfer tokens to USER_SENDER +// IERC20(address(state.tokenA)).transfer( +// USER_SENDER, +// state.amountToTransfer +// ); + +// return state; +// } + +// // Helper function to execute and verify the swap +// function _executeAndVerifyMultiHopSwap( +// MultiHopTestState memory state +// ) private { +// vm.startPrank(USER_SENDER); + +// uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( +// USER_SENDER +// ); + +// // Approve spending +// IERC20(address(state.tokenA)).approve( +// address(ldaDiamond), +// state.amountIn +// ); + +// // Build route +// bytes memory route = _buildMultiHopRouteForTest(state); + +// // Execute swap +// coreRouteFacet.processRoute( +// address(state.tokenA), +// state.amountIn, +// address(state.tokenC), +// 0, // No minimum amount out for testing +// USER_SENDER, +// route +// ); + +// // Verify results +// _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); + +// vm.stopPrank(); +// } + +// // Helper function to build the multi-hop route for test +// function _buildMultiHopRouteForTest( +// MultiHopTestState memory state +// ) private view returns (bytes memory) { +// // 1. Get the specific data payload for each hop +// bytes memory firstHopData = _buildAlgebraSwapData( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: address(state.tokenA), +// recipient: address(ldaDiamond), // Hop 1 sends to the contract itself +// pool: state.pool1, +// supportsFeeOnTransfer: false +// }) +// ); + +// bytes memory secondHopData = _buildAlgebraSwapData( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessMyERC20, +// tokenIn: address(state.tokenB), +// recipient: USER_SENDER, // Hop 2 sends to the final user +// pool: state.pool2, +// supportsFeeOnTransfer: state.isFeeOnTransfer +// }) +// ); + +// // 2. Assemble the first full command with its length prefix +// bytes memory firstCommand = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// state.tokenA, +// uint8(1), +// FULL_SHARE, +// uint16(firstHopData.length), +// firstHopData +// ); + +// // 3. Assemble the second full command with its length prefix +// bytes memory secondCommand = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// state.tokenB, +// uint8(1), // num splits for the second hop +// FULL_SHARE, // full share for the second hop +// uint16(secondHopData.length), +// secondHopData +// ); + +// // 4. Concatenate the commands to create the final route +// return bytes.concat(firstCommand, secondCommand); +// } + +// // Helper function to verify multi-hop results +// function _verifyMultiHopResults( +// MultiHopTestState memory state, +// uint256 initialBalanceA, +// uint256 initialBalanceC +// ) private { +// uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( +// USER_SENDER +// ); +// uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( +// USER_SENDER +// ); + +// assertApproxEqAbs( +// initialBalanceA - finalBalanceA, +// state.amountIn, +// 1, // 1 wei tolerance +// "TokenA spent amount mismatch" +// ); +// assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); + +// emit log_named_uint( +// state.isFeeOnTransfer +// ? "Output amount with fee tokens" +// : "Output amount with regular tokens", +// finalBalanceC - initialBalanceC +// ); +// } + +// // Helper function to create an Algebra pool +// function _createAlgebraPool( +// address tokenA, +// address tokenB +// ) internal returns (address pool) { +// // Call the actual Algebra factory to create a pool +// pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( +// tokenA, +// tokenB +// ); +// return pool; +// } + +// // Helper function to add liquidity to a pool +// function _addLiquidityToPool( +// address pool, +// address token0, +// address token1 +// ) internal { +// // For fee-on-transfer tokens, we need to send more to account for the fee +// // We'll use a small amount and send extra to cover fees +// uint256 initialAmount0 = 1e17; // 0.1 token +// uint256 initialAmount1 = 1e17; // 0.1 token + +// // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) +// uint256 transferAmount0 = (initialAmount0 * 110) / 100; +// uint256 transferAmount1 = (initialAmount1 * 110) / 100; + +// // Initialize with 1:1 price ratio (Q64.96 format) +// uint160 initialPrice = uint160(1 << 96); +// IAlgebraPool(pool).initialize(initialPrice); + +// // Create AlgebraLiquidityAdderHelper with safe transfer logic +// AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( +// token0, +// token1 +// ); + +// // Transfer tokens with extra amounts to account for fees +// IERC20(token0).transfer( +// address(algebraLiquidityAdderHelper), +// transferAmount0 +// ); +// IERC20(token1).transfer( +// address(algebraLiquidityAdderHelper), +// transferAmount1 +// ); + +// // Get actual balances to use for liquidity, accounting for any fees +// uint256 actualBalance0 = IERC20(token0).balanceOf( +// address(algebraLiquidityAdderHelper) +// ); +// uint256 actualBalance1 = IERC20(token1).balanceOf( +// address(algebraLiquidityAdderHelper) +// ); + +// // Use the smaller of the two balances for liquidity amount +// uint128 liquidityAmount = uint128( +// actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 +// ); + +// // Add liquidity using the actual token amounts we have +// algebraLiquidityAdderHelper.addLiquidity( +// pool, +// -887220, +// 887220, +// liquidityAmount / 2 // Use half of available liquidity to ensure success +// ); +// } + +// struct MultiHopTestState { +// IERC20 tokenA; +// IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken +// IERC20 tokenC; +// address pool1; +// address pool2; +// uint256 amountIn; +// uint256 amountToTransfer; +// bool isFeeOnTransfer; +// } + +// struct AlgebraRouteParams { +// CommandType commandCode; // 1 for contract funds, 2 for user funds +// address tokenIn; // Input token address +// address recipient; // Address receiving the output tokens +// address pool; // Algebra pool address +// bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens +// } + +// // Helper function to build route for Apechain Algebra swap +// function _buildAlgebraSwapData( +// AlgebraRouteParams memory params +// ) private view returns (bytes memory) { +// address token0 = IAlgebraPool(params.pool).token0(); +// bool zeroForOne = (params.tokenIn == token0); +// SwapDirection direction = zeroForOne +// ? SwapDirection.Token0ToToken1 +// : SwapDirection.Token1ToToken0; + +// // This data blob is what the AlgebraFacet will receive and parse +// return +// abi.encodePacked( +// AlgebraFacet.swapAlgebra.selector, +// params.pool, +// uint8(direction), +// params.recipient, +// params.supportsFeeOnTransfer ? uint8(1) : uint8(0) +// ); +// } + +// // Helper function to test an Algebra swap +// function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { +// // Find or create a pool +// address pool = _getPool(params.tokenIn, params.tokenOut); +// vm.label(pool, "AlgebraPool"); + +// // Get token0 from pool for labeling +// address token0 = IAlgebraPool(pool).token0(); +// if (params.tokenIn == token0) { +// vm.label( +// params.tokenIn, +// string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") +// ); +// vm.label( +// params.tokenOut, +// string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") +// ); +// } else { +// vm.label( +// params.tokenIn, +// string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") +// ); +// vm.label( +// params.tokenOut, +// string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") +// ); +// } + +// // Record initial balances +// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + +// // Get expected output from QuoterV2 +// uint256 expectedOutput = _getQuoteExactInput( +// params.tokenIn, +// params.tokenOut, +// params.amountIn +// ); + +// // 1. Pack the specific data for this swap +// bytes memory swapData = _buildAlgebraSwapData( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper +// tokenIn: params.tokenIn, +// recipient: params.to, +// pool: pool, +// supportsFeeOnTransfer: params.supportsFeeOnTransfer +// }) +// ); + +// // 2. Approve tokens +// IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); + +// // 3. Set up event expectations +// address fromAddress = params.from == address(coreRouteFacet) +// ? USER_SENDER +// : params.from; + +// vm.expectEmit(true, true, true, false); +// emit Route( +// fromAddress, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// expectedOutput, +// expectedOutput +// ); + +// // 4. Build the route inline and execute the swap to save stack space +// coreRouteFacet.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// (expectedOutput * 995) / 1000, // minOut calculated inline +// params.to, +// abi.encodePacked( +// uint8( +// params.from == address(coreRouteFacet) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20 +// ), +// params.tokenIn, +// uint8(1), +// FULL_SHARE, +// uint16(swapData.length), +// swapData +// ) +// ); + +// // 5. Verify final balances +// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); + +// assertApproxEqAbs( +// initialTokenIn - finalTokenIn, +// params.amountIn, +// 1, +// "TokenIn amount mismatch" +// ); +// assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); +// } + +// function _getPool( +// address tokenA, +// address tokenB +// ) private view returns (address pool) { +// pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( +// tokenA, +// tokenB +// ); +// if (pool == address(0)) revert PoolDoesNotExist(); +// return pool; +// } + +// function _getQuoteExactInput( +// address tokenIn, +// address tokenOut, +// uint256 amountIn +// ) private returns (uint256 amountOut) { +// (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) +// .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); +// return amountOut; +// } + +// function testRevert_AlgebraSwap_ZeroAddressPool() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on address(0) +// vm.mockCall( +// address(0), +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with address(0) as pool +// bytes memory swapData = _buildAlgebraSwapData( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: USER_SENDER, +// pool: address(0), // Zero address pool +// supportsFeeOnTransfer: true +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// APE_ETH_TOKEN, +// uint8(1), // number of pools/splits +// FULL_SHARE, // 100% share +// uint16(swapData.length), // <--- Add the length prefix +// swapData +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// coreRouteFacet.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { +// // // Transfer tokens from whale to user +// // vm.prank(APE_ETH_HOLDER_APECHAIN); +// // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// // vm.startPrank(USER_SENDER); + +// // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS +// // vm.mockCall( +// // IMPOSSIBLE_POOL_ADDRESS, +// // abi.encodeWithSelector(IAlgebraPool.token0.selector), +// // abi.encode(APE_ETH_TOKEN) +// // ); + +// // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool +// // bytes memory swapData = _buildAlgebraSwapData( +// // AlgebraRouteParams({ +// // commandCode: CommandType.ProcessUserERC20, +// // tokenIn: APE_ETH_TOKEN, +// // recipient: USER_SENDER, +// // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address +// // supportsFeeOnTransfer: true +// // }) +// // ); + +// // bytes memory route = abi.encodePacked( +// // uint8(CommandType.ProcessUserERC20), +// // APE_ETH_TOKEN, +// // uint8(1), // number of pools/splits +// // FULL_SHARE, // 100% share +// // uint16(swapData.length), // <--- Add the length prefix +// // swapData +// // ); + +// // // Approve tokens +// // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + +// // // Expect revert with InvalidCallData +// // vm.expectRevert(InvalidCallData.selector); + +// // coreRouteFacet.processRoute( +// // APE_ETH_TOKEN, +// // 1 * 1e18, +// // address(WETH_TOKEN), +// // 0, +// // USER_SENDER, +// // route +// // ); + +// // vm.stopPrank(); +// // vm.clearMockedCalls(); +// // } + +// function testRevert_AlgebraSwap_ZeroAddressRecipient() public { +// // Transfer tokens from whale to user +// vm.prank(APE_ETH_HOLDER_APECHAIN); +// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + +// vm.startPrank(USER_SENDER); + +// // Mock token0() call on the pool +// vm.mockCall( +// ALGEBRA_POOL_APECHAIN, +// abi.encodeWithSelector(IAlgebraPool.token0.selector), +// abi.encode(APE_ETH_TOKEN) +// ); + +// // Build route with address(0) as recipient +// bytes memory swapData = _buildAlgebraSwapData( +// AlgebraRouteParams({ +// commandCode: CommandType.ProcessUserERC20, +// tokenIn: APE_ETH_TOKEN, +// recipient: address(0), // Zero address recipient +// pool: ALGEBRA_POOL_APECHAIN, +// supportsFeeOnTransfer: true +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// APE_ETH_TOKEN, +// uint8(1), // number of pools/splits +// FULL_SHARE, // 100% share +// uint16(swapData.length), // <--- Add the length prefix +// swapData +// ); + +// // Approve tokens +// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); + +// coreRouteFacet.processRoute( +// APE_ETH_TOKEN, +// 1 * 1e18, +// address(WETH_TOKEN), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } +// } + +// /** +// * @title LiFiDexAggregatorIzumiV3UpgradeTest +// * @notice Tests specific to iZiSwap V3 selector +// */ +// contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { +// IzumiV3Facet private izumiV3Facet; + +// // ==================== iZiSwap V3 specific variables ==================== +// // Base constants +// address internal constant USDC = +// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; +// address internal constant WETH = +// 0x4200000000000000000000000000000000000006; +// address internal constant USDB_C = +// 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; + +// // iZiSwap pools +// address internal constant IZUMI_WETH_USDC_POOL = +// 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; +// address internal constant IZUMI_WETH_USDB_C_POOL = +// 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; + +// // Test parameters +// uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals +// uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals + +// // structs +// struct IzumiV3SwapTestParams { +// address from; +// address to; +// address tokenIn; +// uint256 amountIn; +// address tokenOut; +// SwapDirection direction; +// } + +// struct MultiHopTestParams { +// address tokenIn; +// address tokenMid; +// address tokenOut; +// address pool1; +// address pool2; +// uint256 amountIn; +// SwapDirection direction1; +// SwapDirection direction2; +// } + +// error IzumiV3SwapUnexpected(); +// error IzumiV3SwapCallbackUnknownSource(); +// error IzumiV3SwapCallbackNotPositiveAmount(); + +// // Setup function for Base tests +// function setupBase() internal { +// customRpcUrlForForking = "ETH_NODE_URI_BASE"; +// customBlockNumberForForking = 29831758; +// } + +// function setUp() public override { +// setupBase(); +// super.setUp(); + +// deal(address(USDC), address(USER_SENDER), 1_000 * 1e6); +// } + +// function _addDexFacet() internal override { +// izumiV3Facet = new IzumiV3Facet(); +// bytes4[] memory functionSelectors = new bytes4[](3); +// functionSelectors[0] = izumiV3Facet.swapIzumiV3.selector; +// functionSelectors[1] = izumiV3Facet.swapX2YCallback.selector; +// functionSelectors[2] = izumiV3Facet.swapY2XCallback.selector; +// addFacet( +// address(ldaDiamond), +// address(izumiV3Facet), +// functionSelectors +// ); + +// izumiV3Facet = IzumiV3Facet(payable(address(ldaDiamond))); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // Test USDC -> WETH +// deal(USDC, address(coreRouteFacet), AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); +// _testSwap( +// IzumiV3SwapTestParams({ +// from: address(coreRouteFacet), +// to: USER_SENDER, +// tokenIn: USDC, +// amountIn: AMOUNT_USDC, +// tokenOut: WETH, +// direction: SwapDirection.Token1ToToken0 +// }) +// ); +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// _testMultiHopSwap( +// MultiHopTestParams({ +// tokenIn: USDC, +// tokenMid: WETH, +// tokenOut: USDB_C, +// pool1: IZUMI_WETH_USDC_POOL, +// pool2: IZUMI_WETH_USDB_C_POOL, +// amountIn: AMOUNT_USDC, +// direction1: SwapDirection.Token1ToToken0, +// direction2: SwapDirection.Token0ToToken1 +// }) +// ); +// } + +// function test_CanSwap() public override { +// deal(address(USDC), USER_SENDER, AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); +// IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); + +// bytes memory swapData = _buildIzumiV3SwapData( +// IzumiV3SwapParams({ +// pool: IZUMI_WETH_USDC_POOL, +// direction: SwapDirection.Token1ToToken0, +// recipient: USER_RECEIVER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// USDC, +// uint8(1), // number of pools/splits +// FULL_SHARE, // 100% share +// uint16(swapData.length), // length prefix +// swapData +// ); + +// vm.expectEmit(true, true, true, false); +// emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); + +// coreRouteFacet.processRoute( +// USDC, +// AMOUNT_USDC, +// WETH, +// 0, +// USER_RECEIVER, +// route +// ); + +// vm.stopPrank(); +// } + +// function testRevert_IzumiV3SwapUnexpected() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// vm.startPrank(USER_SENDER); + +// // create invalid pool address +// address invalidPool = address(0x999); + +// bytes memory swapData = _buildIzumiV3SwapData( +// IzumiV3SwapParams({ +// pool: invalidPool, +// direction: SwapDirection.Token1ToToken0, +// recipient: USER_SENDER +// }) +// ); + +// // create a route with an invalid pool +// bytes memory invalidRoute = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// USDC, +// uint8(1), // number of pools (1) +// FULL_SHARE, // 100% share +// uint16(swapData.length), // length prefix +// swapData +// ); + +// IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); + +// // mock the iZiSwap pool to return without updating lastCalledPool +// vm.mockCall( +// invalidPool, +// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), +// abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool +// ); + +// vm.expectRevert(IzumiV3SwapUnexpected.selector); + +// coreRouteFacet.processRoute( +// USDC, +// AMOUNT_USDC, +// WETH, +// 0, +// USER_SENDER, +// invalidRoute +// ); + +// vm.stopPrank(); +// vm.clearMockedCalls(); +// } + +// function testRevert_UnexpectedCallbackSender() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// // Set up the expected callback sender through the diamond +// vm.store( +// address(ldaDiamond), +// keccak256("com.lifi.lda.callbackmanager"), +// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) +// ); + +// // Try to call callback from a different address than expected +// address unexpectedCaller = address(0xdead); +// vm.prank(unexpectedCaller); +// vm.expectRevert( +// abi.encodeWithSelector( +// LibCallbackManager.UnexpectedCallbackSender.selector, +// unexpectedCaller, +// IZUMI_WETH_USDC_POOL +// ) +// ); +// izumiV3Facet.swapY2XCallback(1, 1, abi.encode(USDC)); +// } + +// function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { +// deal(USDC, USER_SENDER, AMOUNT_USDC); + +// // Set the expected callback sender through the diamond storage +// vm.store( +// address(ldaDiamond), +// keccak256("com.lifi.lda.callbackmanager"), +// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) +// ); + +// // try to call the callback with zero amount +// vm.prank(IZUMI_WETH_USDC_POOL); +// vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); +// izumiV3Facet.swapY2XCallback( +// 0, +// 0, // zero amount should trigger the error +// abi.encode(USDC) +// ); +// } + +// function testRevert_FailsIfAmountInIsTooLarge() public { +// deal(address(WETH), USER_SENDER, type(uint256).max); + +// vm.startPrank(USER_SENDER); +// IERC20(WETH).approve(address(ldaDiamond), type(uint256).max); + +// bytes memory swapData = _buildIzumiV3SwapData( +// IzumiV3SwapParams({ +// pool: IZUMI_WETH_USDC_POOL, +// direction: SwapDirection.Token0ToToken1, +// recipient: USER_RECEIVER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// WETH, +// uint8(1), // number of pools (1) +// FULL_SHARE, // 100% share +// uint16(swapData.length), // length prefix +// swapData +// ); + +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// WETH, +// type(uint216).max, +// USDC, +// 0, +// USER_RECEIVER, +// route +// ); + +// vm.stopPrank(); +// } + +// function _testSwap(IzumiV3SwapTestParams memory params) internal { +// // Fund the sender with tokens if not the contract itself +// if (params.from != address(coreRouteFacet)) { +// deal(params.tokenIn, params.from, params.amountIn); +// } + +// // Capture initial token balances +// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( +// params.from +// ); +// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( +// params.to +// ); + +// // Build the route based on the command type +// CommandType commandCode = params.from == address(coreRouteFacet) +// ? CommandType.ProcessMyERC20 +// : CommandType.ProcessUserERC20; + +// bytes memory swapData = _buildIzumiV3SwapData( +// IzumiV3SwapParams({ +// pool: IZUMI_WETH_USDC_POOL, +// direction: params.direction == SwapDirection.Token0ToToken1 +// ? SwapDirection.Token0ToToken1 +// : SwapDirection.Token1ToToken0, +// recipient: params.to +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(commandCode), +// params.tokenIn, +// uint8(1), // number of pools (1) +// FULL_SHARE, // 100% share +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Approve tokens if necessary +// if (params.from == USER_SENDER) { +// vm.startPrank(USER_SENDER); +// IERC20(params.tokenIn).approve( +// address(ldaDiamond), +// params.amountIn +// ); +// } + +// // Expect the Route event emission +// address from = params.from == address(coreRouteFacet) +// ? USER_SENDER +// : params.from; + +// vm.expectEmit(true, true, true, false); +// emit Route( +// from, +// params.to, +// params.tokenIn, +// params.tokenOut, +// params.amountIn, +// 0, // No minimum amount enforced in test +// 0 // Actual amount will be checked after the swap +// ); + +// // Execute the swap +// uint256 amountOut = coreRouteFacet.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// 0, // No minimum amount for testing +// params.to, +// route +// ); + +// if (params.from == USER_SENDER) { +// vm.stopPrank(); +// } + +// // Verify balances have changed correctly +// uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); +// uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); + +// assertApproxEqAbs( +// initialBalanceIn - finalBalanceIn, +// params.amountIn, +// 1, // 1 wei tolerance because of undrain protection for dex aggregator +// "TokenIn amount mismatch" +// ); +// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); +// assertEq( +// amountOut, +// finalBalanceOut - initialBalanceOut, +// "AmountOut mismatch" +// ); + +// emit log_named_uint("Amount In", params.amountIn); +// emit log_named_uint("Amount Out", amountOut); +// } + +// function _testMultiHopSwap(MultiHopTestParams memory params) internal { +// // Fund the sender with tokens +// deal(params.tokenIn, USER_SENDER, params.amountIn); + +// // Capture initial token balances +// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( +// USER_SENDER +// ); +// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( +// USER_SENDER +// ); + +// // Build first swap data +// bytes memory firstSwapData = _buildIzumiV3SwapData( +// IzumiV3SwapParams({ +// pool: params.pool1, +// direction: params.direction1, +// recipient: address(coreRouteFacet) +// }) +// ); + +// bytes memory firstHop = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// params.tokenIn, +// uint8(1), // number of pools/splits +// FULL_SHARE, // 100% share +// uint16(firstSwapData.length), // length prefix +// firstSwapData +// ); + +// // Build second swap data +// bytes memory secondSwapData = _buildIzumiV3SwapData( +// IzumiV3SwapParams({ +// pool: params.pool2, +// direction: params.direction2, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory secondHop = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// params.tokenMid, +// uint8(1), // number of pools/splits +// FULL_SHARE, // 100% share +// uint16(secondSwapData.length), // length prefix +// secondSwapData +// ); + +// // Combine into route +// bytes memory route = bytes.concat(firstHop, secondHop); + +// // Approve tokens +// vm.startPrank(USER_SENDER); +// IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); + +// // Execute the swap +// uint256 amountOut = coreRouteFacet.processRoute( +// params.tokenIn, +// params.amountIn, +// params.tokenOut, +// 0, // No minimum amount for testing +// USER_SENDER, +// route +// ); +// vm.stopPrank(); + +// // Verify balances have changed correctly +// uint256 finalBalanceIn; +// uint256 finalBalanceOut; + +// finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); +// finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); + +// assertEq( +// initialBalanceIn - finalBalanceIn, +// params.amountIn, +// "TokenIn amount mismatch" +// ); +// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); +// assertEq( +// amountOut, +// finalBalanceOut - initialBalanceOut, +// "AmountOut mismatch" +// ); +// } + +// function _buildIzumiV3Route( +// CommandType commandCode, +// address tokenIn, +// uint8 direction, +// address pool, +// address recipient +// ) internal pure returns (bytes memory) { +// return +// abi.encodePacked( +// uint8(commandCode), +// tokenIn, +// uint8(1), // number of pools (1) +// FULL_SHARE, // 100% share +// IzumiV3Facet.swapIzumiV3.selector, +// pool, +// uint8(direction), +// recipient +// ); +// } + +// function _buildIzumiV3MultiHopRoute( +// MultiHopTestParams memory params +// ) internal view returns (bytes memory) { +// // First hop: USER_ERC20 -> LDA +// bytes memory firstHop = _buildIzumiV3Route( +// CommandType.ProcessUserERC20, +// params.tokenIn, +// uint8(params.direction1), +// params.pool1, +// address(coreRouteFacet) +// ); + +// // Second hop: MY_ERC20 (LDA) -> pool2 +// bytes memory secondHop = _buildIzumiV3Route( +// CommandType.ProcessMyERC20, +// params.tokenMid, +// uint8(params.direction2), +// params.pool2, +// USER_SENDER // final recipient +// ); + +// // Combine the two hops +// return bytes.concat(firstHop, secondHop); +// } + +// struct IzumiV3SwapParams { +// address pool; +// SwapDirection direction; +// address recipient; +// } + +// function _buildIzumiV3SwapData( +// IzumiV3SwapParams memory params +// ) internal view returns (bytes memory) { +// return +// abi.encodePacked( +// izumiV3Facet.swapIzumiV3.selector, +// params.pool, +// uint8(params.direction), +// params.recipient +// ); +// } +// } + +// // ----------------------------------------------------------------------------- +// // HyperswapV3 on HyperEVM +// // ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorHyperswapV3UpgradeTest is +// LiFiDexAggregatorUpgradeTest +// { +// using SafeERC20 for IERC20; + +// UniV3StyleFacet internal uniV3StyleFacet; + +// /// @dev HyperswapV3 router on HyperEVM chain +// IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = +// IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); +// /// @dev HyperswapV3 quoter on HyperEVM chain +// IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = +// IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); + +// /// @dev a liquid USDT on HyperEVM +// IERC20 internal constant USDT0 = +// IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); +// /// @dev WHYPE on HyperEVM +// IERC20 internal constant WHYPE = +// IERC20(0x5555555555555555555555555555555555555555); + +// struct HyperswapV3Params { +// CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 +// address tokenIn; // Input token address +// address recipient; // Address receiving the output tokens +// address pool; // HyperswapV3 pool address +// bool zeroForOne; // Direction of the swap +// } + +// function setUp() public override { +// setupHyperEVM(); +// super.setUp(); + +// deal(address(USDT0), address(USER_SENDER), 1_000 * 1e6); +// } + +// function _addDexFacet() internal override { +// uniV3StyleFacet = new UniV3StyleFacet(); +// bytes4[] memory functionSelectors = new bytes4[](2); +// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; +// functionSelectors[1] = uniV3StyleFacet +// .hyperswapV3SwapCallback +// .selector; +// addFacet( +// address(ldaDiamond), +// address(uniV3StyleFacet), +// functionSelectors +// ); + +// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + +// deal(address(USDT0), USER_SENDER, amountIn); + +// // user approves +// vm.prank(USER_SENDER); +// USDT0.approve(address(ldaDiamond), amountIn); + +// // fetch the real pool and quote +// address pool = HYPERSWAP_FACTORY.getPool( +// address(USDT0), +// address(WHYPE), +// 3000 +// ); + +// // Create the params struct for quoting +// IHyperswapV3QuoterV2.QuoteExactInputSingleParams +// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ +// tokenIn: address(USDT0), +// tokenOut: address(WHYPE), +// amountIn: amountIn, +// fee: 3000, +// sqrtPriceLimitX96: 0 +// }); + +// // Get the quote using the struct +// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( +// params +// ); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: pool, +// direction: SwapDirection.Token1ToToken0, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(USDT0), +// uint8(1), // 1 pool +// FULL_SHARE, // FULL_SHARE +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // expect the Route event +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// address(USDT0), +// address(WHYPE), +// amountIn, +// quoted, +// quoted +// ); + +// // execute +// vm.prank(USER_SENDER); +// coreRouteFacet.processRoute( +// address(USDT0), +// amountIn, +// address(WHYPE), +// quoted, +// USER_SENDER, +// route +// ); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 + +// // Fund dex aggregator contract +// deal(address(USDT0), address(ldaDiamond), amountIn); + +// // fetch the real pool and quote +// address pool = HYPERSWAP_FACTORY.getPool( +// address(USDT0), +// address(WHYPE), +// 3000 +// ); + +// // Create the params struct for quoting +// IHyperswapV3QuoterV2.QuoteExactInputSingleParams +// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ +// tokenIn: address(USDT0), +// tokenOut: address(WHYPE), +// amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection +// fee: 3000, +// sqrtPriceLimitX96: 0 +// }); + +// // Get the quote using the struct +// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( +// params +// ); + +// // Build route using our helper function +// // bytes memory route = _buildHyperswapV3Route( +// // HyperswapV3Params({ +// // commandCode: CommandType.ProcessMyERC20, +// // tokenIn: address(USDT0), +// // recipient: USER_SENDER, +// // pool: pool, +// // zeroForOne: true // USDT0 < WHYPE +// // }) +// // ); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: pool, +// direction: SwapDirection.Token1ToToken0, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(USDT0), +// uint8(1), // number of pools (1) +// FULL_SHARE, // 100% share +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // expect the Route event +// vm.expectEmit(true, true, true, true); +// emit Route( +// USER_SENDER, +// USER_SENDER, +// address(USDT0), +// address(WHYPE), +// amountIn - 1, // Account for slot undrain protection +// quoted, +// quoted +// ); + +// // execute +// vm.prank(USER_SENDER); +// coreRouteFacet.processRoute( +// address(USDT0), +// amountIn - 1, // Account for slot undrain protection +// address(WHYPE), +// quoted, +// USER_SENDER, +// route +// ); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. +// // HyperswapV3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. HyperswapV3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// // function _buildHyperswapV3Route( +// // HyperswapV3Params memory params +// // ) internal pure returns (bytes memory route) { +// // route = abi.encodePacked( +// // uint8(params.commandCode), +// // params.tokenIn, +// // uint8(1), // 1 pool +// // FULL_SHARE, // 65535 - 100% share +// // uint8(PoolType.UniV3), // POOL_TYPE_UNIV3 = 1 +// // params.pool, +// // uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false +// // params.recipient +// // ); + +// // return route; +// // } + +// struct UniV3SwapParams { +// address pool; +// SwapDirection direction; +// address recipient; +// } + +// function _buildUniV3SwapData( +// UniV3SwapParams memory params +// ) internal returns (bytes memory) { +// return +// abi.encodePacked( +// uniV3StyleFacet.swapUniV3.selector, +// params.pool, +// uint8(params.direction), +// params.recipient +// ); +// } +// } + +// // ----------------------------------------------------------------------------- +// // LaminarV3 on HyperEVM +// // ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorLaminarV3UpgradeTest is +// LiFiDexAggregatorUpgradeTest +// { +// UniV3StyleFacet internal uniV3StyleFacet; +// using SafeERC20 for IERC20; + +// IERC20 internal constant WHYPE = +// IERC20(0x5555555555555555555555555555555555555555); +// IERC20 internal constant LHYPE = +// IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); + +// address internal constant WHYPE_LHYPE_POOL = +// 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; + +// function setUp() public override { +// setupHyperEVM(); +// super.setUp(); +// } + +// function _addDexFacet() internal override { +// uniV3StyleFacet = new UniV3StyleFacet(); +// bytes4[] memory functionSelectors = new bytes4[](2); +// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; +// functionSelectors[1] = uniV3StyleFacet.laminarV3SwapCallback.selector; +// addFacet( +// address(ldaDiamond), +// address(uniV3StyleFacet), +// functionSelectors +// ); + +// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // Fund the user with WHYPE +// deal(address(WHYPE), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WHYPE.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: WHYPE_LHYPE_POOL, +// direction: SwapDirection.Token0ToToken1, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(WHYPE), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Record balances +// uint256 inBefore = WHYPE.balanceOf(USER_SENDER); +// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + +// // Execute swap (minOut = 0 for test) +// coreRouteFacet.processRoute( +// address(WHYPE), +// amountIn, +// address(LHYPE), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify +// uint256 inAfter = WHYPE.balanceOf(USER_SENDER); +// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the aggregator directly +// deal(address(WHYPE), address(uniV3StyleFacet), amountIn); + +// vm.startPrank(USER_SENDER); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: WHYPE_LHYPE_POOL, +// direction: SwapDirection.Token0ToToken1, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(WHYPE), +// uint8(1), +// FULL_SHARE, +// uint16(swapData.length), // length prefix +// swapData +// ); + +// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); + +// // Withdraw 1 wei to avoid slot-undrain protection +// coreRouteFacet.processRoute( +// address(WHYPE), +// amountIn - 1, +// address(LHYPE), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. +// // Laminar V3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. Laminar V3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// struct UniV3SwapParams { +// address pool; +// SwapDirection direction; +// address recipient; +// } + +// function _buildUniV3SwapData( +// UniV3SwapParams memory params +// ) internal returns (bytes memory) { +// return +// abi.encodePacked( +// uniV3StyleFacet.swapUniV3.selector, +// params.pool, +// uint8(params.direction), +// params.recipient +// ); +// } +// } + +// contract LiFiDexAggregatorXSwapV3UpgradeTest is LiFiDexAggregatorUpgradeTest { +// using SafeERC20 for IERC20; + +// UniV3StyleFacet internal uniV3StyleFacet; + +// address internal constant USDC_E_WXDC_POOL = +// 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; + +// /// @dev our two tokens: USDC.e and wrapped XDC +// IERC20 internal constant USDC_E = +// IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); +// IERC20 internal constant WXDC = +// IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); + +// function setUp() public override { +// setupXDC(); +// super.setUp(); +// } + +// function _addDexFacet() internal override { +// uniV3StyleFacet = new UniV3StyleFacet(); +// bytes4[] memory functionSelectors = new bytes4[](2); +// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; +// functionSelectors[1] = uniV3StyleFacet.xswapCallback.selector; +// addFacet( +// address(ldaDiamond), +// address(uniV3StyleFacet), +// functionSelectors +// ); + +// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e6; +// deal(address(USDC_E), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// USDC_E.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: USDC_E_WXDC_POOL, +// direction: SwapDirection.Token0ToToken1, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(USDC_E), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Record balances before swap +// uint256 inBefore = USDC_E.balanceOf(USER_SENDER); +// uint256 outBefore = WXDC.balanceOf(USER_SENDER); + +// // Execute swap (minOut = 0 for test) +// coreRouteFacet.processRoute( +// address(USDC_E), +// amountIn, +// address(WXDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify balances after swap +// uint256 inAfter = USDC_E.balanceOf(USER_SENDER); +// uint256 outAfter = WXDC.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + +// vm.stopPrank(); +// } + +// /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 5_000 * 1e6; + +// // fund the aggregator +// deal(address(USDC_E), address(uniV3StyleFacet), amountIn); + +// vm.startPrank(USER_SENDER); + +// // Account for slot-undrain protection +// uint256 swapAmount = amountIn - 1; + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: USDC_E_WXDC_POOL, +// direction: SwapDirection.Token0ToToken1, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(USDC_E), +// uint8(1), +// FULL_SHARE, +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Record balances before swap +// uint256 outBefore = WXDC.balanceOf(USER_SENDER); + +// coreRouteFacet.processRoute( +// address(USDC_E), +// swapAmount, +// address(WXDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify balances after swap +// uint256 outAfter = WXDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. +// // XSwap V3 does not support a "one-pool" second hop today, because +// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. XSwap V3's swap() immediately reverts on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// struct UniV3SwapParams { +// address pool; +// SwapDirection direction; +// address recipient; +// } + +// function _buildUniV3SwapData( +// UniV3SwapParams memory params +// ) internal returns (bytes memory) { +// return +// abi.encodePacked( +// uniV3StyleFacet.swapUniV3.selector, +// params.pool, +// uint8(params.direction), +// params.recipient +// ); +// } +// } + +// // ----------------------------------------------------------------------------- +// // RabbitSwap on Viction +// // ----------------------------------------------------------------------------- +// contract LiFiDexAggregatorRabbitSwapUpgradeTest is +// LiFiDexAggregatorUpgradeTest +// { +// using SafeERC20 for IERC20; + +// UniV3StyleFacet internal uniV3StyleFacet; + +// // Constants for RabbitSwap on Viction +// IERC20 internal constant SOROS = +// IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); +// IERC20 internal constant C98 = +// IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); +// address internal constant SOROS_C98_POOL = +// 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; + +// function setUp() public override { +// setupViction(); +// super.setUp(); +// } + +// function _addDexFacet() internal override { +// uniV3StyleFacet = new UniV3StyleFacet(); +// bytes4[] memory functionSelectors = new bytes4[](2); +// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; +// functionSelectors[1] = uniV3StyleFacet +// .rabbitSwapV3SwapCallback +// .selector; +// addFacet( +// address(ldaDiamond), +// address(uniV3StyleFacet), +// functionSelectors +// ); + +// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); +// } + +// function test_CanSwap() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the user with SOROS +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: SOROS_C98_POOL, +// direction: SwapDirection.Token1ToToken0, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // record balances before swap +// uint256 inBefore = SOROS.balanceOf(USER_SENDER); +// uint256 outBefore = C98.balanceOf(USER_SENDER); + +// // execute swap (minOut = 0 for test) +// coreRouteFacet.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// // verify balances after swap +// uint256 inAfter = SOROS.balanceOf(USER_SENDER); +// uint256 outAfter = C98.balanceOf(USER_SENDER); +// assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive C98"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// uint256 amountIn = 1_000 * 1e18; + +// // fund the aggregator directly +// deal(address(SOROS), address(uniV3StyleFacet), amountIn); + +// vm.startPrank(USER_SENDER); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: SOROS_C98_POOL, +// direction: SwapDirection.Token1ToToken0, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint16(swapData.length), // length prefix +// swapData +// ); + +// uint256 outBefore = C98.balanceOf(USER_SENDER); + +// // withdraw 1 wei less to avoid slot-undrain protection +// coreRouteFacet.processRoute( +// address(SOROS), +// amountIn - 1, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 outAfter = C98.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive C98"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. +// // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, +// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. UniV3-style pools immediately revert on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// function testRevert_RabbitSwapInvalidPool() public { +// uint256 amountIn = 1_000 * 1e18; +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: address(0), +// direction: SwapDirection.Token1ToToken0, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint16(swapData.length), // length prefix +// swapData +// ); + +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } + +// function testRevert_RabbitSwapInvalidRecipient() public { +// uint256 amountIn = 1_000 * 1e18; +// deal(address(SOROS), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// SOROS.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: SOROS_C98_POOL, +// direction: SwapDirection.Token1ToToken0, +// recipient: address(0) +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), +// address(SOROS), +// uint8(1), +// FULL_SHARE, +// uint16(swapData.length), // length prefix +// swapData +// ); + +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(SOROS), +// amountIn, +// address(C98), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } + +// struct UniV3SwapParams { +// address pool; +// SwapDirection direction; +// address recipient; +// } + +// function _buildUniV3SwapData( +// UniV3SwapParams memory params +// ) internal returns (bytes memory) { +// return +// abi.encodePacked( +// uniV3StyleFacet.swapUniV3.selector, +// params.pool, +// uint8(params.direction), +// params.recipient +// ); +// } +// } + +// // ---------------------------------------------- +// // EnosysDexV3 on Flare +// // ---------------------------------------------- +// contract LiFiDexAggregatorEnosysDexV3UpgradeTest is +// LiFiDexAggregatorUpgradeTest +// { +// using SafeERC20 for IERC20; + +// UniV3StyleFacet internal uniV3StyleFacet; + +// /// @dev HLN token on Flare +// IERC20 internal constant HLN = +// IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); + +// /// @dev USDT0 token on Flare +// IERC20 internal constant USDT0 = +// IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); + +// /// @dev The single EnosysDexV3 pool for HLN–USDT0 +// address internal constant ENOSYS_V3_POOL = +// 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; + +// function setUp() public override { +// setupFlare(); +// super.setUp(); +// } + +// function _addDexFacet() internal override { +// uniV3StyleFacet = new UniV3StyleFacet(); +// bytes4[] memory functionSelectors = new bytes4[](2); +// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; +// functionSelectors[1] = uniV3StyleFacet +// .enosysdexV3SwapCallback +// .selector; +// addFacet( +// address(ldaDiamond), +// address(uniV3StyleFacet), +// functionSelectors +// ); + +// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); +// } + +// /// @notice Single‐pool swap: USER sends HLN → receives USDT0 +// function test_CanSwap() public override { +// // Mint 1 000 HLN to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(HLN), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// HLN.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: ENOSYS_V3_POOL, +// direction: SwapDirection.Token0ToToken1, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(HLN), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Record balances before swap +// uint256 inBefore = HLN.balanceOf(USER_SENDER); +// uint256 outBefore = USDT0.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// coreRouteFacet.processRoute( +// address(HLN), +// amountIn, +// address(USDT0), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that HLN was spent and some USDT0 was received +// uint256 inAfter = HLN.balanceOf(USER_SENDER); +// uint256 outAfter = USDT0.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + +// vm.stopPrank(); +// } + +// /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 +// function test_CanSwap_FromDexAggregator() public override { +// // Fund the aggregator with 1 000 HLN +// uint256 amountIn = 1_000 * 1e18; +// deal(address(HLN), address(coreRouteFacet), amountIn); + +// vm.startPrank(USER_SENDER); + +// bytes memory swapData = _buildUniV3SwapData( +// UniV3SwapParams({ +// pool: ENOSYS_V3_POOL, +// direction: SwapDirection.Token0ToToken1, +// recipient: USER_SENDER +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(HLN), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDT0.balanceOf(USER_SENDER); + +// coreRouteFacet.processRoute( +// address(HLN), +// swapAmount, +// address(USDT0), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDT0 was received +// uint256 outAfter = USDT0.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. +// // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, +// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into +// // the pool.swap call. UniV3-style pools immediately revert on +// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools +// // in a single processRoute invocation. +// } + +// struct UniV3SwapParams { +// address pool; +// SwapDirection direction; +// address recipient; +// } + +// function _buildUniV3SwapData( +// UniV3SwapParams memory params +// ) internal view returns (bytes memory) { +// return +// abi.encodePacked( +// uniV3StyleFacet.swapUniV3.selector, +// params.pool, +// uint8(params.direction), +// params.recipient +// ); +// } +// } + +// // ---------------------------------------------- +// // SyncSwapV2 on Linea +// // ---------------------------------------------- +// contract LiFiDexAggregatorSyncSwapV2UpgradeTest is +// LiFiDexAggregatorUpgradeTest +// { +// using SafeERC20 for IERC20; + +// SyncSwapV2Facet internal syncSwapV2Facet; + +// IERC20 internal constant USDC = +// IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); +// IERC20 internal constant WETH = +// IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); +// address internal constant USDC_WETH_POOL_V1 = +// address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); +// address internal constant SYNC_SWAP_VAULT = +// address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); + +// address internal constant USDC_WETH_POOL_V2 = +// address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); + +// IERC20 internal constant USDT = +// IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); +// address internal constant USDC_USDT_POOL_V1 = +// address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); + +// function setUp() public override { +// setupLinea(); +// super.setUp(); +// } + +// function _addDexFacet() internal override { +// syncSwapV2Facet = new SyncSwapV2Facet(); +// bytes4[] memory functionSelectors = new bytes4[](1); +// functionSelectors[0] = syncSwapV2Facet.swapSyncSwapV2.selector; +// addFacet( +// address(ldaDiamond), +// address(syncSwapV2Facet), +// functionSelectors +// ); + +// syncSwapV2Facet = SyncSwapV2Facet(payable(address(ldaDiamond))); +// } + +// /// @notice Single‐pool swap: USER sends WETH → receives USDC +// function test_CanSwap() public override { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V1, +// to: address(USER_SENDER), +// withdrawMode: 2, +// isV1Pool: 1, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Record balances before swap +// uint256 inBefore = WETH.balanceOf(USER_SENDER); +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// coreRouteFacet.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that WETH was spent and some USDC_C was received +// uint256 inAfter = WETH.balanceOf(USER_SENDER); +// uint256 outAfter = USDC.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_PoolV2() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V2, +// to: address(USER_SENDER), +// withdrawMode: 2, +// isV1Pool: 0, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Record balances before swap +// uint256 inBefore = WETH.balanceOf(USER_SENDER); +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// // Execute the swap (minOut = 0 for test) +// coreRouteFacet.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that WETH was spent and some USDC_C was received +// uint256 inAfter = WETH.balanceOf(USER_SENDER); +// uint256 outAfter = USDC.balanceOf(USER_SENDER); + +// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator() public override { +// // Fund the aggregator with 1 000 WETH +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), address(ldaDiamond), amountIn); + +// vm.startPrank(USER_SENDER); + +// bytes memory swapData = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V1, +// to: address(USER_SENDER), +// withdrawMode: 2, +// isV1Pool: 1, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// coreRouteFacet.processRoute( +// address(WETH), +// swapAmount, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDC was received +// uint256 outAfter = USDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_FromDexAggregator_PoolV2() public { +// // Fund the aggregator with 1 000 WETH +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), address(ldaDiamond), amountIn); + +// vm.startPrank(USER_SENDER); + +// bytes memory swapData = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V2, +// to: address(USER_SENDER), +// withdrawMode: 2, +// isV1Pool: 0, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessMyERC20), // aggregator's funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Subtract 1 to protect against slot‐undrain +// uint256 swapAmount = amountIn - 1; +// uint256 outBefore = USDC.balanceOf(USER_SENDER); + +// coreRouteFacet.processRoute( +// address(WETH), +// swapAmount, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// // Verify that some USDC was received +// uint256 outAfter = USDC.balanceOf(USER_SENDER); +// assertGt(outAfter - outBefore, 0, "Should receive USDC"); + +// vm.stopPrank(); +// } + +// function test_CanSwap_MultiHop() public override { +// uint256 amountIn = 1_000e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(ldaDiamond), amountIn); + +// uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); +// uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); + +// // +// // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) +// // +// bytes memory swapData = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V1, +// to: SYNC_SWAP_VAULT, +// withdrawMode: 2, +// isV1Pool: 1, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory routeHop1 = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // +// // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 +// // +// bytes memory swapDataHop2 = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_USDT_POOL_V1, +// to: address(USER_SENDER), +// withdrawMode: 2, +// isV1Pool: 1, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory routeHop2 = abi.encodePacked( +// uint8(CommandType.ProcessOnePool), +// address(USDC), +// uint16(swapDataHop2.length), // length prefix +// swapDataHop2 +// ); + +// bytes memory route = bytes.concat(routeHop1, routeHop2); + +// uint256 amountOut = coreRouteFacet.processRoute( +// address(WETH), +// amountIn, +// address(USDT), +// 0, +// USER_SENDER, +// route +// ); + +// uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); +// uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); + +// assertEq( +// initialBalanceIn - afterBalanceIn, +// amountIn, +// "WETH spent mismatch" +// ); +// assertEq( +// amountOut, +// afterBalanceOut - initialBalanceOut, +// "USDT amountOut mismatch" +// ); +// vm.stopPrank(); +// } + +// function testRevert_V1PoolMissingVaultAddress() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V1, +// to: address(USER_SENDER), +// withdrawMode: 2, +// isV1Pool: 1, +// vault: address(0) +// }) +// ); + +// bytes memory route = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// route +// ); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidPoolOrRecipient() public { +// // Transfer 1 000 WETH from whale to USER_SENDER +// uint256 amountIn = 1_000 * 1e18; +// deal(address(WETH), USER_SENDER, amountIn); + +// vm.startPrank(USER_SENDER); +// WETH.approve(address(ldaDiamond), amountIn); + +// bytes memory swapData = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: address(0), +// to: address(USER_SENDER), +// withdrawMode: 2, +// isV1Pool: 1, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory routeWithInvalidPool = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapData.length), // length prefix +// swapData +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidPool +// ); + +// bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V1, +// to: address(0), +// withdrawMode: 2, +// isV1Pool: 1, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory routeWithInvalidRecipient = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapDataWithInvalidRecipient.length), // length prefix +// swapDataWithInvalidRecipient +// ); + +// // Expect revert with InvalidCallData +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(WETH), +// amountIn, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidRecipient +// ); + +// vm.stopPrank(); +// } + +// function testRevert_InvalidWithdrawMode() public { +// vm.startPrank(USER_SENDER); + +// bytes +// memory swapDataWithInvalidWithdrawMode = _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams({ +// pool: USDC_WETH_POOL_V1, +// to: address(USER_SENDER), +// withdrawMode: 3, +// isV1Pool: 1, +// vault: SYNC_SWAP_VAULT +// }) +// ); + +// bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( +// uint8(CommandType.ProcessUserERC20), // user funds +// address(WETH), // tokenIn +// uint8(1), // one pool +// FULL_SHARE, // 100% +// uint16(swapDataWithInvalidWithdrawMode.length), // length prefix +// swapDataWithInvalidWithdrawMode +// ); + +// // Expect revert with InvalidCallData because withdrawMode is invalid +// vm.expectRevert(InvalidCallData.selector); +// coreRouteFacet.processRoute( +// address(WETH), +// 1, +// address(USDC), +// 0, +// USER_SENDER, +// routeWithInvalidWithdrawMode +// ); + +// vm.stopPrank(); +// } + +// struct SyncSwapV2SwapParams { +// address pool; +// address to; +// uint8 withdrawMode; +// uint8 isV1Pool; +// address vault; +// } + +// function _buildSyncSwapV2SwapData( +// SyncSwapV2SwapParams memory params +// ) internal view returns (bytes memory) { +// return +// abi.encodePacked( +// syncSwapV2Facet.swapSyncSwapV2.selector, +// params.pool, +// params.to, +// params.withdrawMode, +// params.isV1Pool, +// params.vault +// ); +// } +// } From bf25a66ae91bd4047ff8502820f20d1959ea618c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 19 Aug 2025 11:40:09 +0200 Subject: [PATCH 038/220] Refactor CoreRouteFacet to optimize payload handling --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 67 ++++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 53d277642..8242f40dd 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -251,16 +251,58 @@ contract CoreRouteFacet is ReentrancyGuard { bytes memory data = cur.readBytesWithLength(); bytes4 selector = _readSelector(data); - bytes memory payload = _payloadFrom(data); + // in-place payload alias (no copy) + bytes memory payload; + assembly { + payload := add(data, 4) + mstore(payload, sub(mload(data), 4)) + } address facet = LibDiamondLoupe.facetAddress(selector); if (facet == address(0)) revert UnknownSelector(); - (bool ok, bytes memory ret) = facet.delegatecall( - abi.encodeWithSelector(selector, payload, from, tokenIn, amountIn) - ); - if (!ok) { - LibUtil.revertWith(ret); + bool success; + bytes memory returnData; + assembly { + let free := mload(0x40) + // selector + mstore(free, selector) + let args := add(free, 4) + + // head (4 args): [offset_to_payload, from, tokenIn, amountIn] + mstore(args, 0x80) // offset to payload data (after 4 static slots) + mstore(add(args, 0x20), from) + mstore(add(args, 0x40), tokenIn) + mstore(add(args, 0x60), amountIn) + + // payload area + let d := add(args, 0x80) + let len := mload(payload) + mstore(d, len) + // copy payload bytes + // identity precompile is cheapest for arbitrary-length copy + pop( + staticcall(gas(), 0x04, add(payload, 32), len, add(d, 32), len) + ) + + let padded := and(add(len, 31), not(31)) + let total := add(4, add(0x80, add(0x20, padded))) + success := delegatecall(gas(), facet, free, total, 0, 0) + + // update free memory pointer + mstore(0x40, add(free, total)) + + // capture return data + let rsize := returndatasize() + returnData := mload(0x40) + mstore(returnData, rsize) + let rptr := add(returnData, 32) + returndatacopy(rptr, 0, rsize) + mstore(0x40, add(rptr, and(add(rsize, 31), not(31)))) + } + + if (!success) { + LibUtil.revertWith(returnData); } } @@ -278,17 +320,10 @@ contract CoreRouteFacet is ReentrancyGuard { /// @dev Returns a fresh bytes containing the original blob without the first 4 bytes. function _payloadFrom( bytes memory blob - ) private view returns (bytes memory out) { - uint256 len = blob.length; - if (len <= 4) return new bytes(0); - - uint256 newLen = len - 4; + ) private pure returns (bytes memory payload) { assembly { - out := mload(0x40) - mstore(0x40, add(out, add(newLen, 32))) - mstore(out, newLen) - let src := add(blob, 36) // skip length(32) + 4 - pop(staticcall(gas(), 4, src, newLen, add(out, 32), newLen)) + payload := add(blob, 4) + mstore(payload, sub(mload(blob), 4)) } } } From 30e3b6ab8ca8b0b295ae44f6dbf2c472b24af212 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 19 Aug 2025 12:08:16 +0200 Subject: [PATCH 039/220] Refactor test contracts to use pure functions for default token amounts and update route building logic in CoreRouteFacet tests --- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 7 +- .../Periphery/Lda/CoreRouteFacet.t.sol | 155 ++++++++++-------- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 2 +- .../Lda/Facets/HyperswapV3Facet.t.sol | 1 + .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 1 + 6 files changed, 100 insertions(+), 68 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 4dbe7f250..eb4fccd5b 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -201,7 +201,12 @@ abstract contract BaseDexFacetTest is CoreRouteTestBase { return route; } - function _getDefaultAmountForTokenIn() internal virtual returns (uint256) { + function _getDefaultAmountForTokenIn() + internal + pure + virtual + returns (uint256) + { return 1_000 * 1e18; } } diff --git a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol index f7340ca4d..c771001ed 100644 --- a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol @@ -118,7 +118,16 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { SwapTestParams memory params, bytes memory swapData ) internal pure returns (bytes memory) { - if (params.commandType == CommandType.ProcessOnePool) { + if (params.commandType == CommandType.ProcessNative) { + return + abi.encodePacked( + uint8(params.commandType), + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ); + } else if (params.commandType == CommandType.ProcessOnePool) { return abi.encodePacked( uint8(params.commandType), @@ -385,23 +394,13 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { } } -contract CoreRouteFacetTest is LdaDiamondTest { +contract CoreRouteFacetTest is CoreRouteTestBase { using SafeTransferLib for address; - CoreRouteFacet internal coreRouteFacet; - - uint16 internal constant FULL_SHARE = 65535; bytes4 internal pullSel; function setUp() public override { - LdaDiamondTest.setUp(); - - // CoreRouteFacet - coreRouteFacet = new CoreRouteFacet(); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = CoreRouteFacet.processRoute.selector; - addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); - coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); + CoreRouteTestBase.setUp(); // Register mock pull facet once and store selector MockPullERC20Facet mockPull = new MockPullERC20Facet(); @@ -473,13 +472,17 @@ contract CoreRouteFacetTest is LdaDiamondTest { ); // route: [3][num=1][share=FULL_SHARE][len][swapData] - bytes memory route = abi.encodePacked( - uint8(3), - uint8(1), - FULL_SHARE, - uint16(swapData.length), - swapData - ); + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(0), + tokenOut: address(0), + amountIn: amount, + minOut: 0, + sender: USER_SENDER, + recipient: recipient, + commandType: CommandType.ProcessNative // This maps to value 3 + }); + + bytes memory route = _buildBaseRoute(params, swapData); uint256 beforeBal = recipient.balance; @@ -574,14 +577,20 @@ contract CoreRouteFacetTest is LdaDiamondTest { 1e18 ); + bytes memory swapData = abi.encodePacked(bytes4(0xdeadbeef)); + // ProcessUserERC20: [2][tokenIn][num=1][share=FULL_SHARE][len=4][selector=0xdeadbeef] - bytes memory route = abi.encodePacked( - uint8(2), - address(token), - uint8(1), - FULL_SHARE, - uint16(4), - bytes4(0xdeadbeef) + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(token), + tokenOut: address(token), + amountIn: 0, + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData ); vm.prank(USER_SENDER); @@ -610,20 +619,24 @@ contract CoreRouteFacetTest is LdaDiamondTest { vm.startPrank(USER_SENDER); IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); + bytes memory swapData = abi.encodePacked(pullSel); + // Build one step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] - bytes memory step = abi.encodePacked( - uint8(2), - address(token), - uint8(1), - FULL_SHARE, - uint16(4), - pullSel + bytes memory step = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(token), + tokenOut: address(0), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData ); - // Route with two identical steps → actual deduction = 2 * amountIn, but amountIn param = amountIn bytes memory route = bytes.concat(step, step); - // Expect MinimalInputBalanceViolation vm.expectRevert( abi.encodeWithSelector( CoreRouteFacet.MinimalInputBalanceViolation.selector, @@ -663,14 +676,21 @@ contract CoreRouteFacetTest is LdaDiamondTest { vm.startPrank(USER_SENDER); IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); - bytes memory step = abi.encodePacked( - uint8(2), - address(token), - uint8(1), - FULL_SHARE, - uint16(4), - pullSel + bytes memory swapData = abi.encodePacked(pullSel); + + bytes memory step = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(token), + tokenOut: address(tokenOut), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData ); + bytes memory route = bytes.concat(step, step); vm.expectRevert( @@ -709,19 +729,22 @@ contract CoreRouteFacetTest is LdaDiamondTest { 0 ); // recipient starts at 0 - // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] - bytes memory step = abi.encodePacked( - uint8(2), - address(tokenIn), - uint8(1), - FULL_SHARE, - uint16(4), - sel - ); + bytes memory swapData = abi.encodePacked(sel); - bytes memory route = step; // single step; no tokenOut will be sent to recipient + // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + minOut: 1, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); // single step; no tokenOut will be sent to recipient - // Approve and call vm.startPrank(USER_SENDER); IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); @@ -756,19 +779,21 @@ contract CoreRouteFacetTest is LdaDiamondTest { amountIn ); - // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] - bytes memory step = abi.encodePacked( - uint8(2), - address(tokenIn), - uint8(1), - FULL_SHARE, - uint16(4), - sel - ); + bytes memory swapData = abi.encodePacked(sel); - bytes memory route = step; // no native will be sent to recipient + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(0), + amountIn: amountIn, + minOut: 1, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); - // Approve and call vm.startPrank(USER_SENDER); IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 4ce68c30d..894aaa57d 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -147,7 +147,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { algebraFacet = AlgebraFacet(facetAddress); } - // NEW: slot wiring for primary pair function _setupDexEnv() internal override { tokenIn = IERC20(0xcF800F4948D16F23333508191B1B1591daF70438); // APE_ETH_TOKEN tokenOut = IERC20(0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949); // WETH_TOKEN @@ -156,6 +155,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { function _getDefaultAmountForTokenIn() internal + pure override returns (uint256) { diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index eb14f9d9a..d4c8a2380 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -28,6 +28,7 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { function _getDefaultAmountForTokenIn() internal + pure override returns (uint256) { diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index cc23651c4..0b03c2ce8 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -51,13 +51,13 @@ contract IzumiV3FacetTest is BaseDexFacetTest { function _getDefaultAmountForTokenIn() internal + pure override returns (uint256) { return 100 * 1e6; // 100 USDC with 6 decimals } - // NEW function _setupDexEnv() internal override { tokenIn = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); // USDC tokenMid = IERC20(0x4200000000000000000000000000000000000006); // WETH diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index bd7527abe..3c1e12706 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -22,6 +22,7 @@ contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { function _getDefaultAmountForTokenIn() internal + pure override returns (uint256) { From 353093ad4169150681fc43154271ae27e53a1bc0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 19 Aug 2025 12:32:59 +0200 Subject: [PATCH 040/220] Enhance CoreRouteFacet tests to utilize LibAsset for native asset handling, ensuring correct balance checks and route processing for both native and ERC20 tokens --- .../Periphery/Lda/CoreRouteFacet.t.sol | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol index c771001ed..89c87e2fa 100644 --- a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol @@ -6,6 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; @@ -155,7 +156,10 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { bool isFeeOnTransferToken, RouteEventVerification memory routeEventVerification ) internal { - if (params.commandType != CommandType.ProcessMyERC20) { + if ( + params.commandType != CommandType.ProcessMyERC20 && + !LibAsset.isNativeAsset(params.tokenIn) + ) { IERC20(params.tokenIn).approve( address(ldaDiamond), params.amountIn @@ -163,15 +167,19 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { } uint256 inBefore; - uint256 outBefore = IERC20(params.tokenOut).balanceOf( - params.recipient - ); + uint256 outBefore = LibAsset.isNativeAsset(params.tokenOut) + ? params.recipient.balance + : IERC20(params.tokenOut).balanceOf(params.recipient); // For aggregator funds, check the diamond's balance if (params.commandType == CommandType.ProcessMyERC20) { - inBefore = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + inBefore = LibAsset.isNativeAsset(params.tokenIn) + ? address(ldaDiamond).balance + : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); } else { - inBefore = IERC20(params.tokenIn).balanceOf(params.sender); + inBefore = LibAsset.isNativeAsset(params.tokenIn) + ? params.sender.balance + : IERC20(params.tokenIn).balanceOf(params.sender); } address fromAddress = params.sender == address(ldaDiamond) @@ -187,27 +195,45 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { params.tokenIn, params.tokenOut, params.amountIn, - params.minOut, // Use minOut from SwapTestParams + params.minOut, routeEventVerification.expectedExactOut ); - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - params.minOut, // Use minOut from SwapTestParams - params.recipient, - route - ); + // For native token, send value with the call + if (LibAsset.isNativeAsset(params.tokenIn)) { + coreRouteFacet.processRoute{ value: params.amountIn }( + params.tokenIn, + params.amountIn, + params.tokenOut, + params.minOut, + params.recipient, + route + ); + } else { + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + params.minOut, + params.recipient, + route + ); + } uint256 inAfter; - uint256 outAfter = IERC20(params.tokenOut).balanceOf(params.recipient); + uint256 outAfter = LibAsset.isNativeAsset(params.tokenOut) + ? params.recipient.balance + : IERC20(params.tokenOut).balanceOf(params.recipient); // Check balance change on the correct address if (params.commandType == CommandType.ProcessMyERC20) { - inAfter = IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + inAfter = LibAsset.isNativeAsset(params.tokenIn) + ? address(ldaDiamond).balance + : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); } else { - inAfter = IERC20(params.tokenIn).balanceOf(params.sender); + inAfter = LibAsset.isNativeAsset(params.tokenIn) + ? params.sender.balance + : IERC20(params.tokenIn).balanceOf(params.sender); } // Use assertEq or assertApproxEqAbs based on isFeeOnTransferToken @@ -477,29 +503,23 @@ contract CoreRouteFacetTest is CoreRouteTestBase { tokenOut: address(0), amountIn: amount, minOut: 0, - sender: USER_SENDER, + sender: USER_SENDER, // Use USER_SENDER directly recipient: recipient, - commandType: CommandType.ProcessNative // This maps to value 3 + commandType: CommandType.ProcessNative }); bytes memory route = _buildBaseRoute(params, swapData); - uint256 beforeBal = recipient.balance; - - vm.prank(USER_SENDER); - coreRouteFacet.processRoute{ value: amount }( - address(0), // tokenIn: native - 0, - address(0), // tokenOut: native - 0, - recipient, - route - ); - - assertEq( - recipient.balance - beforeBal, - amount, - "recipient should receive full amount" + vm.prank(USER_SENDER); // Set msg.sender to USER_SENDER + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + false, + RouteEventVerification({ + expectedExactOut: amount, + checkData: true + }) ); } From b7e4f5c9134d19692598e16b82de3c69bb74b3de Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 19 Aug 2025 14:19:44 +0200 Subject: [PATCH 041/220] Refactor facets to utilize LibAsset for ERC20 transfers, update import paths for libraries, and enhance test contracts with structured error handling and improved setup functions --- src/Libraries/LibAsset.sol | 2 +- src/Periphery/Lda/Facets/AlgebraFacet.sol | 15 +- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 14 +- src/Periphery/Lda/Facets/CurveFacet.sol | 64 +-- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 21 +- src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 7 +- src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 48 ++- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 19 +- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 22 +- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 163 ++++---- .../Lda/BaseUniV3StyleDexFacet.t.sol | 42 +- .../Periphery/Lda/CoreRouteFacet.t.sol | 53 +-- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 388 +++++++++--------- .../Lda/Facets/EnosysDexV3Facet.t.sol | 1 + .../Lda/Facets/HyperswapV3Facet.t.sol | 1 + .../Lda/Facets/VelodromeV2Facet.t.sol | 14 +- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 1 + 17 files changed, 436 insertions(+), 439 deletions(-) diff --git a/src/Libraries/LibAsset.sol b/src/Libraries/LibAsset.sol index 89d3f2352..7b943a09d 100644 --- a/src/Libraries/LibAsset.sol +++ b/src/Libraries/LibAsset.sol @@ -77,7 +77,7 @@ library LibAsset { address assetId, address recipient, uint256 amount - ) private { + ) internal { // make sure a meaningful receiver address was provided if (recipient == NULL_ADDRESS) { revert InvalidReceiver(); diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 6419c9912..79cf9db6c 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibPackedStream } from "../../../Libraries/LibPackedStream.sol"; -import { LibCallbackManager } from "../../../Libraries/LibCallbackManager.sol"; -import { LibUniV3Logic } from "../../../Libraries/LibUniV3Logic.sol"; -import { IAlgebraPool } from "../../../Interfaces/IAlgebraPool.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { InvalidCallData } from "../../../Errors/GenericErrors.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; +import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title AlgebraFacet /// @author LI.FI (https://li.fi) @@ -17,14 +17,15 @@ contract AlgebraFacet { using LibPackedStream for uint256; using SafeERC20 for IERC20; - /// Constants /// + // ==== Constants ==== uint160 internal constant MIN_SQRT_RATIO = 4295128739; uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - /// Errors /// + // ==== Errors ==== error AlgebraSwapUnexpected(); + // ==== External Functions ==== function swapAlgebra( bytes memory swapData, address from, diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 8242f40dd..19f16688e 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -3,11 +3,12 @@ pragma solidity ^0.8.17; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; + import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibUtil } from "lifi/Libraries/LibUtil.sol"; -import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; /// @title CoreRouteFacet /// @author LI.FI (https://li.fi) @@ -19,10 +20,11 @@ contract CoreRouteFacet is ReentrancyGuard { using SafeERC20 for IERC20Permit; using LibPackedStream for uint256; + // ==== Constants ==== /// @dev sentinel used to indicate that the input is already at the destination pool address internal constant INTERNAL_INPUT_SOURCE = address(0); - /// Events /// + // ==== Events ==== event Route( address indexed from, address to, @@ -33,13 +35,14 @@ contract CoreRouteFacet is ReentrancyGuard { uint256 amountOut ); - /// Errors /// + // ==== Errors ==== error MinimalOutputBalanceViolation(uint256 amountOut); error MinimalInputBalanceViolation(uint256 available, uint256 required); error UnknownCommandCode(); error SwapFailed(); error UnknownSelector(); + // ==== External Functions ==== function processRoute( address tokenIn, uint256 amountIn, @@ -59,6 +62,7 @@ contract CoreRouteFacet is ReentrancyGuard { ); } + // ==== Private Functions - Core Logic ==== function _executeRoute( address tokenIn, uint256 amountIn, @@ -175,7 +179,7 @@ contract CoreRouteFacet is ReentrancyGuard { amountOut = balOutFinal - balOutStart; } - /// ===== Command handlers (renamed/reorganized) ===== + // ==== Private Functions - Command Handlers ==== /// @notice ERC-2612 permit application for `tokenIn`. function _applyPermit(address tokenIn, uint256 cur) private { @@ -306,7 +310,7 @@ contract CoreRouteFacet is ReentrancyGuard { } } - /// ===== Helpers ===== + // ==== Private Functions - Helpers ==== /// @dev Extracts the first 4 bytes as a selector. function _readSelector( diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index bc3f27e2c..b578837dc 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -2,12 +2,10 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { ICurve } from "../../../Interfaces/ICurve.sol"; -import { ICurveLegacy } from "../../../Interfaces/ICurveLegacy.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ICurve } from "lifi/Interfaces/ICurve.sol"; +import { ICurveLegacy } from "lifi/Interfaces/ICurveLegacy.sol"; /// @title Curve Facet /// @author LI.FI (https://li.fi) @@ -15,14 +13,9 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; /// @custom:version 1.0.0 contract CurveFacet { using LibInputStream for uint256; - using SafeERC20 for IERC20; using LibAsset for IERC20; - using Approve for IERC20; - - /// Constants /// - address internal constant NATIVE_ADDRESS = - 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + // ==== External Functions ==== /// @notice Curve pool swap. Legacy pools that don't return amountOut and have native coins are not supported /// @param stream [pool, poolType, fromIndex, toIndex, recipient, output token] /// @param from Where to take liquidity for swap @@ -44,7 +37,7 @@ contract CurveFacet { // TODO arm callback protection uint256 amountOut; - if (tokenIn == NATIVE_ADDRESS) { + if (LibAsset.isNativeAsset(tokenIn)) { amountOut = ICurve(pool).exchange{ value: amountIn }( fromIndex, toIndex, @@ -53,13 +46,14 @@ contract CurveFacet { ); } else { if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom( + LibAsset.transferFromERC20( + tokenIn, msg.sender, address(this), amountIn ); } - IERC20(tokenIn).approveSafe(pool, amountIn); + LibAsset.maxApproveERC20(IERC20(tokenIn), pool, amountIn); if (poolType == 0) { amountOut = ICurve(pool).exchange( fromIndex, @@ -80,51 +74,9 @@ contract CurveFacet { } if (to != address(this)) { - if (tokenOut == NATIVE_ADDRESS) { - SafeTransferLib.safeTransferETH(to, amountOut); - } else { - IERC20(tokenOut).safeTransfer(to, amountOut); - } + LibAsset.transferAsset(tokenOut, payable(to), amountOut); } return amountOut; } } - -library Approve { - /** - * @dev ERC20 approve that correct works with token.approve which returns bool or nothing (USDT for example) - * @param token The token targeted by the call. - * @param spender token spender - * @param amount token amount - */ - function approveStable( - IERC20 token, - address spender, - uint256 amount - ) internal returns (bool) { - (bool success, bytes memory data) = address(token).call( - abi.encodeWithSelector(token.approve.selector, spender, amount) - ); - return success && (data.length == 0 || abi.decode(data, (bool))); - } - - /** - * @dev ERC20 approve that correct works with token.approve which reverts if amount and - * current allowance are not zero simultaniously (USDT for example). - * In second case it tries to set allowance to 0, and then back to amount. - * @param token The token targeted by the call. - * @param spender token spender - * @param amount token amount - */ - function approveSafe( - IERC20 token, - address spender, - uint256 amount - ) internal returns (bool) { - return - approveStable(token, spender, amount) || - (approveStable(token, spender, 0) && - approveStable(token, spender, amount)); - } -} diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 8c7450bf3..83e9ffd03 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; @@ -14,23 +14,19 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; contract IzumiV3Facet { using LibPackedStream for uint256; using LibCallbackManager for *; - using SafeERC20 for IERC20; + // ==== Constants ==== /// @dev iZiSwap pool price points boundaries int24 internal constant IZUMI_LEFT_MOST_PT = -800000; int24 internal constant IZUMI_RIGHT_MOST_PT = 800000; uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + // ==== Errors ==== error IzumiV3SwapUnexpected(); error IzumiV3SwapCallbackUnknownSource(); error IzumiV3SwapCallbackNotPositiveAmount(); - /// @notice Performs a swap through iZiSwap V3 pools - /// @dev This function handles both X to Y and Y to X swaps through iZiSwap V3 pools - /// @param swapData [pool, direction, recipient] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap + // ==== External Functions ==== function swapIzumiV3( bytes memory swapData, address from, @@ -49,7 +45,8 @@ contract IzumiV3Facet { ) revert InvalidCallData(); if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom( + LibAsset.transferFromERC20( + tokenIn, msg.sender, address(this), amountIn @@ -84,6 +81,7 @@ contract IzumiV3Facet { return 0; // Return value not used in current implementation } + // ==== Callback Functions ==== function swapX2YCallback( uint256 amountX, uint256, @@ -100,6 +98,7 @@ contract IzumiV3Facet { _handleIzumiV3SwapCallback(amountY, data); } + // ==== Private Functions ==== /// @dev Common logic for iZiSwap callbacks /// @param amountToPay The amount of tokens to be sent to the pool /// @param data The data passed through by the caller @@ -114,7 +113,7 @@ contract IzumiV3Facet { } address tokenIn = abi.decode(data, (address)); - IERC20(tokenIn).safeTransfer(msg.sender, amountToPay); + LibAsset.transferERC20(tokenIn, msg.sender, amountToPay); LibCallbackManager.clear(); } diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index d88ba9344..c0545d8df 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ISyncSwapPool } from "lifi/Interfaces/ISyncSwapPool.sol"; import { ISyncSwapVault } from "lifi/Interfaces/ISyncSwapVault.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; @@ -13,7 +13,6 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @custom:version 1.0.0 contract SyncSwapV2Facet { using LibPackedStream for uint256; - using SafeERC20 for IERC20; /// @notice Performs a swap through SyncSwapV2 pools /// @dev This function handles both X to Y and Y to X swaps through SyncSwapV2 pools. @@ -51,9 +50,9 @@ contract SyncSwapV2Facet { if (isV1Pool && target == address(0)) revert InvalidCallData(); if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom(msg.sender, target, amountIn); + LibAsset.transferFromERC20(tokenIn, msg.sender, target, amountIn); } else if (from == address(this)) { - IERC20(tokenIn).safeTransfer(target, amountIn); + LibAsset.transferERC20(tokenIn, target, amountIn); } // if from is not msg.sender or address(this), it must be INTERNAL_INPUT_SOURCE // which means tokens are already in the vault/pool, no transfer needed diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index bc2039838..616663b1a 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -1,14 +1,26 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; -import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; interface IUniswapV2Pair { - function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); - function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; } /// @title UniV2Style Facet @@ -16,16 +28,16 @@ interface IUniswapV2Pair { /// @notice Handles UniswapV2-style swaps (UniV2, SushiSwap, PancakeV2, etc.) /// @custom:version 2.0.0 contract UniV2StyleFacet { - using SafeERC20 for IERC20; using LibInputStream for uint256; - /// Constants /// + // ==== Constants ==== address internal constant INTERNAL_INPUT_SOURCE = address(0); uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; - /// Errors /// + // ==== Errors ==== error WrongPoolReserves(); + // ==== External Functions ==== /// @notice Executes a UniswapV2-style swap /// @param stream The input stream containing swap parameters /// @param from Where to take liquidity for swap @@ -48,29 +60,33 @@ contract UniV2StyleFacet { // Transfer tokens to pool if needed if (from == address(this)) { - IERC20(tokenIn).safeTransfer(pool, amountIn); + LibAsset.transferERC20(tokenIn, pool, amountIn); } else if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom(msg.sender, pool, amountIn); + LibAsset.transferFromERC20(tokenIn, msg.sender, pool, amountIn); } // Get reserves and calculate output (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves(); if (r0 == 0 || r1 == 0) revert WrongPoolReserves(); - - (uint256 reserveIn, uint256 reserveOut) = direction == DIRECTION_TOKEN0_TO_TOKEN1 + + (uint256 reserveIn, uint256 reserveOut) = direction == + DIRECTION_TOKEN0_TO_TOKEN1 ? (r0, r1) : (r1, r0); - + // Calculate actual input amount from pool balance amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; uint256 amountInWithFee = amountIn * (1_000_000 - fee); - amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1_000_000 + amountInWithFee); - - (uint256 amount0Out, uint256 amount1Out) = direction == DIRECTION_TOKEN0_TO_TOKEN1 + amountOut = + (amountInWithFee * reserveOut) / + (reserveIn * 1_000_000 + amountInWithFee); + + (uint256 amount0Out, uint256 amount1Out) = direction == + DIRECTION_TOKEN0_TO_TOKEN1 ? (uint256(0), amountOut) : (amountOut, uint256(0)); - + IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0)); } -} \ No newline at end of file +} diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index f859fb56d..68a8a00f2 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; @@ -22,25 +22,25 @@ interface IUniV3StylePool { /// @notice Handles Uniswap V3 swaps with callback management /// @custom:version 1.0.0 contract UniV3StyleFacet { - using SafeERC20 for IERC20; using LibCallbackManager for *; using LibPackedStream for uint256; - /// Constants /// + // ==== Constants ==== uint160 internal constant MIN_SQRT_RATIO = 4295128739; uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - /// Errors /// + // ==== Errors ==== error UniV3SwapUnexpected(); - /// Modifiers /// + // ==== Modifiers ==== modifier onlyExpectedPool() { LibCallbackManager.verifyCallbackSender(); _; LibCallbackManager.clear(); } + // ==== External Functions ==== /// @notice Executes a UniswapV3 swap /// @param swapData The input stream containing swap parameters /// @param from Where to take liquidity for swap @@ -63,7 +63,8 @@ contract UniV3StyleFacet { // Transfer tokens if needed if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom( + LibAsset.transferFromERC20( + tokenIn, msg.sender, address(this), amountIn @@ -83,14 +84,12 @@ contract UniV3StyleFacet { ); // Verify callback was called (arm should be cleared by callback) - LibCallbackManager.CallbackStorage storage cbStor = LibCallbackManager - .callbackStorage(); - if (cbStor.expected != address(0)) { + if (LibCallbackManager.callbackStorage().expected != address(0)) { revert UniV3SwapUnexpected(); } } - /// @notice Callback for UniswapV3 swaps + // ==== Callback Functions ==== function uniswapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 92bec7ca3..7fc44b89f 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title VelodromeV2Facet @@ -13,14 +13,16 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @custom:version 1.0.0 contract VelodromeV2Facet { using LibPackedStream for uint256; - using SafeERC20 for IERC20; + // ==== Constants ==== uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; uint8 internal constant CALLBACK_ENABLED = 1; address internal constant INTERNAL_INPUT_SOURCE = address(0); + // ==== Errors ==== error WrongPoolReserves(); + // ==== External Functions ==== /// @notice Performs a swap through VelodromeV2 pools /// @dev This function does not handle native token swaps directly, so processNative command cannot be used /// @param swapData [pool, direction, to, callback] @@ -51,10 +53,16 @@ contract VelodromeV2Facet { amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; } else { - if (from == address(this)) - IERC20(tokenIn).safeTransfer(pool, amountIn); - else if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom(msg.sender, pool, amountIn); + if (from == address(this)) { + LibAsset.transferERC20(tokenIn, pool, amountIn); + } else if (from == msg.sender) { + LibAsset.transferFromERC20( + tokenIn, + msg.sender, + pool, + amountIn + ); + } } // calculate the expected output amount using the pool's getAmountOut function diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index eb4fccd5b..bbc813652 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -3,30 +3,36 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { CoreRouteTestBase } from "./CoreRouteFacet.t.sol"; +import { BaseCoreRouteTest } from "./CoreRouteFacet.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; /** * @title BaseDexFacetTest * @notice Base test contract with common functionality and abstractions for DEX-specific tests */ -abstract contract BaseDexFacetTest is CoreRouteTestBase { +abstract contract BaseDexFacetTest is BaseCoreRouteTest { using SafeERC20 for IERC20; + // ==== Types ==== struct ForkConfig { string networkName; uint256 blockNumber; } - // Direction constants enum SwapDirection { Token1ToToken0, // 0 Token0ToToken1 // 1 } - ForkConfig internal forkConfig; + struct RouteParams { + CommandType commandType; + address tokenIn; + uint8 numPools; // defaults to 1 + uint16 share; // defaults to FULL_SHARE + bytes swapData; + } - // Common events and errors + // ==== Events ==== event HookCalled( address sender, uint256 amount0, @@ -34,16 +40,17 @@ abstract contract BaseDexFacetTest is CoreRouteTestBase { bytes data ); + // ==== Errors ==== error WrongPoolReserves(); error PoolDoesNotExist(); - - // Add custom errors at the top of the contract error ParamsDataLengthMismatch(); error NoHopsProvided(); error InvalidForkConfig(string reason); error UnknownNetwork(string name); - // At top-level state + // ==== Storage Variables ==== + ForkConfig internal forkConfig; + IERC20 internal tokenIn; IERC20 internal tokenMid; // optional for multi-hop IERC20 internal tokenOut; @@ -55,52 +62,19 @@ abstract contract BaseDexFacetTest is CoreRouteTestBase { // Optional flag/hook for aggregator slot-undrain behavior bool internal aggregatorUndrainMinusOne; - function _addDexFacet() internal virtual { - ( - address facetAddress, - bytes4[] memory functionSelectors - ) = _createFacetAndSelectors(); - - addFacet(address(ldaDiamond), facetAddress, functionSelectors); - - _setFacetInstance(payable(address(ldaDiamond))); - } - - // Each facet test must implement these + // ==== Virtual Functions ==== function _createFacetAndSelectors() internal virtual returns (address, bytes4[] memory); + function _setFacetInstance(address payable facetAddress) internal virtual; function _setupDexEnv() internal virtual; - // helper to uppercase ASCII - function _convertToUpperCase( - string memory s - ) internal pure returns (string memory) { - bytes memory b = bytes(s); - for (uint256 i; i < b.length; i++) { - uint8 c = uint8(b[i]); - if (c >= 97 && c <= 122) { - b[i] = bytes1(c - 32); - } - } - return string(b); - } - - // optional: ensure key exists in config/networks.json - function _ensureNetworkExists(string memory name) internal { - // will revert if the key path is missing - string memory json = vm.readFile("config/networks.json"); - // read the "..name" path to confirm key presence - string memory path = string.concat(".", name, ".name"); - string memory value = stdJson.readString(json, path); - if (bytes(value).length == 0) { - revert UnknownNetwork(name); - } - } + function _setupForkConfig() internal virtual; + // ==== Setup Functions ==== function setUp() public virtual override { _setupForkConfig(); @@ -135,54 +109,50 @@ abstract contract BaseDexFacetTest is CoreRouteTestBase { customBlockNumberForForking = forkConfig.blockNumber; fork(); - CoreRouteTestBase.setUp(); + BaseCoreRouteTest.setUp(); _setupDexEnv(); // populate tokens/pools _addDexFacet(); } - function _setupForkConfig() internal virtual; + // ==== Internal Functions ==== + function _addDexFacet() internal virtual { + ( + address facetAddress, + bytes4[] memory functionSelectors + ) = _createFacetAndSelectors(); - // ============================ Abstract DEX Tests ============================ - /** - * @notice Abstract test for basic token swapping functionality - * Each DEX implementation should override this - */ - function test_CanSwap() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap: Not implemented"); - } + addFacet(address(ldaDiamond), facetAddress, functionSelectors); - /** - * @notice Abstract test for swapping tokens from the DEX aggregator - * Each DEX implementation should override this - */ - function test_CanSwap_FromDexAggregator() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_FromDexAggregator: Not implemented"); + _setFacetInstance(payable(address(ldaDiamond))); } - /** - * @notice Abstract test for multi-hop swapping - * Each DEX implementation should override this - */ - function test_CanSwap_MultiHop() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_MultiHop: Not implemented"); + // ==== Helper Functions ==== + // helper to uppercase ASCII + function _convertToUpperCase( + string memory s + ) internal pure returns (string memory) { + bytes memory b = bytes(s); + for (uint256 i; i < b.length; i++) { + uint8 c = uint8(b[i]); + if (c >= 97 && c <= 122) { + b[i] = bytes1(c - 32); + } + } + return string(b); } - // Add this struct for route building - struct RouteParams { - CommandType commandType; - address tokenIn; - uint8 numPools; // defaults to 1 - uint16 share; // defaults to FULL_SHARE - bytes swapData; + // optional: ensure key exists in config/networks.json + function _ensureNetworkExists(string memory name) internal { + // will revert if the key path is missing + string memory json = vm.readFile("config/networks.json"); + // read the "..name" path to confirm key presence + string memory path = string.concat(".", name, ".name"); + string memory value = stdJson.readString(json, path); + if (bytes(value).length == 0) { + revert UnknownNetwork(name); + } } - // Helper for building multi-hop route function _buildMultiHopRoute( SwapTestParams[] memory hopParams, bytes[] memory hopData @@ -209,4 +179,35 @@ abstract contract BaseDexFacetTest is CoreRouteTestBase { { return 1_000 * 1e18; } + + // ==== Abstract Test Cases ==== + /** + * @notice Abstract test for basic token swapping functionality + * Each DEX implementation should override this + */ + function test_CanSwap() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap: Not implemented"); + } + + /** + * @notice Abstract test for swapping tokens from the DEX aggregator + * Each DEX implementation should override this + */ + function test_CanSwap_FromDexAggregator() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_FromDexAggregator: Not implemented"); + } + + /** + * @notice Abstract test for multi-hop swapping + * Each DEX implementation should override this + */ + function test_CanSwap_MultiHop() public virtual { + // Each DEX implementation must override this + // solhint-disable-next-line gas-custom-errors + revert("test_CanSwap_MultiHop: Not implemented"); + } } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 15f8c29b7..a09eca7ff 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -7,18 +7,27 @@ import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; +// ==== Base Contract ==== abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { + // ==== Storage Variables ==== UniV3StyleFacet internal uniV3Facet; + // ==== Types ==== struct UniV3SwapParams { address pool; SwapDirection direction; address recipient; } - // Add the custom error + struct UniV3AutoSwapParams { + CommandType commandType; + uint256 amountIn; + } + + // ==== Errors ==== error TokenNotInPool(address token, address pool); + // ==== Setup Functions ==== function _createFacetAndSelectors() internal override @@ -40,15 +49,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { // Each UniV3-style DEX must implement this to provide its specific callback selector function _getCallbackSelector() internal virtual returns (bytes4); - function test_CanSwap_MultiHop() public virtual override { - // SKIPPED: UniV3 forke dex multi-hop unsupported due to AS (amount specified) requirement. - // UniV3 forke dex does not support a "one-pool" second hop today, - // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. UniV3-style pools immediately revert on - // require(amountSpecified != 0, 'AS'), so you can't chain two uniV3 pools - // in a single processRoute invocation. - } - + // ==== Helper Functions ==== function _buildUniV3SwapData( UniV3SwapParams memory params ) internal view returns (bytes memory) { @@ -90,9 +91,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.stopPrank(); } - // === Additions below === - - // Infer swap direction from pool’s token0/token1 and TOKEN_IN + // Infer swap direction from pool's token0/token1 and TOKEN_IN function _getDirection( address pool, address tokenIn @@ -104,11 +103,6 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { revert TokenNotInPool(tokenIn, pool); } - struct UniV3AutoSwapParams { - CommandType commandType; - uint256 amountIn; - } - function _executeUniV3StyleSwapAuto( UniV3AutoSwapParams memory params ) internal { @@ -164,6 +158,16 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.stopPrank(); } + // ==== Test Cases ==== + function test_CanSwap_MultiHop() public virtual override { + // SKIPPED: UniV3 forke dex multi-hop unsupported due to AS (amount specified) requirement. + // UniV3 forke dex does not support a "one-pool" second hop today, + // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. UniV3-style pools immediately revert on + // require(amountSpecified != 0, 'AS'), so you can't chain two uniV3 pools + // in a single processRoute invocation. + } + function test_CanSwap() public virtual override { _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ @@ -186,7 +190,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { // No swap has armed the guard; expected == address(0) vm.startPrank(USER_SENDER); vm.expectRevert(LibCallbackManager.UnexpectedCallbackSender.selector); - // Call the facet’s callback directly on the diamond + // Call the facet's callback directly on the diamond (bool ok, ) = address(ldaDiamond).call( abi.encodeWithSelector( _getCallbackSelector(), diff --git a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol index 89c87e2fa..f9bd4292a 100644 --- a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol @@ -46,10 +46,10 @@ contract MockPullERC20Facet { } } -abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { +abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { using SafeERC20 for IERC20; - // Command codes for route processing + // ==== Types ==== enum CommandType { None, // 0 - not used ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) @@ -59,20 +59,6 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { ApplyPermit // 6 - applyPermit } - uint16 internal constant FULL_SHARE = 65535; - - CoreRouteFacet internal coreRouteFacet; - - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - struct ExpectedEvent { bool checkTopic1; bool checkTopic2; @@ -98,10 +84,29 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { CommandType commandType; } + // ==== Constants ==== + uint16 internal constant FULL_SHARE = 65535; + + // ==== Storage Variables ==== + CoreRouteFacet internal coreRouteFacet; + + // ==== Events ==== + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + + // ==== Errors ==== error InvalidTopicLength(); error TooManyIndexedParams(); error DynamicParamsNotSupported(); + // ==== Setup Functions ==== function setUp() public virtual override { LdaDiamondTest.setUp(); _addCoreRouteFacet(); @@ -115,6 +120,7 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); } + // ==== Helper Functions ==== function _buildBaseRoute( SwapTestParams memory params, bytes memory swapData @@ -420,14 +426,16 @@ abstract contract CoreRouteTestBase is LdaDiamondTest, TestHelpers { } } -contract CoreRouteFacetTest is CoreRouteTestBase { +// ==== Main Test Contract ==== +contract CoreRouteFacetTest is BaseCoreRouteTest { using SafeTransferLib for address; + // ==== Storage Variables ==== bytes4 internal pullSel; + // ==== Setup Functions ==== function setUp() public override { - CoreRouteTestBase.setUp(); - + BaseCoreRouteTest.setUp(); // Register mock pull facet once and store selector MockPullERC20Facet mockPull = new MockPullERC20Facet(); bytes4[] memory sel = new bytes4[](1); @@ -436,8 +444,7 @@ contract CoreRouteFacetTest is CoreRouteTestBase { pullSel = sel[0]; } - // --- Helpers --- - + // ==== Helper Functions ==== function _addMockNativeFacet() internal { MockNativeFacet mock = new MockNativeFacet(); bytes4[] memory selectors = new bytes4[](1); @@ -480,8 +487,7 @@ contract CoreRouteFacetTest is CoreRouteTestBase { (v, r, s) = vm.sign(ownerPk, digest); } - // --- Tests --- - + // ==== Test Cases ==== function test_ProcessNativeCommandSendsEthToRecipient() public { _addMockNativeFacet(); @@ -573,7 +579,6 @@ contract CoreRouteFacetTest is CoreRouteTestBase { ); } - // Revert tests - use testRevert_ prefix function testRevert_WhenCommandCodeIsUnknown() public { bytes memory route = abi.encodePacked(uint8(9)); diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 894aaa57d..61d1f2cd8 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -12,7 +12,9 @@ import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +// ==== Helper Contracts ==== contract AlgebraLiquidityAdderHelper { + // ==== Storage Variables ==== address public immutable TOKEN_0; address public immutable TOKEN_1; @@ -21,6 +23,7 @@ contract AlgebraLiquidityAdderHelper { TOKEN_1 = _token1; } + // ==== External Functions ==== function addLiquidity( address pool, int24 bottomTick, @@ -97,19 +100,22 @@ contract AlgebraLiquidityAdderHelper { } } +// ==== Main Test Contract ==== contract AlgebraFacetTest is BaseDexFacetTest { + // ==== Storage Variables ==== AlgebraFacet internal algebraFacet; + // ==== Constants ==== address private constant ALGEBRA_FACTORY_APECHAIN = 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; address private constant ALGEBRA_QUOTER_V2_APECHAIN = 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; address private constant RANDOM_APE_ETH_HOLDER_APECHAIN = address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); - address private constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; + // ==== Types ==== struct AlgebraSwapTestParams { address from; address to; @@ -120,8 +126,29 @@ contract AlgebraFacetTest is BaseDexFacetTest { bool supportsFeeOnTransfer; } + struct MultiHopTestState { + IERC20 tokenA; + IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken + IERC20 tokenC; + address pool1; + address pool2; + uint256 amountIn; + uint256 amountToTransfer; + bool isFeeOnTransfer; + } + + struct AlgebraRouteParams { + CommandType commandCode; // 1 for contract funds, 2 for user funds + address tokenIn; // Input token address + address recipient; // Address receiving the output tokens + address pool; // Algebra pool address + bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens + } + + // ==== Errors ==== error AlgebraSwapUnexpected(); + // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "apechain", @@ -162,7 +189,7 @@ contract AlgebraFacetTest is BaseDexFacetTest { return 1_000 * 1e6; } - // Override the abstract test with Algebra implementation + // ==== Test Cases ==== function test_CanSwap_FromDexAggregator() public override { // Fund LDA from whale address vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); @@ -308,7 +335,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { _executeAndVerifyMultiHopSwap(state); } - // Test that the proper error is thrown when algebra swap fails function testRevert_SwapUnexpected() public { // Transfer tokens from whale to user vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); @@ -377,7 +403,172 @@ contract AlgebraFacetTest is BaseDexFacetTest { vm.clearMockedCalls(); } - // Helper function to setup tokens and pools + function testRevert_AlgebraSwap_ZeroAddressPool() public { + // Transfer tokens from whale to user + vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); + IERC20(tokenIn).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Mock token0() call on address(0) + vm.mockCall( + address(0), + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(tokenIn) + ); + + // Build route with address(0) as pool + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: address(tokenIn), + recipient: USER_SENDER, + pool: address(0), // Zero address pool + supportsFeeOnTransfer: true + }) + ); + + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route, + InvalidCallData.selector + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { + // // Transfer tokens from whale to user + // vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); + // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); + + // vm.startPrank(USER_SENDER); + + // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS + // vm.mockCall( + // IMPOSSIBLE_POOL_ADDRESS, + // abi.encodeWithSelector(IAlgebraPool.token0.selector), + // abi.encode(APE_ETH_TOKEN) + // ); + + // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool + // bytes memory swapData = _buildAlgebraSwapData( + // AlgebraRouteParams({ + // commandCode: CommandType.ProcessUserERC20, + // tokenIn: APE_ETH_TOKEN, + // recipient: USER_SENDER, + // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address + // supportsFeeOnTransfer: true + // }) + // ); + + // bytes memory route = abi.encodePacked( + // uint8(CommandType.ProcessUserERC20), + // APE_ETH_TOKEN, + // uint8(1), // number of pools/splits + // FULL_SHARE, // 100% share + // uint16(swapData.length), // <--- Add the length prefix + // swapData + // ); + + // // Approve tokens + // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); + + // // Expect revert with InvalidCallData + // vm.expectRevert(InvalidCallData.selector); + + // coreRouteFacet.processRoute( + // APE_ETH_TOKEN, + // 1 * 1e18, + // address(WETH_TOKEN), + // 0, + // USER_SENDER, + // route + // ); + + // vm.stopPrank(); + // vm.clearMockedCalls(); + // } + + function testRevert_AlgebraSwap_ZeroAddressRecipient() public { + // Transfer tokens from whale to user + vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); + IERC20(tokenIn).transfer(USER_SENDER, 1 * 1e18); + + vm.startPrank(USER_SENDER); + + // Mock token0() call on the pool + vm.mockCall( + poolInOut, + abi.encodeWithSelector(IAlgebraPool.token0.selector), + abi.encode(tokenIn) + ); + + // Build route with address(0) as recipient + bytes memory swapData = _buildAlgebraSwapData( + AlgebraRouteParams({ + commandCode: CommandType.ProcessUserERC20, + tokenIn: address(tokenIn), + recipient: address(0), // Zero address recipient + pool: poolInOut, + supportsFeeOnTransfer: true + }) + ); + + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route, + InvalidCallData.selector + ); + + vm.stopPrank(); + vm.clearMockedCalls(); + } + + // ==== Helper Functions ==== function _setupTokensAndPools( MultiHopTestState memory state ) private returns (MultiHopTestState memory) { @@ -462,7 +653,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { return state; } - // Helper function to execute and verify the swap function _executeAndVerifyMultiHopSwap( MultiHopTestState memory state ) private { @@ -540,7 +730,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { vm.stopPrank(); } - // Helper function to create an Algebra pool function _createAlgebraPool( address tokenA, address tokenB @@ -553,7 +742,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { return pool; } - // Helper function to add liquidity to a pool function _addLiquidityToPool( address pool, address token0, @@ -610,26 +798,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { ); } - struct MultiHopTestState { - IERC20 tokenA; - IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken - IERC20 tokenC; - address pool1; - address pool2; - uint256 amountIn; - uint256 amountToTransfer; - bool isFeeOnTransfer; - } - - struct AlgebraRouteParams { - CommandType commandCode; // 1 for contract funds, 2 for user funds - address tokenIn; // Input token address - address recipient; // Address receiving the output tokens - address pool; // Algebra pool address - bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens - } - - // Helper function to build route for Apechain Algebra swap function _buildAlgebraSwapData( AlgebraRouteParams memory params ) private view returns (bytes memory) { @@ -650,7 +818,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { ); } - // Helper function to test an Algebra swap function _testSwap(AlgebraSwapTestParams memory params) internal { // Find or create a pool address pool = _getPool(params.tokenIn, params.tokenOut); @@ -731,169 +898,4 @@ contract AlgebraFacetTest is BaseDexFacetTest { .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); return amountOut; } - - function testRevert_AlgebraSwap_ZeroAddressPool() public { - // Transfer tokens from whale to user - vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); - IERC20(tokenIn).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Mock token0() call on address(0) - vm.mockCall( - address(0), - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(tokenIn) - ); - - // Build route with address(0) as pool - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: address(tokenIn), - recipient: USER_SENDER, - pool: address(0), // Zero address pool - supportsFeeOnTransfer: true - }) - ); - - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route, - InvalidCallData.selector - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { - // // Transfer tokens from whale to user - // vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); - // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - // vm.startPrank(USER_SENDER); - - // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS - // vm.mockCall( - // IMPOSSIBLE_POOL_ADDRESS, - // abi.encodeWithSelector(IAlgebraPool.token0.selector), - // abi.encode(APE_ETH_TOKEN) - // ); - - // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool - // bytes memory swapData = _buildAlgebraSwapData( - // AlgebraRouteParams({ - // commandCode: CommandType.ProcessUserERC20, - // tokenIn: APE_ETH_TOKEN, - // recipient: USER_SENDER, - // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address - // supportsFeeOnTransfer: true - // }) - // ); - - // bytes memory route = abi.encodePacked( - // uint8(CommandType.ProcessUserERC20), - // APE_ETH_TOKEN, - // uint8(1), // number of pools/splits - // FULL_SHARE, // 100% share - // uint16(swapData.length), // <--- Add the length prefix - // swapData - // ); - - // // Approve tokens - // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // // Expect revert with InvalidCallData - // vm.expectRevert(InvalidCallData.selector); - - // coreRouteFacet.processRoute( - // APE_ETH_TOKEN, - // 1 * 1e18, - // address(WETH_TOKEN), - // 0, - // USER_SENDER, - // route - // ); - - // vm.stopPrank(); - // vm.clearMockedCalls(); - // } - - function testRevert_AlgebraSwap_ZeroAddressRecipient() public { - // Transfer tokens from whale to user - vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); - IERC20(tokenIn).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Mock token0() call on the pool - vm.mockCall( - poolInOut, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(tokenIn) - ); - - // Build route with address(0) as recipient - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: address(tokenIn), - recipient: address(0), // Zero address recipient - pool: poolInOut, - supportsFeeOnTransfer: true - }) - ); - - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route, - InvalidCallData.selector - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } } diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 5df12347a..1b4e2deaa 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -6,6 +6,7 @@ import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { + // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "flare", diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index d4c8a2380..545b443c8 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -7,6 +7,7 @@ import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { + // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "hyperevm", diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index d99ceedd3..0d9eab827 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; +// ==== Imports ==== import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; @@ -11,6 +12,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { + // ==== Events ==== event HookCalled( address sender, uint256 amount0, @@ -28,10 +30,12 @@ contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { } } +// ==== Main Test Contract ==== contract VelodromeV2FacetTest is BaseDexFacetTest { + // ==== Storage Variables ==== VelodromeV2Facet internal velodromeV2Facet; - // ==================== Velodrome V2 specific variables ==================== + // ==== Constants ==== IVelodromeV2Router internal constant VELODROME_V2_ROUTER = IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router address internal constant VELODROME_V2_FACTORY_REGISTRY = @@ -40,13 +44,12 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { MockVelodromeV2FlashLoanCallbackReceiver internal mockFlashloanCallbackReceiver; - // Callback constants + // ==== Types ==== enum CallbackStatus { Disabled, // 0 Enabled // 1 } - // Velodrome V2 structs struct VelodromeV2SwapTestParams { address from; address to; @@ -84,6 +87,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { CallbackStatus callbackStatus; } + // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "optimism", @@ -124,7 +128,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { return 1_000 * 1e6; } - // ============================ Velodrome V2 Tests ============================ + // ==== Test Cases ==== // no stable swap function test_CanSwap() public override { @@ -633,7 +637,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { vm.clearMockedCalls(); } - // ============================ Velodrome V2 Helper Functions ============================ + // ==== Helper Functions ==== /** * @dev Helper function to test a VelodromeV2 swap. diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 3c1e12706..1266c7647 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -6,6 +6,7 @@ import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { + // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "xdc", blockNumber: 89279495 }); } From 51f9ac2d3553810a90f8d478122d06da642a54e3 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 19 Aug 2025 17:09:29 +0200 Subject: [PATCH 042/220] Update EmergencyPauseFacet import paths and add new facet for emergency pause functionality. enhance tests to reflect changes in imports and structure --- .solhint.json | 1 - .../facets/DeployEmergencyPauseFacet.s.sol | 2 +- .../DeployEmergencyPauseFacet.zksync.s.sol | 2 +- src/Periphery/Lda/Facets/AlgebraFacet.sol | 24 +- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 165 ++-- .../EmergencyPauseFacet.sol | 0 .../EmergencyPauseFacet.fork.t.sol | 2 +- .../EmergencyPauseFacet.local.t.sol | 4 +- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 410 +++++++++ .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 2 +- .../Periphery/Lda/CoreRouteFacet.t.sol | 843 ------------------ .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 439 +++++++++ test/solidity/utils/DiamondTest.sol | 10 +- test/solidity/utils/MockNativeFacet.sol | 21 + test/solidity/utils/MockPullERC20Facet.sol | 28 + 15 files changed, 1023 insertions(+), 930 deletions(-) rename src/{Facets => Security}/EmergencyPauseFacet.sol (100%) create mode 100644 test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol delete mode 100644 test/solidity/Periphery/Lda/CoreRouteFacet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol create mode 100644 test/solidity/utils/MockNativeFacet.sol create mode 100644 test/solidity/utils/MockPullERC20Facet.sol diff --git a/.solhint.json b/.solhint.json index 1d57bd7c7..c0c4e08e7 100644 --- a/.solhint.json +++ b/.solhint.json @@ -24,7 +24,6 @@ "imports-on-top": "error", "indent": ["error", 4], "interface-starts-with-i": "off", - "max-line-length": ["error", 125], "max-states-count": ["error", 15], "no-empty-blocks": "off", "no-global-import": "error", diff --git a/script/deploy/facets/DeployEmergencyPauseFacet.s.sol b/script/deploy/facets/DeployEmergencyPauseFacet.s.sol index 3fa8b8dec..87245eb7b 100644 --- a/script/deploy/facets/DeployEmergencyPauseFacet.s.sol +++ b/script/deploy/facets/DeployEmergencyPauseFacet.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; -import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; contract DeployScript is DeployScriptBase { using stdJson for string; diff --git a/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol b/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol index 3fa8b8dec..87245eb7b 100644 --- a/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol +++ b/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; -import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; contract DeployScript is DeployScriptBase { using stdJson for string; diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 79cf9db6c..2d0be1270 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -12,26 +12,37 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title AlgebraFacet /// @author LI.FI (https://li.fi) /// @notice Handles Algebra swaps with callback management +/// @dev Implements direct selector-callable swap function for Algebra pools /// @custom:version 1.0.0 contract AlgebraFacet { using LibPackedStream for uint256; using SafeERC20 for IERC20; // ==== Constants ==== + /// @dev Minimum sqrt price ratio for Algebra pool swaps uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev Maximum sqrt price ratio for Algebra pool swaps uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; // ==== Errors ==== + /// @dev Thrown when callback verification fails or unexpected callback state error AlgebraSwapUnexpected(); // ==== External Functions ==== + /// @notice Executes a swap through an Algebra pool + /// @dev Handles both regular swaps and fee-on-transfer token swaps + /// @param swapData Encoded swap parameters [pool, direction, recipient, supportsFeeOnTransfer] + /// @param from Token source address - if equals msg.sender, + /// tokens will be pulled from the caller; otherwise assumes tokens are already at this contract + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens function swapAlgebra( bytes memory swapData, address from, address tokenIn, uint256 amountIn - ) external returns (uint256) { + ) external { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); bool direction = stream.readUint8() > 0; @@ -73,10 +84,17 @@ contract AlgebraFacet { if (LibCallbackManager.callbackStorage().expected != address(0)) { revert AlgebraSwapUnexpected(); } - - return 0; } + /// @notice Called by Algebra pool after executing a swap via IAlgebraPool#swap + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be verified to be an AlgebraPool using LibCallbackManager. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IAlgebraPool#swap call. Contains the input token address. function algebraSwapCallback( int256 amount0Delta, int256 amount1Delta, diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 19f16688e..f2e64935b 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -3,19 +3,20 @@ pragma solidity ^0.8.17; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; - import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibUtil } from "lifi/Libraries/LibUtil.sol"; import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; +import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol"; +import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; /// @title CoreRouteFacet /// @author LI.FI (https://li.fi) -/// @notice Orchestrates LDA route execution by interpreting a compact byte stream. -/// @dev Public surface (ABI) is preserved; internals are reorganized for clarity. +/// @notice Orchestrates LDA route execution using direct function selector dispatch +/// @dev Implements selector-based routing where each DEX facet's swap function is called directly via its selector /// @custom:version 1.0.0 -contract CoreRouteFacet is ReentrancyGuard { +contract CoreRouteFacet is ReentrancyGuard, WithdrawablePeriphery { using SafeERC20 for IERC20; using SafeERC20 for IERC20Permit; using LibPackedStream for uint256; @@ -42,7 +43,19 @@ contract CoreRouteFacet is ReentrancyGuard { error SwapFailed(); error UnknownSelector(); + constructor(address _owner) WithdrawablePeriphery(_owner) { + if (_owner == address(0)) revert InvalidConfig(); + } + // ==== External Functions ==== + /// @notice Process a route encoded with function selectors for direct DEX facet dispatch + /// @param tokenIn The input token address (address(0) for native) + /// @param amountIn The amount of input tokens + /// @param tokenOut The expected output token address (address(0) for native) + /// @param amountOutMin The minimum acceptable output amount + /// @param to The recipient address for the output tokens + /// @param route The encoded route data containing function selectors and parameters + /// @return amountOut The actual amount of output tokens received function processRoute( address tokenIn, uint256 amountIn, @@ -63,6 +76,15 @@ contract CoreRouteFacet is ReentrancyGuard { } // ==== Private Functions - Core Logic ==== + /// @notice Executes a route with balance checks and event emission + /// @dev Handles both native and ERC20 tokens with pre/post balance validation + /// @param tokenIn The input token address (address(0) for native) + /// @param amountIn The amount of input tokens + /// @param tokenOut The expected output token address (address(0) for native) + /// @param amountOutMin The minimum acceptable output amount + /// @param to The recipient address for the output tokens + /// @param route The encoded route data containing function selectors and parameters + /// @return amountOut The actual amount of output tokens received function _executeRoute( address tokenIn, uint256 amountIn, @@ -71,23 +93,34 @@ contract CoreRouteFacet is ReentrancyGuard { address to, bytes calldata route ) private returns (uint256 amountOut) { - (uint256 balInStart, uint256 balOutStart) = _precheck( - tokenIn, - tokenOut, - to - ); + uint256 balInInitial = LibAsset.isNativeAsset(tokenIn) + ? 0 + : IERC20(tokenIn).balanceOf(msg.sender); + + uint256 balOutInitial = LibAsset.isNativeAsset(tokenOut) + ? address(to).balance + : IERC20(tokenOut).balanceOf(to); uint256 realAmountIn = _runRoute(tokenIn, amountIn, route); - amountOut = _postcheck( - tokenIn, - tokenOut, - to, - amountIn, - amountOutMin, - balInStart, - balOutStart - ); + uint256 balInFinal = LibAsset.isNativeAsset(tokenIn) + ? 0 + : IERC20(tokenIn).balanceOf(msg.sender); + if (balInFinal + amountIn < balInInitial) { + revert MinimalInputBalanceViolation( + balInFinal + amountIn, + balInInitial + ); + } + + uint256 balOutFinal = LibAsset.isNativeAsset(tokenOut) + ? address(to).balance + : IERC20(tokenOut).balanceOf(to); + if (balOutFinal < balOutInitial + amountOutMin) { + revert MinimalOutputBalanceViolation(balOutFinal - balOutInitial); + } + + amountOut = balOutFinal - balOutInitial; emit Route( msg.sender, @@ -100,23 +133,12 @@ contract CoreRouteFacet is ReentrancyGuard { ); } - /// @notice Capture initial balances for input/output accounting. - function _precheck( - address tokenIn, - address tokenOut, - address to - ) private view returns (uint256 balInStart, uint256 balOutStart) { - balInStart = LibAsset.isNativeAsset(tokenIn) - ? 0 - : IERC20(tokenIn).balanceOf(msg.sender); - - balOutStart = LibAsset.isNativeAsset(tokenOut) - ? address(to).balance - : IERC20(tokenOut).balanceOf(to); - } - - /// @notice Interpret the `route` byte stream and perform all commanded actions. - /// @return realAmountIn The actual first-hop amount determined by the route. + /// @notice Interprets and executes commands from the route byte stream + /// @dev Processes commands in sequence: ERC20, native, permits, and pool interactions + /// @param tokenIn The input token address + /// @param declaredAmountIn The declared input amount + /// @param route The encoded route data + /// @return realAmountIn The actual amount used in the first hop function _runRoute( address tokenIn, uint256 declaredAmountIn, @@ -149,39 +171,12 @@ contract CoreRouteFacet is ReentrancyGuard { } } - /// @notice Validate post-conditions and determine `amountOut`. - function _postcheck( - address tokenIn, - address tokenOut, - address to, - uint256 declaredAmountIn, - uint256 minAmountOut, - uint256 balInStart, - uint256 balOutStart - ) private view returns (uint256 amountOut) { - uint256 balInFinal = LibAsset.isNativeAsset(tokenIn) - ? 0 - : IERC20(tokenIn).balanceOf(msg.sender); - if (balInFinal + declaredAmountIn < balInStart) { - revert MinimalInputBalanceViolation( - balInFinal + declaredAmountIn, - balInStart - ); - } - - uint256 balOutFinal = LibAsset.isNativeAsset(tokenOut) - ? address(to).balance - : IERC20(tokenOut).balanceOf(to); - if (balOutFinal < balOutStart + minAmountOut) { - revert MinimalOutputBalanceViolation(balOutFinal - balOutStart); - } - - amountOut = balOutFinal - balOutStart; - } - // ==== Private Functions - Command Handlers ==== - /// @notice ERC-2612 permit application for `tokenIn`. + /// @notice Applies ERC20 permit for token approval + /// @dev Reads permit parameters from the stream and calls permit on the token + /// @param tokenIn The token to approve + /// @param cur The current position in the byte stream function _applyPermit(address tokenIn, uint256 cur) private { uint256 value = cur.readUint256(); uint256 deadline = cur.readUint256(); @@ -199,13 +194,19 @@ contract CoreRouteFacet is ReentrancyGuard { ); } - /// @notice Handle native coin inputs (assumes value already present on this contract). + /// @notice Handles native token (ETH) inputs + /// @dev Assumes ETH is already present on the contract + /// @param cur The current position in the byte stream + /// @return total The total amount of ETH to process function _handleNative(uint256 cur) private returns (uint256 total) { total = address(this).balance; _distributeAndSwap(cur, address(this), INTERNAL_INPUT_SOURCE, total); } - /// @notice Pull ERC20 from this contract’s balance and process it. + /// @notice Processes ERC20 tokens already on this contract + /// @dev Includes protection against full balance draining + /// @param cur The current position in the byte stream + /// @return total The total amount of tokens to process function _handleSelfERC20(uint256 cur) private returns (uint256 total) { address token = cur.readAddress(); total = IERC20(token).balanceOf(address(this)); @@ -215,19 +216,26 @@ contract CoreRouteFacet is ReentrancyGuard { _distributeAndSwap(cur, address(this), token, total); } - /// @notice Pull ERC20 from the caller and process it. + /// @notice Processes ERC20 tokens from the caller + /// @param cur The current position in the byte stream + /// @param total The total amount to process function _handleUserERC20(uint256 cur, uint256 total) private { address token = cur.readAddress(); _distributeAndSwap(cur, msg.sender, token, total); } - /// @notice Process a “single pool” hop where inputs are already resident in the pool. + /// @notice Processes a pool interaction where tokens are already in the pool + /// @param cur The current position in the byte stream function _handleSinglePool(uint256 cur) private { address token = cur.readAddress(); _dispatchSwap(cur, INTERNAL_INPUT_SOURCE, token, 0); } - /// @notice Split an amount across N pools and trigger swaps. + /// @notice Distributes tokens across multiple pools based on share ratios + /// @param cur The current position in the byte stream + /// @param from The source address for tokens + /// @param tokenIn The token being distributed + /// @param total The total amount to distribute function _distributeAndSwap( uint256 cur, address from, @@ -245,7 +253,12 @@ contract CoreRouteFacet is ReentrancyGuard { } } - /// @notice Extract selector and payload and delegate the call to the facet that implements it. + /// @notice Dispatches a swap call to the appropriate DEX facet + /// @dev Uses direct selector dispatch with optimized calldata construction + /// @param cur The current position in the byte stream + /// @param from The source address for tokens + /// @param tokenIn The input token address + /// @param amountIn The amount of tokens to swap function _dispatchSwap( uint256 cur, address from, @@ -312,7 +325,9 @@ contract CoreRouteFacet is ReentrancyGuard { // ==== Private Functions - Helpers ==== - /// @dev Extracts the first 4 bytes as a selector. + /// @notice Extracts function selector from calldata + /// @param blob The calldata bytes + /// @return sel The extracted selector function _readSelector( bytes memory blob ) private pure returns (bytes4 sel) { @@ -321,7 +336,9 @@ contract CoreRouteFacet is ReentrancyGuard { } } - /// @dev Returns a fresh bytes containing the original blob without the first 4 bytes. + /// @notice Creates a new bytes array without the selector + /// @param blob The original calldata bytes + /// @return payload The calldata without selector function _payloadFrom( bytes memory blob ) private pure returns (bytes memory payload) { diff --git a/src/Facets/EmergencyPauseFacet.sol b/src/Security/EmergencyPauseFacet.sol similarity index 100% rename from src/Facets/EmergencyPauseFacet.sol rename to src/Security/EmergencyPauseFacet.sol diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol index fb414aebe..defd6ae92 100644 --- a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { LibAllowList, TestBase } from "../../utils/TestBase.sol"; import { OnlyContractOwner, UnAuthorized, DiamondIsPaused } from "src/Errors/GenericErrors.sol"; -import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol index 429a62107..1611d8af5 100644 --- a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.17; import { TestBase } from "../../utils/TestBase.sol"; -import { OnlyContractOwner, InvalidCallData, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist } from "src/Errors/GenericErrors.sol"; -import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { OnlyContractOwner, InvalidCallData, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist } from "lifi/Errors/GenericErrors.sol"; +import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol new file mode 100644 index 000000000..b974a6dcc --- /dev/null +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { TestHelpers } from "../../utils/TestHelpers.sol"; +import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; + +contract MockPullERC20Facet { + using SafeERC20 for IERC20; + + // Pulls `amountIn` from msg.sender if `from == msg.sender` + function pull( + bytes memory /*payload*/, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + if (from == msg.sender) { + IERC20(tokenIn).safeTransferFrom( + msg.sender, + address(this), + amountIn + ); + } + return amountIn; + } +} + +abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { + using SafeERC20 for IERC20; + + // ==== Types ==== + enum CommandType { + None, // 0 - not used + ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) + ProcessUserERC20, // 2 - processUserERC20 (User's funds) + ProcessNative, // 3 - processNative + ProcessOnePool, // 4 - processOnePool (Pool's funds) + ApplyPermit // 6 - applyPermit + } + + struct ExpectedEvent { + bool checkTopic1; + bool checkTopic2; + bool checkTopic3; + bool checkData; + bytes32 eventSelector; // The event selector (keccak256 hash of the event signature) + bytes[] eventParams; // The event parameters, each encoded separately + uint8[] indexedParamIndices; // indices of params that are indexed (→ topics 1..3) + } + + struct RouteEventVerification { + uint256 expectedExactOut; // Only for event verification + bool checkData; + } + + struct SwapTestParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint256 minOut; + address sender; + address recipient; + CommandType commandType; + } + + // ==== Constants ==== + uint16 internal constant FULL_SHARE = 65535; + + // ==== Storage Variables ==== + CoreRouteFacet internal coreRouteFacet; + + // ==== Events ==== + event Route( + address indexed from, + address to, + address indexed tokenIn, + address indexed tokenOut, + uint256 amountIn, + uint256 amountOutMin, + uint256 amountOut + ); + + // ==== Errors ==== + error InvalidTopicLength(); + error TooManyIndexedParams(); + error DynamicParamsNotSupported(); + + // ==== Setup Functions ==== + function setUp() public virtual override { + LdaDiamondTest.setUp(); + _addCoreRouteFacet(); + } + + function _addCoreRouteFacet() internal { + coreRouteFacet = new CoreRouteFacet(USER_DIAMOND_OWNER); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = CoreRouteFacet.processRoute.selector; + addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); + coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); + } + + // ==== Helper Functions ==== + function _buildBaseRoute( + SwapTestParams memory params, + bytes memory swapData + ) internal pure returns (bytes memory) { + if (params.commandType == CommandType.ProcessNative) { + return + abi.encodePacked( + uint8(params.commandType), + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ); + } else if (params.commandType == CommandType.ProcessOnePool) { + return + abi.encodePacked( + uint8(params.commandType), + params.tokenIn, + uint16(swapData.length), + swapData + ); + } else { + return + abi.encodePacked( + uint8(params.commandType), + params.tokenIn, + uint8(1), + FULL_SHARE, + uint16(swapData.length), + swapData + ); + } + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + ExpectedEvent[] memory additionalEvents, + bool isFeeOnTransferToken, + RouteEventVerification memory routeEventVerification + ) internal { + if ( + params.commandType != CommandType.ProcessMyERC20 && + !LibAsset.isNativeAsset(params.tokenIn) + ) { + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + } + + uint256 inBefore; + uint256 outBefore = LibAsset.isNativeAsset(params.tokenOut) + ? params.recipient.balance + : IERC20(params.tokenOut).balanceOf(params.recipient); + + // For aggregator funds, check the diamond's balance + if (params.commandType == CommandType.ProcessMyERC20) { + inBefore = LibAsset.isNativeAsset(params.tokenIn) + ? address(ldaDiamond).balance + : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + } else { + inBefore = LibAsset.isNativeAsset(params.tokenIn) + ? params.sender.balance + : IERC20(params.tokenIn).balanceOf(params.sender); + } + + address fromAddress = params.sender == address(ldaDiamond) + ? USER_SENDER + : params.sender; + + _expectEvents(additionalEvents); + + vm.expectEmit(true, true, true, routeEventVerification.checkData); + emit Route( + fromAddress, + params.recipient, + params.tokenIn, + params.tokenOut, + params.amountIn, + params.minOut, + routeEventVerification.expectedExactOut + ); + + // For native token, send value with the call + if (LibAsset.isNativeAsset(params.tokenIn)) { + coreRouteFacet.processRoute{ value: params.amountIn }( + params.tokenIn, + params.amountIn, + params.tokenOut, + params.minOut, + params.recipient, + route + ); + } else { + coreRouteFacet.processRoute( + params.tokenIn, + params.amountIn, + params.tokenOut, + params.minOut, + params.recipient, + route + ); + } + + uint256 inAfter; + uint256 outAfter = LibAsset.isNativeAsset(params.tokenOut) + ? params.recipient.balance + : IERC20(params.tokenOut).balanceOf(params.recipient); + + // Check balance change on the correct address + if (params.commandType == CommandType.ProcessMyERC20) { + inAfter = LibAsset.isNativeAsset(params.tokenIn) + ? address(ldaDiamond).balance + : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); + } else { + inAfter = LibAsset.isNativeAsset(params.tokenIn) + ? params.sender.balance + : IERC20(params.tokenIn).balanceOf(params.sender); + } + + // Use assertEq or assertApproxEqAbs based on isFeeOnTransferToken + if (isFeeOnTransferToken) { + assertApproxEqAbs( + inBefore - inAfter, + params.amountIn, + 1, // Allow 1 wei difference for fee-on-transfer tokens + "Token spent mismatch" + ); + } else { + assertEq( + inBefore - inAfter, + params.amountIn, + "Token spent mismatch" + ); + } + + assertGt(outAfter - outBefore, 0, "Should receive tokens"); + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + ExpectedEvent[] memory additionalEvents, + bool isFeeOnTransferToken + ) internal { + _executeAndVerifySwap( + params, + route, + additionalEvents, + isFeeOnTransferToken, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route + ) internal { + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + false, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + bool isFeeOnTransferToken + ) internal { + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + isFeeOnTransferToken, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + + // Keep the revert case separate + function _executeAndVerifySwap( + SwapTestParams memory params, + bytes memory route, + bytes4 expectedRevert + ) internal { + if (params.commandType != CommandType.ProcessMyERC20) { + IERC20(params.tokenIn).approve( + address(ldaDiamond), + params.amountIn + ); + } + + vm.expectRevert(expectedRevert); + coreRouteFacet.processRoute( + params.tokenIn, + params.commandType == CommandType.ProcessMyERC20 + ? params.amountIn + : params.amountIn - 1, + params.tokenOut, + 0, // minOut = 0 for tests + params.recipient, + route + ); + } + + // helper: load a 32-byte topic from a 32-byte abi.encode(param) + function _asTopic(bytes memory enc) internal pure returns (bytes32 topic) { + if (enc.length != 32) revert InvalidTopicLength(); + assembly { + topic := mload(add(enc, 32)) + } + } + + /** + * @notice Sets up event expectations for a list of events + * @param events Array of events to expect + */ + function _expectEvents(ExpectedEvent[] memory events) internal { + for (uint256 i = 0; i < events.length; i++) { + _expectEvent(events[i]); + } + } + + /** + * @notice Sets up expectation for a single event + * @param evt The event to expect with its check parameters and data + */ + function _expectEvent(ExpectedEvent memory evt) internal { + vm.expectEmit( + evt.checkTopic1, + evt.checkTopic2, + evt.checkTopic3, + evt.checkData + ); + + // Build topics (topic0 = selector; topics1..3 from indexedParamIndices) + bytes32 topic0 = evt.eventSelector; + uint8[] memory idx = evt.indexedParamIndices; + + bytes32 t1; + bytes32 t2; + bytes32 t3; + + uint256 topicsCount = idx.length; + if (topicsCount > 3) { + revert TooManyIndexedParams(); + } + if (topicsCount >= 1) { + t1 = _asTopic(evt.eventParams[idx[0]]); + } + if (topicsCount >= 2) { + t2 = _asTopic(evt.eventParams[idx[1]]); + } + if (topicsCount == 3) { + t3 = _asTopic(evt.eventParams[idx[2]]); + } + + // Build data (non-indexed params in event order) + bytes memory data; + if (evt.checkData) { + // Only support static params for now (each abi.encode(param) must be 32 bytes) + uint256 total = evt.eventParams.length; + bool[8] memory isIndexed; // up to 8 params; expand if needed + for (uint256 k = 0; k < topicsCount; k++) { + uint8 pos = idx[k]; + if (pos < isIndexed.length) isIndexed[pos] = true; + } + + for (uint256 p = 0; p < total; p++) { + if (!isIndexed[p]) { + bytes memory enc = evt.eventParams[p]; + if (enc.length != 32) { + revert DynamicParamsNotSupported(); + } + data = bytes.concat(data, enc); + } + } + } else { + data = ""; + } + + // Emit raw log with correct number of topics + assembly { + let ptr := add(data, 0x20) + let len := mload(data) + switch topicsCount + case 0 { + log1(ptr, len, topic0) + } + case 1 { + log2(ptr, len, topic0, t1) + } + case 2 { + log3(ptr, len, topic0, t1, t2) + } + case 3 { + log4(ptr, len, topic0, t1, t2, t3) + } + } + } +} diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index bbc813652..6897d06fc 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { BaseCoreRouteTest } from "./CoreRouteFacet.t.sol"; +import { BaseCoreRouteTest } from "./BaseCoreRouteTest.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; /** diff --git a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol deleted file mode 100644 index f9bd4292a..000000000 --- a/test/solidity/Periphery/Lda/CoreRouteFacet.t.sol +++ /dev/null @@ -1,843 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; - -contract MockNativeFacet { - using SafeTransferLib for address; - - function handleNative( - bytes memory payload, - address /*from*/, - address /*tokenIn*/, - uint256 amountIn - ) external payable returns (uint256) { - address recipient = abi.decode(payload, (address)); - recipient.safeTransferETH(amountIn); - return amountIn; - } -} - -contract MockPullERC20Facet { - using SafeERC20 for IERC20; - - // Pulls `amountIn` from msg.sender if `from == msg.sender` - function pull( - bytes memory /*payload*/, - address from, - address tokenIn, - uint256 amountIn - ) external returns (uint256) { - if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(this), - amountIn - ); - } - return amountIn; - } -} - -abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { - using SafeERC20 for IERC20; - - // ==== Types ==== - enum CommandType { - None, // 0 - not used - ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) - ProcessUserERC20, // 2 - processUserERC20 (User's funds) - ProcessNative, // 3 - processNative - ProcessOnePool, // 4 - processOnePool (Pool's funds) - ApplyPermit // 6 - applyPermit - } - - struct ExpectedEvent { - bool checkTopic1; - bool checkTopic2; - bool checkTopic3; - bool checkData; - bytes32 eventSelector; // The event selector (keccak256 hash of the event signature) - bytes[] eventParams; // The event parameters, each encoded separately - uint8[] indexedParamIndices; // indices of params that are indexed (→ topics 1..3) - } - - struct RouteEventVerification { - uint256 expectedExactOut; // Only for event verification - bool checkData; - } - - struct SwapTestParams { - address tokenIn; - address tokenOut; - uint256 amountIn; - uint256 minOut; - address sender; - address recipient; - CommandType commandType; - } - - // ==== Constants ==== - uint16 internal constant FULL_SHARE = 65535; - - // ==== Storage Variables ==== - CoreRouteFacet internal coreRouteFacet; - - // ==== Events ==== - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - - // ==== Errors ==== - error InvalidTopicLength(); - error TooManyIndexedParams(); - error DynamicParamsNotSupported(); - - // ==== Setup Functions ==== - function setUp() public virtual override { - LdaDiamondTest.setUp(); - _addCoreRouteFacet(); - } - - function _addCoreRouteFacet() internal { - coreRouteFacet = new CoreRouteFacet(); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = CoreRouteFacet.processRoute.selector; - addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); - coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); - } - - // ==== Helper Functions ==== - function _buildBaseRoute( - SwapTestParams memory params, - bytes memory swapData - ) internal pure returns (bytes memory) { - if (params.commandType == CommandType.ProcessNative) { - return - abi.encodePacked( - uint8(params.commandType), - uint8(1), - FULL_SHARE, - uint16(swapData.length), - swapData - ); - } else if (params.commandType == CommandType.ProcessOnePool) { - return - abi.encodePacked( - uint8(params.commandType), - params.tokenIn, - uint16(swapData.length), - swapData - ); - } else { - return - abi.encodePacked( - uint8(params.commandType), - params.tokenIn, - uint8(1), - FULL_SHARE, - uint16(swapData.length), - swapData - ); - } - } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - ExpectedEvent[] memory additionalEvents, - bool isFeeOnTransferToken, - RouteEventVerification memory routeEventVerification - ) internal { - if ( - params.commandType != CommandType.ProcessMyERC20 && - !LibAsset.isNativeAsset(params.tokenIn) - ) { - IERC20(params.tokenIn).approve( - address(ldaDiamond), - params.amountIn - ); - } - - uint256 inBefore; - uint256 outBefore = LibAsset.isNativeAsset(params.tokenOut) - ? params.recipient.balance - : IERC20(params.tokenOut).balanceOf(params.recipient); - - // For aggregator funds, check the diamond's balance - if (params.commandType == CommandType.ProcessMyERC20) { - inBefore = LibAsset.isNativeAsset(params.tokenIn) - ? address(ldaDiamond).balance - : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); - } else { - inBefore = LibAsset.isNativeAsset(params.tokenIn) - ? params.sender.balance - : IERC20(params.tokenIn).balanceOf(params.sender); - } - - address fromAddress = params.sender == address(ldaDiamond) - ? USER_SENDER - : params.sender; - - _expectEvents(additionalEvents); - - vm.expectEmit(true, true, true, routeEventVerification.checkData); - emit Route( - fromAddress, - params.recipient, - params.tokenIn, - params.tokenOut, - params.amountIn, - params.minOut, - routeEventVerification.expectedExactOut - ); - - // For native token, send value with the call - if (LibAsset.isNativeAsset(params.tokenIn)) { - coreRouteFacet.processRoute{ value: params.amountIn }( - params.tokenIn, - params.amountIn, - params.tokenOut, - params.minOut, - params.recipient, - route - ); - } else { - coreRouteFacet.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - params.minOut, - params.recipient, - route - ); - } - - uint256 inAfter; - uint256 outAfter = LibAsset.isNativeAsset(params.tokenOut) - ? params.recipient.balance - : IERC20(params.tokenOut).balanceOf(params.recipient); - - // Check balance change on the correct address - if (params.commandType == CommandType.ProcessMyERC20) { - inAfter = LibAsset.isNativeAsset(params.tokenIn) - ? address(ldaDiamond).balance - : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); - } else { - inAfter = LibAsset.isNativeAsset(params.tokenIn) - ? params.sender.balance - : IERC20(params.tokenIn).balanceOf(params.sender); - } - - // Use assertEq or assertApproxEqAbs based on isFeeOnTransferToken - if (isFeeOnTransferToken) { - assertApproxEqAbs( - inBefore - inAfter, - params.amountIn, - 1, // Allow 1 wei difference for fee-on-transfer tokens - "Token spent mismatch" - ); - } else { - assertEq( - inBefore - inAfter, - params.amountIn, - "Token spent mismatch" - ); - } - - assertGt(outAfter - outBefore, 0, "Should receive tokens"); - } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - ExpectedEvent[] memory additionalEvents, - bool isFeeOnTransferToken - ) internal { - _executeAndVerifySwap( - params, - route, - additionalEvents, - isFeeOnTransferToken, - RouteEventVerification({ expectedExactOut: 0, checkData: false }) - ); - } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route - ) internal { - _executeAndVerifySwap( - params, - route, - new ExpectedEvent[](0), - false, - RouteEventVerification({ expectedExactOut: 0, checkData: false }) - ); - } - - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - bool isFeeOnTransferToken - ) internal { - _executeAndVerifySwap( - params, - route, - new ExpectedEvent[](0), - isFeeOnTransferToken, - RouteEventVerification({ expectedExactOut: 0, checkData: false }) - ); - } - - // Keep the revert case separate - function _executeAndVerifySwap( - SwapTestParams memory params, - bytes memory route, - bytes4 expectedRevert - ) internal { - if (params.commandType != CommandType.ProcessMyERC20) { - IERC20(params.tokenIn).approve( - address(ldaDiamond), - params.amountIn - ); - } - - vm.expectRevert(expectedRevert); - coreRouteFacet.processRoute( - params.tokenIn, - params.commandType == CommandType.ProcessMyERC20 - ? params.amountIn - : params.amountIn - 1, - params.tokenOut, - 0, // minOut = 0 for tests - params.recipient, - route - ); - } - - // helper: load a 32-byte topic from a 32-byte abi.encode(param) - function _asTopic(bytes memory enc) internal pure returns (bytes32 topic) { - if (enc.length != 32) revert InvalidTopicLength(); - assembly { - topic := mload(add(enc, 32)) - } - } - - /** - * @notice Sets up event expectations for a list of events - * @param events Array of events to expect - */ - function _expectEvents(ExpectedEvent[] memory events) internal { - for (uint256 i = 0; i < events.length; i++) { - _expectEvent(events[i]); - } - } - - /** - * @notice Sets up expectation for a single event - * @param evt The event to expect with its check parameters and data - */ - function _expectEvent(ExpectedEvent memory evt) internal { - vm.expectEmit( - evt.checkTopic1, - evt.checkTopic2, - evt.checkTopic3, - evt.checkData - ); - - // Build topics (topic0 = selector; topics1..3 from indexedParamIndices) - bytes32 topic0 = evt.eventSelector; - uint8[] memory idx = evt.indexedParamIndices; - - bytes32 t1; - bytes32 t2; - bytes32 t3; - - uint256 topicsCount = idx.length; - if (topicsCount > 3) { - revert TooManyIndexedParams(); - } - if (topicsCount >= 1) { - t1 = _asTopic(evt.eventParams[idx[0]]); - } - if (topicsCount >= 2) { - t2 = _asTopic(evt.eventParams[idx[1]]); - } - if (topicsCount == 3) { - t3 = _asTopic(evt.eventParams[idx[2]]); - } - - // Build data (non-indexed params in event order) - bytes memory data; - if (evt.checkData) { - // Only support static params for now (each abi.encode(param) must be 32 bytes) - uint256 total = evt.eventParams.length; - bool[8] memory isIndexed; // up to 8 params; expand if needed - for (uint256 k = 0; k < topicsCount; k++) { - uint8 pos = idx[k]; - if (pos < isIndexed.length) isIndexed[pos] = true; - } - - for (uint256 p = 0; p < total; p++) { - if (!isIndexed[p]) { - bytes memory enc = evt.eventParams[p]; - if (enc.length != 32) { - revert DynamicParamsNotSupported(); - } - data = bytes.concat(data, enc); - } - } - } else { - data = ""; - } - - // Emit raw log with correct number of topics - assembly { - let ptr := add(data, 0x20) - let len := mload(data) - switch topicsCount - case 0 { - log1(ptr, len, topic0) - } - case 1 { - log2(ptr, len, topic0, t1) - } - case 2 { - log3(ptr, len, topic0, t1, t2) - } - case 3 { - log4(ptr, len, topic0, t1, t2, t3) - } - } - } -} - -// ==== Main Test Contract ==== -contract CoreRouteFacetTest is BaseCoreRouteTest { - using SafeTransferLib for address; - - // ==== Storage Variables ==== - bytes4 internal pullSel; - - // ==== Setup Functions ==== - function setUp() public override { - BaseCoreRouteTest.setUp(); - // Register mock pull facet once and store selector - MockPullERC20Facet mockPull = new MockPullERC20Facet(); - bytes4[] memory sel = new bytes4[](1); - sel[0] = MockPullERC20Facet.pull.selector; - addFacet(address(ldaDiamond), address(mockPull), sel); - pullSel = sel[0]; - } - - // ==== Helper Functions ==== - function _addMockNativeFacet() internal { - MockNativeFacet mock = new MockNativeFacet(); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = MockNativeFacet.handleNative.selector; - addFacet(address(ldaDiamond), address(mock), selectors); - } - - function _addMockPullFacet() internal returns (bytes4 sel) { - MockPullERC20Facet mock = new MockPullERC20Facet(); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = MockPullERC20Facet.pull.selector; - addFacet(address(ldaDiamond), address(mock), selectors); - return selectors[0]; - } - - function _signPermit( - ERC20PermitMock token, - uint256 ownerPk, - address owner, - address spender, - uint256 value, - uint256 deadline - ) internal view returns (uint8 v, bytes32 r, bytes32 s) { - bytes32 typehash = keccak256( - "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" - ); - bytes32 structHash = keccak256( - abi.encode( - typehash, - owner, - spender, - value, - token.nonces(owner), - deadline - ) - ); - bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash) - ); - (v, r, s) = vm.sign(ownerPk, digest); - } - - // ==== Test Cases ==== - function test_ProcessNativeCommandSendsEthToRecipient() public { - _addMockNativeFacet(); - - address recipient = USER_RECEIVER; - uint256 amount = 1 ether; - - // Fund the actual caller (USER_SENDER) - vm.deal(USER_SENDER, amount); - - // swapData: selector + abi.encode(recipient) - bytes memory swapData = abi.encodePacked( - MockNativeFacet.handleNative.selector, - abi.encode(recipient) - ); - - // route: [3][num=1][share=FULL_SHARE][len][swapData] - SwapTestParams memory params = SwapTestParams({ - tokenIn: address(0), - tokenOut: address(0), - amountIn: amount, - minOut: 0, - sender: USER_SENDER, // Use USER_SENDER directly - recipient: recipient, - commandType: CommandType.ProcessNative - }); - - bytes memory route = _buildBaseRoute(params, swapData); - - vm.prank(USER_SENDER); // Set msg.sender to USER_SENDER - _executeAndVerifySwap( - params, - route, - new ExpectedEvent[](0), - false, - RouteEventVerification({ - expectedExactOut: amount, - checkData: true - }) - ); - } - - function test_ApplyPermitCommandSetsAllowanceOnDiamond() public { - uint256 ownerPk = 0xA11CE; - address owner = vm.addr(ownerPk); - uint256 init = 1_000_000e18; - ERC20PermitMock token = new ERC20PermitMock( - "Mock", - "MCK", - owner, - init - ); - - uint256 value = 500_000e18; - uint256 deadline = block.timestamp + 1 days; - - (uint8 v, bytes32 r, bytes32 s) = _signPermit( - token, - ownerPk, - owner, - address(ldaDiamond), - value, - deadline - ); - - // route: [5][value][deadline][v][r][s] - bytes memory route = abi.encodePacked( - uint8(5), - value, - deadline, - v, - r, - s - ); - - vm.prank(owner); - coreRouteFacet.processRoute( - address(token), // tokenIn used by _applyPermit - 0, - address(token), // tokenOut unused; must be a contract for balanceOf read - 0, - owner, - route - ); - - assertEq( - IERC20(address(token)).allowance(owner, address(ldaDiamond)), - value, - "permit allowance not set" - ); - } - - function testRevert_WhenCommandCodeIsUnknown() public { - bytes memory route = abi.encodePacked(uint8(9)); - - vm.prank(USER_SENDER); - vm.expectRevert(CoreRouteFacet.UnknownCommandCode.selector); - coreRouteFacet.processRoute( - address(0), - 0, - address(0), - 0, - USER_RECEIVER, - route - ); - } - - function testRevert_WhenSelectorIsUnknown() public { - ERC20PermitMock token = new ERC20PermitMock( - "Mock2", - "MCK2", - USER_SENDER, - 1e18 - ); - - bytes memory swapData = abi.encodePacked(bytes4(0xdeadbeef)); - - // ProcessUserERC20: [2][tokenIn][num=1][share=FULL_SHARE][len=4][selector=0xdeadbeef] - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(token), - tokenOut: address(token), - amountIn: 0, - minOut: 0, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - vm.prank(USER_SENDER); - vm.expectRevert(CoreRouteFacet.UnknownSelector.selector); - coreRouteFacet.processRoute( - address(token), - 0, - address(token), - 0, - USER_RECEIVER, - route - ); - } - - // MinimalInputBalanceViolation: trigger by charging the user twice via two ProcessUserERC20 steps. - function testRevert_WhenInputBalanceIsInsufficientForTwoSteps() public { - // Prepare token and approvals - uint256 amountIn = 1e18; - ERC20PermitMock token = new ERC20PermitMock( - "Pull", - "PULL", - USER_SENDER, - 2 * amountIn - ); - - vm.startPrank(USER_SENDER); - IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); - - bytes memory swapData = abi.encodePacked(pullSel); - - // Build one step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] - bytes memory step = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(token), - tokenOut: address(0), - amountIn: amountIn, - minOut: 0, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - bytes memory route = bytes.concat(step, step); - - vm.expectRevert( - abi.encodeWithSelector( - CoreRouteFacet.MinimalInputBalanceViolation.selector, - amountIn, // available = final(0) + amountIn - 2 * amountIn // required = initial (we minted 2*amountIn) - ) - ); - coreRouteFacet.processRoute( - address(token), // tokenIn for balance checks - amountIn, // only 1e18 declared - address(0), // no tokenOut change - 0, - USER_RECEIVER, - route - ); - vm.stopPrank(); - } - - // Same as above but with tokenOut set to an ERC20, to ensure path-independent behavior. - function testRevert_WhenInputBalanceIsInsufficientForTwoStepsWithERC20Out() - public - { - uint256 amountIn = 1e18; - ERC20PermitMock token = new ERC20PermitMock( - "Pull2", - "PULL2", - USER_SENDER, - 2 * amountIn - ); - ERC20PermitMock tokenOut = new ERC20PermitMock( - "Out", - "OUT", - address(this), - 0 - ); - - vm.startPrank(USER_SENDER); - IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); - - bytes memory swapData = abi.encodePacked(pullSel); - - bytes memory step = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(token), - tokenOut: address(tokenOut), - amountIn: amountIn, - minOut: 0, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - bytes memory route = bytes.concat(step, step); - - vm.expectRevert( - abi.encodeWithSelector( - CoreRouteFacet.MinimalInputBalanceViolation.selector, - amountIn, // available = final(0) + amountIn - 2 * amountIn // required = initial (we minted 2*amountIn) - ) - ); - coreRouteFacet.processRoute( - address(token), - amountIn, - address(tokenOut), - 0, - USER_RECEIVER, - route - ); - vm.stopPrank(); - } - - function testRevert_WhenOutputBalanceIsZeroForERC20() public { - // Register the mock pull facet; it pulls tokenIn but never transfers tokenOut to `to` - bytes4 sel = pullSel; - - uint256 amountIn = 1e18; - ERC20PermitMock tokenIn = new ERC20PermitMock( - "IN", - "IN", - USER_SENDER, - amountIn - ); - ERC20PermitMock tokenOut = new ERC20PermitMock( - "OUT", - "OUT", - USER_RECEIVER, - 0 - ); // recipient starts at 0 - - bytes memory swapData = abi.encodePacked(sel); - - // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: amountIn, - minOut: 1, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); // single step; no tokenOut will be sent to recipient - - vm.startPrank(USER_SENDER); - IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); - - // Expect MinimalOutputBalanceViolation with deltaOut = 0 - vm.expectRevert( - abi.encodeWithSelector( - CoreRouteFacet.MinimalOutputBalanceViolation.selector, - uint256(0) - ) - ); - - coreRouteFacet.processRoute( - address(tokenIn), - amountIn, - address(tokenOut), // tokenOut is ERC20 - 1, // amountOutMin > 0 to trigger the revert when no output arrives - USER_RECEIVER, - route - ); - vm.stopPrank(); - } - - function testRevert_WhenOutputBalanceIsZeroForNative() public { - // Register the mock pull facet; it pulls tokenIn but never transfers native to `to` - bytes4 sel = pullSel; - - uint256 amountIn = 1e18; - ERC20PermitMock tokenIn = new ERC20PermitMock( - "IN2", - "IN2", - USER_SENDER, - amountIn - ); - - bytes memory swapData = abi.encodePacked(sel); - - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(0), - amountIn: amountIn, - minOut: 1, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - vm.startPrank(USER_SENDER); - IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); - - // Expect MinimalOutputBalanceViolation with deltaOut = 0 (no ETH sent) - vm.expectRevert( - abi.encodeWithSelector( - CoreRouteFacet.MinimalOutputBalanceViolation.selector, - uint256(0) - ) - ); - - coreRouteFacet.processRoute( - address(tokenIn), - amountIn, - address(0), // tokenOut is native - 1, // amountOutMin > 0 - USER_RECEIVER, - route - ); - vm.stopPrank(); - } -} diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol new file mode 100644 index 000000000..0189f93f2 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { MockPullERC20Facet } from "../../../utils/MockPullERC20Facet.sol"; +import { MockNativeFacet } from "../../../utils/MockNativeFacet.sol"; +import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; +import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; + +contract CoreRouteFacetTest is BaseCoreRouteTest { + using SafeTransferLib for address; + + bytes4 internal pullSel; + + // ==== Setup Functions ==== + function setUp() public override { + BaseCoreRouteTest.setUp(); + // Register mock pull facet once and store selector + MockPullERC20Facet mockPull = new MockPullERC20Facet(); + bytes4[] memory sel = new bytes4[](1); + sel[0] = MockPullERC20Facet.pull.selector; + addFacet(address(ldaDiamond), address(mockPull), sel); + pullSel = sel[0]; + } + + // ==== Helper Functions ==== + function _addMockNativeFacet() internal { + MockNativeFacet mock = new MockNativeFacet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockNativeFacet.handleNative.selector; + addFacet(address(ldaDiamond), address(mock), selectors); + } + + function _addMockPullFacet() internal returns (bytes4 sel) { + MockPullERC20Facet mock = new MockPullERC20Facet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockPullERC20Facet.pull.selector; + addFacet(address(ldaDiamond), address(mock), selectors); + return selectors[0]; + } + + function _signPermit( + ERC20PermitMock token, + uint256 ownerPk, + address owner, + address spender, + uint256 value, + uint256 deadline + ) internal view returns (uint8 v, bytes32 r, bytes32 s) { + bytes32 typehash = keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ); + bytes32 structHash = keccak256( + abi.encode( + typehash, + owner, + spender, + value, + token.nonces(owner), + deadline + ) + ); + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash) + ); + (v, r, s) = vm.sign(ownerPk, digest); + } + + // ==== Test Cases ==== + function test_ContractIsSetUpCorrectly() public { + // Test that owner is set correctly + assertEq( + coreRouteFacet.owner(), + USER_DIAMOND_OWNER, + "owner not set correctly" + ); + } + + function testRevert_WhenConstructedWithZeroAddress() public { + vm.expectRevert(InvalidConfig.selector); + new CoreRouteFacet(address(0)); + } + + function test_ProcessNativeCommandSendsEthToRecipient() public { + _addMockNativeFacet(); + + address recipient = USER_RECEIVER; + uint256 amount = 1 ether; + + // Fund the actual caller (USER_SENDER) + vm.deal(USER_SENDER, amount); + + // swapData: selector + abi.encode(recipient) + bytes memory swapData = abi.encodePacked( + MockNativeFacet.handleNative.selector, + abi.encode(recipient) + ); + + // route: [3][num=1][share=FULL_SHARE][len][swapData] + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(0), + tokenOut: address(0), + amountIn: amount, + minOut: 0, + sender: USER_SENDER, // Use USER_SENDER directly + recipient: recipient, + commandType: CommandType.ProcessNative + }); + + bytes memory route = _buildBaseRoute(params, swapData); + + vm.prank(USER_SENDER); // Set msg.sender to USER_SENDER + _executeAndVerifySwap( + params, + route, + new ExpectedEvent[](0), + false, + RouteEventVerification({ + expectedExactOut: amount, + checkData: true + }) + ); + } + + function test_ApplyPermitCommandSetsAllowanceOnDiamond() public { + uint256 ownerPk = 0xA11CE; + address owner = vm.addr(ownerPk); + uint256 init = 1_000_000e18; + ERC20PermitMock token = new ERC20PermitMock( + "Mock", + "MCK", + owner, + init + ); + + uint256 value = 500_000e18; + uint256 deadline = block.timestamp + 1 days; + + (uint8 v, bytes32 r, bytes32 s) = _signPermit( + token, + ownerPk, + owner, + address(ldaDiamond), + value, + deadline + ); + + // route: [5][value][deadline][v][r][s] + bytes memory route = abi.encodePacked( + uint8(5), + value, + deadline, + v, + r, + s + ); + + vm.prank(owner); + coreRouteFacet.processRoute( + address(token), // tokenIn used by _applyPermit + 0, + address(token), // tokenOut unused; must be a contract for balanceOf read + 0, + owner, + route + ); + + assertEq( + IERC20(address(token)).allowance(owner, address(ldaDiamond)), + value, + "permit allowance not set" + ); + } + + function testRevert_WhenCommandCodeIsUnknown() public { + bytes memory route = abi.encodePacked(uint8(9)); + + vm.prank(USER_SENDER); + vm.expectRevert(CoreRouteFacet.UnknownCommandCode.selector); + coreRouteFacet.processRoute( + address(0), + 0, + address(0), + 0, + USER_RECEIVER, + route + ); + } + + function testRevert_WhenSelectorIsUnknown() public { + ERC20PermitMock token = new ERC20PermitMock( + "Mock2", + "MCK2", + USER_SENDER, + 1e18 + ); + + bytes memory swapData = abi.encodePacked(bytes4(0xdeadbeef)); + + // ProcessUserERC20: [2][tokenIn][num=1][share=FULL_SHARE][len=4][selector=0xdeadbeef] + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(token), + tokenOut: address(token), + amountIn: 0, + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + vm.prank(USER_SENDER); + vm.expectRevert(CoreRouteFacet.UnknownSelector.selector); + coreRouteFacet.processRoute( + address(token), + 0, + address(token), + 0, + USER_RECEIVER, + route + ); + } + + // MinimalInputBalanceViolation: trigger by charging the user twice via two ProcessUserERC20 steps. + function testRevert_WhenInputBalanceIsInsufficientForTwoSteps() public { + // Prepare token and approvals + uint256 amountIn = 1e18; + ERC20PermitMock token = new ERC20PermitMock( + "Pull", + "PULL", + USER_SENDER, + 2 * amountIn + ); + + vm.startPrank(USER_SENDER); + IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); + + bytes memory swapData = abi.encodePacked(pullSel); + + // Build one step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] + bytes memory step = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(token), + tokenOut: address(0), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + bytes memory route = bytes.concat(step, step); + + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalInputBalanceViolation.selector, + amountIn, // available = final(0) + amountIn + 2 * amountIn // required = initial (we minted 2*amountIn) + ) + ); + coreRouteFacet.processRoute( + address(token), // tokenIn for balance checks + amountIn, // only 1e18 declared + address(0), // no tokenOut change + 0, + USER_RECEIVER, + route + ); + vm.stopPrank(); + } + + // Same as above but with tokenOut set to an ERC20, to ensure path-independent behavior. + function testRevert_WhenInputBalanceIsInsufficientForTwoStepsWithERC20Out() + public + { + uint256 amountIn = 1e18; + ERC20PermitMock token = new ERC20PermitMock( + "Pull2", + "PULL2", + USER_SENDER, + 2 * amountIn + ); + ERC20PermitMock tokenOut = new ERC20PermitMock( + "Out", + "OUT", + address(this), + 0 + ); + + vm.startPrank(USER_SENDER); + IERC20(address(token)).approve(address(ldaDiamond), type(uint256).max); + + bytes memory swapData = abi.encodePacked(pullSel); + + bytes memory step = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(token), + tokenOut: address(tokenOut), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + bytes memory route = bytes.concat(step, step); + + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalInputBalanceViolation.selector, + amountIn, // available = final(0) + amountIn + 2 * amountIn // required = initial (we minted 2*amountIn) + ) + ); + coreRouteFacet.processRoute( + address(token), + amountIn, + address(tokenOut), + 0, + USER_RECEIVER, + route + ); + vm.stopPrank(); + } + + function testRevert_WhenOutputBalanceIsZeroForERC20() public { + // Register the mock pull facet; it pulls tokenIn but never transfers tokenOut to `to` + bytes4 sel = pullSel; + + uint256 amountIn = 1e18; + ERC20PermitMock tokenIn = new ERC20PermitMock( + "IN", + "IN", + USER_SENDER, + amountIn + ); + ERC20PermitMock tokenOut = new ERC20PermitMock( + "OUT", + "OUT", + USER_RECEIVER, + 0 + ); // recipient starts at 0 + + bytes memory swapData = abi.encodePacked(sel); + + // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + minOut: 1, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); // single step; no tokenOut will be sent to recipient + + vm.startPrank(USER_SENDER); + IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); + + // Expect MinimalOutputBalanceViolation with deltaOut = 0 + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalOutputBalanceViolation.selector, + uint256(0) + ) + ); + + coreRouteFacet.processRoute( + address(tokenIn), + amountIn, + address(tokenOut), // tokenOut is ERC20 + 1, // amountOutMin > 0 to trigger the revert when no output arrives + USER_RECEIVER, + route + ); + vm.stopPrank(); + } + + function testRevert_WhenOutputBalanceIsZeroForNative() public { + // Register the mock pull facet; it pulls tokenIn but never transfers native to `to` + bytes4 sel = pullSel; + + uint256 amountIn = 1e18; + ERC20PermitMock tokenIn = new ERC20PermitMock( + "IN2", + "IN2", + USER_SENDER, + amountIn + ); + + bytes memory swapData = abi.encodePacked(sel); + + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(0), + amountIn: amountIn, + minOut: 1, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + vm.startPrank(USER_SENDER); + IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); + + // Expect MinimalOutputBalanceViolation with deltaOut = 0 (no ETH sent) + vm.expectRevert( + abi.encodeWithSelector( + CoreRouteFacet.MinimalOutputBalanceViolation.selector, + uint256(0) + ) + ); + + coreRouteFacet.processRoute( + address(tokenIn), + amountIn, + address(0), // tokenOut is native + 1, // amountOutMin > 0 + USER_RECEIVER, + route + ); + vm.stopPrank(); + } +} diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/utils/DiamondTest.sol index 6009072ce..4a7b6c8c8 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/utils/DiamondTest.sol @@ -5,7 +5,7 @@ import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; -import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { BaseDiamondTest } from "./BaseDiamondTest.sol"; @@ -36,8 +36,12 @@ contract DiamondTest is BaseDiamondTest { // Add PeripheryRegistry bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = PeripheryRegistryFacet.registerPeripheryContract.selector; - functionSelectors[1] = PeripheryRegistryFacet.getPeripheryContract.selector; + functionSelectors[0] = PeripheryRegistryFacet + .registerPeripheryContract + .selector; + functionSelectors[1] = PeripheryRegistryFacet + .getPeripheryContract + .selector; cut.push( LibDiamond.FacetCut({ facetAddress: address(periphery), diff --git a/test/solidity/utils/MockNativeFacet.sol b/test/solidity/utils/MockNativeFacet.sol new file mode 100644 index 000000000..948e12f9b --- /dev/null +++ b/test/solidity/utils/MockNativeFacet.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; + +/// @title MockNativeFacet +/// @author LI.FI (https://li.fi) +/// @notice Mock facet that handles native token transfers +/// @custom:version 1.0.0 +contract MockNativeFacet { + function handleNative( + bytes memory payload, + address /*from*/, + address /*tokenIn*/, + uint256 amountIn + ) external payable returns (uint256) { + address recipient = abi.decode(payload, (address)); + LibAsset.transferAsset(address(0), payable(recipient), amountIn); + return amountIn; + } +} diff --git a/test/solidity/utils/MockPullERC20Facet.sol b/test/solidity/utils/MockPullERC20Facet.sol new file mode 100644 index 000000000..7c8f4b178 --- /dev/null +++ b/test/solidity/utils/MockPullERC20Facet.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; + +/// @title MockPullERC20Facet +/// @author LI.FI (https://li.fi) +/// @notice Mock facet that pulls ERC20 tokens from msg.sender +/// @custom:version 1.0.0 +contract MockPullERC20Facet { + // Pulls `amountIn` from msg.sender if `from == msg.sender` + function pull( + bytes memory /*payload*/, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + if (from == msg.sender) { + LibAsset.transferFromERC20( + tokenIn, + msg.sender, + address(this), + amountIn + ); + } + return amountIn; + } +} From 8741d6317df96d733021ef6753fc66ad7026de1c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 20 Aug 2025 11:52:08 +0200 Subject: [PATCH 043/220] Add BaseRouteConstants contract and refactor facets to utilize it for common constants. Implement readUint24 function in LibPackedStream and update swap functions across various facets to improve clarity and maintainability. Enhance tests for callback handling in BaseDexFacet and BaseUniV3StyleDexFacet --- src/Libraries/LibPackedStream.sol | 12 +++ src/Periphery/Lda/Facets/AlgebraFacet.sol | 5 +- .../Lda/Facets/BaseRouteConstants.sol | 13 +++ src/Periphery/Lda/Facets/CoreRouteFacet.sol | 11 ++- src/Periphery/Lda/Facets/CurveFacet.sol | 39 ++++---- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 34 +++++-- src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 21 ++--- src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 50 +++++----- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 93 +++++++++++++++++-- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 45 ++++----- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 24 +++++ .../Lda/BaseUniV3StyleDexFacet.t.sol | 4 +- 12 files changed, 250 insertions(+), 101 deletions(-) create mode 100644 src/Periphery/Lda/Facets/BaseRouteConstants.sol diff --git a/src/Libraries/LibPackedStream.sol b/src/Libraries/LibPackedStream.sol index 97fea31ea..b6acff9f0 100644 --- a/src/Libraries/LibPackedStream.sol +++ b/src/Libraries/LibPackedStream.sol @@ -66,6 +66,18 @@ library LibPackedStream { } } + /** @notice Reads uint24 from the stream + * @param stream stream + */ + function readUint24(uint256 stream) internal pure returns (uint24 res) { + assembly { + let pos := mload(stream) + pos := add(pos, 3) + res := mload(pos) + mstore(stream, pos) + } + } + /** @notice Reads uint256 from the stream * @param stream stream */ diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 2d0be1270..22abbe334 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -8,13 +8,14 @@ import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "./BaseRouteConstants.sol"; /// @title AlgebraFacet /// @author LI.FI (https://li.fi) /// @notice Handles Algebra swaps with callback management /// @dev Implements direct selector-callable swap function for Algebra pools /// @custom:version 1.0.0 -contract AlgebraFacet { +contract AlgebraFacet is BaseRouteConstants { using LibPackedStream for uint256; using SafeERC20 for IERC20; @@ -45,7 +46,7 @@ contract AlgebraFacet { ) external { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); - bool direction = stream.readUint8() > 0; + bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; address recipient = stream.readAddress(); bool supportsFeeOnTransfer = stream.readUint8() > 0; diff --git a/src/Periphery/Lda/Facets/BaseRouteConstants.sol b/src/Periphery/Lda/Facets/BaseRouteConstants.sol new file mode 100644 index 000000000..2c4c1ce82 --- /dev/null +++ b/src/Periphery/Lda/Facets/BaseRouteConstants.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +/// @title BaseRouteConstants +/// @author LI.FI (https://li.fi) +/// @notice Base contract providing common constants for DEX facets +/// @dev Abstract contract with shared constants to avoid duplication across facets +abstract contract BaseRouteConstants { + /// @dev Constant indicating swap direction from token0 to token1 + uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + /// @dev Used to indicate tokens are already in the pool/contract + address internal constant INTERNAL_INPUT_SOURCE = address(0); +} diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index f2e64935b..156ae8113 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -10,21 +10,22 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "./BaseRouteConstants.sol"; /// @title CoreRouteFacet /// @author LI.FI (https://li.fi) /// @notice Orchestrates LDA route execution using direct function selector dispatch /// @dev Implements selector-based routing where each DEX facet's swap function is called directly via its selector /// @custom:version 1.0.0 -contract CoreRouteFacet is ReentrancyGuard, WithdrawablePeriphery { +contract CoreRouteFacet is + BaseRouteConstants, + ReentrancyGuard, + WithdrawablePeriphery +{ using SafeERC20 for IERC20; using SafeERC20 for IERC20Permit; using LibPackedStream for uint256; - // ==== Constants ==== - /// @dev sentinel used to indicate that the input is already at the destination pool - address internal constant INTERNAL_INPUT_SOURCE = address(0); - // ==== Events ==== event Route( address indexed from, diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index b578837dc..4ff8b368e 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -2,39 +2,46 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ICurve } from "lifi/Interfaces/ICurve.sol"; import { ICurveLegacy } from "lifi/Interfaces/ICurveLegacy.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -/// @title Curve Facet +/// @title CurveFacet /// @author LI.FI (https://li.fi) -/// @notice Handles Curve swaps with callback management +/// @notice Handles Curve pool swaps for both legacy and modern pools +/// @dev Implements direct selector-callable swap function for Curve pools with balance tracking for legacy pools /// @custom:version 1.0.0 contract CurveFacet { - using LibInputStream for uint256; + using LibPackedStream for uint256; using LibAsset for IERC20; // ==== External Functions ==== - /// @notice Curve pool swap. Legacy pools that don't return amountOut and have native coins are not supported - /// @param stream [pool, poolType, fromIndex, toIndex, recipient, output token] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap + /// @notice Executes a swap through a Curve pool + /// @dev Handles both modern pools that return amounts and legacy pools that require balance tracking + /// @param swapData Encoded swap parameters [pool, poolType, fromIndex, toIndex, recipient, tokenOut] + /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller; + /// otherwise assumes tokens are already at this contract + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens function swapCurve( - uint256 stream, + bytes memory swapData, address from, address tokenIn, uint256 amountIn - ) external returns (uint256) { + ) external { + uint256 stream = LibPackedStream.createStream(swapData); + address pool = stream.readAddress(); uint8 poolType = stream.readUint8(); int128 fromIndex = int8(stream.readUint8()); int128 toIndex = int8(stream.readUint8()); - address to = stream.readAddress(); + address recipient = stream.readAddress(); address tokenOut = stream.readAddress(); - // TODO arm callback protection + if (pool == address(0) || recipient == address(0)) + revert InvalidCallData(); uint256 amountOut; if (LibAsset.isNativeAsset(tokenIn)) { @@ -73,10 +80,8 @@ contract CurveFacet { } } - if (to != address(this)) { - LibAsset.transferAsset(tokenOut, payable(to), amountOut); + if (recipient != address(this)) { + LibAsset.transferAsset(tokenOut, payable(recipient), amountOut); } - - return amountOut; } } diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 83e9ffd03..5dddd5618 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -6,36 +6,44 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "./BaseRouteConstants.sol"; -/// @title IzumiV3 Facet +/// @title IzumiV3Facet /// @author LI.FI (https://li.fi) /// @notice Handles IzumiV3 swaps with callback management /// @custom:version 1.0.0 -contract IzumiV3Facet { +contract IzumiV3Facet is BaseRouteConstants { using LibPackedStream for uint256; using LibCallbackManager for *; // ==== Constants ==== - /// @dev iZiSwap pool price points boundaries + /// @dev Minimum point boundary for iZiSwap pool price range int24 internal constant IZUMI_LEFT_MOST_PT = -800000; + /// @dev Maximum point boundary for iZiSwap pool price range int24 internal constant IZUMI_RIGHT_MOST_PT = 800000; - uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; // ==== Errors ==== + /// @dev Thrown when callback verification fails or unexpected callback state error IzumiV3SwapUnexpected(); - error IzumiV3SwapCallbackUnknownSource(); + /// @dev Thrown when callback amount to pay is zero error IzumiV3SwapCallbackNotPositiveAmount(); // ==== External Functions ==== + /// @notice Executes a swap through an iZiSwap V3 pool + /// @dev Handles both X to Y and Y to X swaps with callback verification + /// @param swapData Encoded swap parameters [pool, direction, recipient] + /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens function swapIzumiV3( bytes memory swapData, address from, address tokenIn, uint256 amountIn - ) external returns (uint256) { + ) external { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); - uint8 direction = stream.readUint8(); // 0 = Y2X, 1 = X2Y + bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; // 0 = Y2X, 1 = X2Y address recipient = stream.readAddress(); if ( @@ -55,7 +63,7 @@ contract IzumiV3Facet { LibCallbackManager.arm(pool); - if (direction == DIRECTION_TOKEN0_TO_TOKEN1) { + if (direction) { IiZiSwapPool(pool).swapX2Y( recipient, uint128(amountIn), @@ -77,11 +85,13 @@ contract IzumiV3Facet { if (LibCallbackManager.callbackStorage().expected != address(0)) { revert IzumiV3SwapUnexpected(); } - - return 0; // Return value not used in current implementation } // ==== Callback Functions ==== + /// @notice Callback for X to Y swaps from iZiSwap pool + /// @dev Verifies callback source and handles token transfer + /// @param amountX Amount of token0 that must be sent to the pool + /// @param data Encoded data containing input token address function swapX2YCallback( uint256 amountX, uint256, @@ -90,6 +100,10 @@ contract IzumiV3Facet { _handleIzumiV3SwapCallback(amountX, data); } + /// @notice Callback for Y to X swaps from iZiSwap pool + /// @dev Verifies callback source and handles token transfer + /// @param amountY Amount of token1 that must be sent to the pool + /// @param data Encoded data containing input token address function swapY2XCallback( uint256, uint256 amountY, diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index c0545d8df..e2d67b2b7 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -9,24 +9,25 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title SyncSwapV2Facet /// @author LI.FI (https://li.fi) -/// @notice Handles SyncSwap swaps with callback management +/// @notice Handles SyncSwap V2 pool swaps with vault integration +/// @dev Implements direct selector-callable swap function for both V1 and V2 SyncSwap pools /// @custom:version 1.0.0 contract SyncSwapV2Facet { using LibPackedStream for uint256; - /// @notice Performs a swap through SyncSwapV2 pools - /// @dev This function handles both X to Y and Y to X swaps through SyncSwapV2 pools. - /// See [SyncSwapV2 API documentation](https://docs.syncswap.xyz/api-documentation) for protocol details. - /// @param swapData [pool, to, withdrawMode, isV1Pool, vault] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap + /// @notice Executes a swap through a SyncSwap V2 pool + /// @dev Handles both V1 (vault-based) and V2 (direct) pool swaps + /// @param swapData Encoded swap parameters [pool, recipient, withdrawMode, isV1Pool, vault] + /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; + /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens function swapSyncSwapV2( bytes memory swapData, address from, address tokenIn, uint256 amountIn - ) external returns (uint256) { + ) external { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); @@ -64,7 +65,5 @@ contract SyncSwapV2Facet { bytes memory data = abi.encode(tokenIn, to, withdrawMode); ISyncSwapPool(pool).swap(data, from, address(0), new bytes(0)); - - return 0; // Return value not used in current implementation } } diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index 616663b1a..4571d3221 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { LibInputStream } from "lifi/Libraries/LibInputStream.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "./BaseRouteConstants.sol"; interface IUniswapV2Pair { function getReserves() @@ -23,38 +24,39 @@ interface IUniswapV2Pair { ) external; } -/// @title UniV2Style Facet +/// @title UniV2StyleFacet /// @author LI.FI (https://li.fi) /// @notice Handles UniswapV2-style swaps (UniV2, SushiSwap, PancakeV2, etc.) -/// @custom:version 2.0.0 -contract UniV2StyleFacet { - using LibInputStream for uint256; - - // ==== Constants ==== - address internal constant INTERNAL_INPUT_SOURCE = address(0); - uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; +/// @custom:version 1.0.0 +contract UniV2StyleFacet is BaseRouteConstants { + using LibPackedStream for uint256; // ==== Errors ==== + /// @dev Thrown when pool reserves are zero, indicating an invalid pool state error WrongPoolReserves(); // ==== External Functions ==== /// @notice Executes a UniswapV2-style swap - /// @param stream The input stream containing swap parameters - /// @param from Where to take liquidity for swap + /// @dev Handles token transfers and calculates output amounts based on pool reserves + /// @param swapData Encoded swap parameters [pool, direction, recipient, fee] + /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; + /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE /// @param tokenIn Input token address /// @param amountIn Amount of input tokens function swapUniV2( - uint256 stream, + bytes memory swapData, address from, address tokenIn, uint256 amountIn - ) external returns (uint256 amountOut) { + ) external { + uint256 stream = LibPackedStream.createStream(swapData); + address pool = stream.readAddress(); - uint8 direction = stream.readUint8(); - address to = stream.readAddress(); + bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; + address recipient = stream.readAddress(); uint24 fee = stream.readUint24(); // pool fee in 1/1_000_000 - if (pool == address(0) || to == address(0)) { + if (pool == address(0) || recipient == address(0)) { revert InvalidCallData(); } @@ -69,8 +71,7 @@ contract UniV2StyleFacet { (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves(); if (r0 == 0 || r1 == 0) revert WrongPoolReserves(); - (uint256 reserveIn, uint256 reserveOut) = direction == - DIRECTION_TOKEN0_TO_TOKEN1 + (uint256 reserveIn, uint256 reserveOut) = direction ? (r0, r1) : (r1, r0); @@ -78,15 +79,18 @@ contract UniV2StyleFacet { amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; uint256 amountInWithFee = amountIn * (1_000_000 - fee); - amountOut = - (amountInWithFee * reserveOut) / + uint256 amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1_000_000 + amountInWithFee); - (uint256 amount0Out, uint256 amount1Out) = direction == - DIRECTION_TOKEN0_TO_TOKEN1 + (uint256 amount0Out, uint256 amount1Out) = direction ? (uint256(0), amountOut) : (amountOut, uint256(0)); - IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0)); + IUniswapV2Pair(pool).swap( + amount0Out, + amount1Out, + recipient, + new bytes(0) + ); } } diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 68a8a00f2..584de592b 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -6,6 +6,7 @@ import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "./BaseRouteConstants.sol"; interface IUniV3StylePool { function swap( @@ -19,21 +20,25 @@ interface IUniV3StylePool { /// @title UniV3StyleFacet /// @author LI.FI (https://li.fi) -/// @notice Handles Uniswap V3 swaps with callback management +/// @notice Handles Uniswap V3 style swaps with callback verification /// @custom:version 1.0.0 -contract UniV3StyleFacet { +contract UniV3StyleFacet is BaseRouteConstants { using LibCallbackManager for *; using LibPackedStream for uint256; // ==== Constants ==== + /// @dev Minimum sqrt price ratio for UniV3 pool swaps uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev Maximum sqrt price ratio for UniV3 pool swaps uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; // ==== Errors ==== + /// @dev Thrown when callback verification fails or unexpected callback state error UniV3SwapUnexpected(); // ==== Modifiers ==== + /// @dev Ensures callback is from expected pool and cleans up after callback modifier onlyExpectedPool() { LibCallbackManager.verifyCallbackSender(); _; @@ -41,9 +46,10 @@ contract UniV3StyleFacet { } // ==== External Functions ==== - /// @notice Executes a UniswapV3 swap - /// @param swapData The input stream containing swap parameters - /// @param from Where to take liquidity for swap + /// @notice Executes a swap through a UniV3-style pool + /// @dev Handles token transfers and manages callback verification + /// @param swapData Encoded swap parameters [pool, direction, recipient] + /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller /// @param tokenIn Input token address /// @param amountIn Amount of input tokens function swapUniV3( @@ -54,7 +60,7 @@ contract UniV3StyleFacet { ) external { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); - bool direction = stream.readUint8() > 0; + bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; address recipient = stream.readAddress(); if (pool == address(0) || recipient == address(0)) { @@ -90,6 +96,11 @@ contract UniV3StyleFacet { } // ==== Callback Functions ==== + /// @notice Callback for Uniswap V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function uniswapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -98,6 +109,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for PancakeSwap V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function pancakeV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -106,6 +122,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for Ramses V2 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function ramsesV2SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -114,6 +135,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for Xei V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function xeiV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -122,6 +148,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for DragonSwap V2 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function dragonswapV2SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -130,6 +161,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for Agni swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function agniSwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -138,6 +174,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for FusionX V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function fusionXV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -146,6 +187,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for VVS V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function vvsV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -154,6 +200,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for Sup V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function supV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -162,6 +213,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for Zebra V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function zebraV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -170,6 +226,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for HyperSwap V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function hyperswapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -178,6 +239,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for Laminar V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function laminarV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -186,6 +252,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for XSwap swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function xswapCallback( int256 amount0Delta, int256 amount1Delta, @@ -194,6 +265,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for RabbitSwap V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function rabbitSwapV3SwapCallback( int256 amount0Delta, int256 amount1Delta, @@ -202,6 +278,11 @@ contract UniV3StyleFacet { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); } + /// @notice Callback for EnosysDEX V3 swaps + /// @dev Verifies callback source and handles token transfer + /// @param amount0Delta The amount of token0 being borrowed/repaid + /// @param amount1Delta The amount of token1 being borrowed/repaid + /// @param data Encoded data containing input token address function enosysdexV3SwapCallback( int256 amount0Delta, int256 amount1Delta, diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 7fc44b89f..266fc8cba 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -6,40 +6,43 @@ import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "./BaseRouteConstants.sol"; /// @title VelodromeV2Facet /// @author LI.FI (https://li.fi) -/// @notice Handles VelodromeV2 swaps with callback management +/// @notice Handles Velodrome V2 pool swaps /// @custom:version 1.0.0 -contract VelodromeV2Facet { +contract VelodromeV2Facet is BaseRouteConstants { using LibPackedStream for uint256; // ==== Constants ==== - uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + /// @dev Flag to enable post-swap callback with flashloan data uint8 internal constant CALLBACK_ENABLED = 1; - address internal constant INTERNAL_INPUT_SOURCE = address(0); // ==== Errors ==== + /// @dev Thrown when pool reserves are zero, indicating an invalid pool state error WrongPoolReserves(); // ==== External Functions ==== /// @notice Performs a swap through VelodromeV2 pools - /// @dev This function does not handle native token swaps directly, so processNative command cannot be used - /// @param swapData [pool, direction, to, callback] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap + /// @dev Handles token transfers and optional callbacks, with comprehensive safety checks + /// @param swapData Encoded swap parameters [pool, direction, recipient, callback] + /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; + /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE + /// @param tokenIn Input token address + /// @param amountIn Amount of input tokens function swapVelodromeV2( bytes memory swapData, address from, address tokenIn, uint256 amountIn - ) external returns (uint256) { + ) external { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); - uint8 direction = stream.readUint8(); - address to = stream.readAddress(); - if (pool == address(0) || to == address(0)) revert InvalidCallData(); + bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; + address recipient = stream.readAddress(); + if (pool == address(0) || recipient == address(0)) + revert InvalidCallData(); // solhint-disable-next-line max-line-length bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee @@ -47,9 +50,7 @@ contract VelodromeV2Facet { (uint256 reserve0, uint256 reserve1, ) = IVelodromeV2Pool(pool) .getReserves(); if (reserve0 == 0 || reserve1 == 0) revert WrongPoolReserves(); - uint256 reserveIn = direction == DIRECTION_TOKEN0_TO_TOKEN1 - ? reserve0 - : reserve1; + uint256 reserveIn = direction ? reserve0 : reserve1; amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; } else { @@ -73,12 +74,8 @@ contract VelodromeV2Facet { // set the appropriate output amount based on which token is being swapped // determine output amounts based on direction - uint256 amount0Out = direction == DIRECTION_TOKEN0_TO_TOKEN1 - ? 0 - : amountOut; - uint256 amount1Out = direction == DIRECTION_TOKEN0_TO_TOKEN1 - ? amountOut - : 0; + uint256 amount0Out = direction ? 0 : amountOut; + uint256 amount1Out = direction ? amountOut : 0; // 'swap' function from IVelodromeV2Pool should be called from a contract which performs important safety checks. // Safety Checks Covered: @@ -100,10 +97,8 @@ contract VelodromeV2Facet { IVelodromeV2Pool(pool).swap( amount0Out, amount1Out, - to, + recipient, callback ? abi.encode(tokenIn) : bytes("") ); - - return 0; // Return value not used in current implementation } } diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 6897d06fc..5843c0f48 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -210,4 +210,28 @@ abstract contract BaseDexFacetTest is BaseCoreRouteTest { // solhint-disable-next-line gas-custom-errors revert("test_CanSwap_MultiHop: Not implemented"); } + + /** + * @notice Abstract test for verifying callback protection against unauthorized calls + * @dev DEX implementations with callbacks must override this + * DEXs without callbacks should leave this empty + */ + function testRevert_CallbackFromUnexpectedSender() public virtual { + // Each DEX implementation with callbacks must override this + // DEXs without callbacks should leave this empty + // solhint-disable-next-line gas-custom-errors + revert("testRevert_CallbackFromUnexpectedSender: Not implemented"); + } + + /** + * @notice Abstract test for verifying swaps fail if callback is not executed + * @dev DEX implementations with callbacks must override this + * DEXs without callbacks should leave this empty + */ + function testRevert_SwapWithoutCallback() public virtual { + // Each DEX implementation with callbacks must override this + // DEXs without callbacks should leave this empty + // solhint-disable-next-line gas-custom-errors + revert("testRevert_SwapWithoutCallback: Not implemented"); + } } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index a09eca7ff..71c41f656 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -186,7 +186,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { ); } - function testRevert_CallbackFromUnexpectedSender() public { + function testRevert_CallbackFromUnexpectedSender() public override { // No swap has armed the guard; expected == address(0) vm.startPrank(USER_SENDER); vm.expectRevert(LibCallbackManager.UnexpectedCallbackSender.selector); @@ -203,7 +203,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.stopPrank(); } - function testRevert_SwapWithoutCallback() public { + function testRevert_SwapWithoutCallback() public override { // Deploy mock pool that doesn't call back MockNoCallbackPool mockPool = new MockNoCallbackPool(); From 8056676cb5b5046c04e0a93599de71ce7636604e Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 20 Aug 2025 15:50:34 +0200 Subject: [PATCH 044/220] Add SwapCallbackNotExecuted error and BaseRouteConstants contract. Refactor facets to utilize BaseRouteConstants for common constants and update error handling in swap functions. Enhance tests for callback verification in various facets, ensuring proper handling of unexpected callback scenarios --- src/Errors/GenericErrors.sol | 1 + .../Lda/{Facets => }/BaseRouteConstants.sol | 0 src/Periphery/Lda/Facets/AlgebraFacet.sol | 9 +- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 2 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 7 +- src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 2 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 6 +- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 2 +- .../Lda/BaseDexFacetWithCallback.t.sol | 84 ++++++ .../Lda/BaseUniV3StyleDexFacet.t.sol | 92 ++----- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 137 +++------- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 241 +++++++----------- .../Lda/Facets/SyncSwapV2Facet.t.sol | 12 + .../Lda/Facets/VelodromeV2Facet.t.sol | 14 + .../EmergencyPauseFacet.fork.t.sol | 0 .../EmergencyPauseFacet.local.t.sol | 0 test/solidity/utils/MockNoCallbackPool.sol | 11 +- 17 files changed, 275 insertions(+), 345 deletions(-) rename src/Periphery/Lda/{Facets => }/BaseRouteConstants.sol (100%) create mode 100644 test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol rename test/solidity/{Facets => Security}/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol (100%) rename test/solidity/{Facets => Security}/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol (100%) diff --git a/src/Errors/GenericErrors.sol b/src/Errors/GenericErrors.sol index cfa9127c5..22358da68 100644 --- a/src/Errors/GenericErrors.sol +++ b/src/Errors/GenericErrors.sol @@ -40,3 +40,4 @@ error UnAuthorized(); error UnsupportedChainId(uint256 chainId); error WithdrawFailed(); error ZeroAmount(); +error SwapCallbackNotExecuted(); diff --git a/src/Periphery/Lda/Facets/BaseRouteConstants.sol b/src/Periphery/Lda/BaseRouteConstants.sol similarity index 100% rename from src/Periphery/Lda/Facets/BaseRouteConstants.sol rename to src/Periphery/Lda/BaseRouteConstants.sol diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 22abbe334..6172859ad 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -8,7 +8,8 @@ import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "./BaseRouteConstants.sol"; +import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title AlgebraFacet /// @author LI.FI (https://li.fi) @@ -26,10 +27,6 @@ contract AlgebraFacet is BaseRouteConstants { uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - // ==== Errors ==== - /// @dev Thrown when callback verification fails or unexpected callback state - error AlgebraSwapUnexpected(); - // ==== External Functions ==== /// @notice Executes a swap through an Algebra pool /// @dev Handles both regular swaps and fee-on-transfer token swaps @@ -83,7 +80,7 @@ contract AlgebraFacet is BaseRouteConstants { } if (LibCallbackManager.callbackStorage().expected != address(0)) { - revert AlgebraSwapUnexpected(); + revert SwapCallbackNotExecuted(); } } diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 156ae8113..e0adb629a 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -10,7 +10,7 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "./BaseRouteConstants.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title CoreRouteFacet /// @author LI.FI (https://li.fi) diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 5dddd5618..37c00743b 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -6,7 +6,8 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "./BaseRouteConstants.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; +import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; /// @title IzumiV3Facet /// @author LI.FI (https://li.fi) @@ -23,8 +24,6 @@ contract IzumiV3Facet is BaseRouteConstants { int24 internal constant IZUMI_RIGHT_MOST_PT = 800000; // ==== Errors ==== - /// @dev Thrown when callback verification fails or unexpected callback state - error IzumiV3SwapUnexpected(); /// @dev Thrown when callback amount to pay is zero error IzumiV3SwapCallbackNotPositiveAmount(); @@ -83,7 +82,7 @@ contract IzumiV3Facet is BaseRouteConstants { // If it hasn't, it means the callback either didn't happen, was incorrect, or the pool misbehaved // so we revert to protect against misuse or faulty integrations if (LibCallbackManager.callbackStorage().expected != address(0)) { - revert IzumiV3SwapUnexpected(); + revert SwapCallbackNotExecuted(); } } diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index 4571d3221..d788d689a 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -5,7 +5,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "./BaseRouteConstants.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; interface IUniswapV2Pair { function getReserves() diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 584de592b..7c74bb8a6 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -5,8 +5,8 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; -import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "./BaseRouteConstants.sol"; +import { InvalidCallData, SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; interface IUniV3StylePool { function swap( @@ -91,7 +91,7 @@ contract UniV3StyleFacet is BaseRouteConstants { // Verify callback was called (arm should be cleared by callback) if (LibCallbackManager.callbackStorage().expected != address(0)) { - revert UniV3SwapUnexpected(); + revert SwapCallbackNotExecuted(); } } diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 266fc8cba..4efb111f3 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -6,7 +6,7 @@ import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "./BaseRouteConstants.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title VelodromeV2Facet /// @author LI.FI (https://li.fi) diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol new file mode 100644 index 000000000..20676c14f --- /dev/null +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; +import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; + +abstract contract BaseDexFacetWithCallbackTest is BaseDexFacetTest { + // Each DEX with callback must implement these hooks + function _getCallbackSelector() internal virtual returns (bytes4); + function _buildCallbackSwapData( + address pool, + address recipient + ) internal virtual returns (bytes memory); + + function _deployNoCallbackPool() internal virtual returns (address) { + return address(new MockNoCallbackPool()); + } + + function testRevert_CallbackFromUnexpectedSender() + public + virtual + override + { + // No swap has armed the guard; expected == address(0) + vm.startPrank(USER_SENDER); + vm.expectRevert(LibCallbackManager.UnexpectedCallbackSender.selector); + (bool ok, ) = address(ldaDiamond).call( + abi.encodeWithSelector( + _getCallbackSelector(), + int256(1), + int256(1), + bytes("") + ) + ); + ok; + vm.stopPrank(); + } + + function testRevert_SwapWithoutCallback() public virtual override { + // Pool that does not call back (facet-specific implementation) + address mockPool = _deployNoCallbackPool(); + + // Setup test params + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); + + // Build facet-specific swap data + bytes memory swapData = _buildCallbackSwapData(mockPool, USER_SENDER); + + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + // Should revert because pool doesn't call back, leaving armed state + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route, + SwapCallbackNotExecuted.selector + ); + + vm.stopPrank(); + } +} diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 71c41f656..f37ddf24b 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -3,13 +3,9 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; -import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; -import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; +import { BaseDexFacetWithCallbackTest } from "./BaseDexFacetWithCallback.t.sol"; -// ==== Base Contract ==== -abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { - // ==== Storage Variables ==== +abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetWithCallbackTest { UniV3StyleFacet internal uniV3Facet; // ==== Types ==== @@ -46,9 +42,6 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { uniV3Facet = UniV3StyleFacet(facetAddress); } - // Each UniV3-style DEX must implement this to provide its specific callback selector - function _getCallbackSelector() internal virtual returns (bytes4); - // ==== Helper Functions ==== function _buildUniV3SwapData( UniV3SwapParams memory params @@ -158,6 +151,21 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { vm.stopPrank(); } + // ==== Overrides ==== + function _buildCallbackSwapData( + address pool, + address recipient + ) internal override returns (bytes memory) { + return + _buildUniV3SwapData( + UniV3SwapParams({ + pool: pool, + direction: SwapDirection.Token0ToToken1, + recipient: recipient + }) + ); + } + // ==== Test Cases ==== function test_CanSwap_MultiHop() public virtual override { // SKIPPED: UniV3 forke dex multi-hop unsupported due to AS (amount specified) requirement. @@ -185,70 +193,4 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetTest { }) ); } - - function testRevert_CallbackFromUnexpectedSender() public override { - // No swap has armed the guard; expected == address(0) - vm.startPrank(USER_SENDER); - vm.expectRevert(LibCallbackManager.UnexpectedCallbackSender.selector); - // Call the facet's callback directly on the diamond - (bool ok, ) = address(ldaDiamond).call( - abi.encodeWithSelector( - _getCallbackSelector(), - int256(1), - int256(1), - bytes("") - ) - ); - ok; - vm.stopPrank(); - } - - function testRevert_SwapWithoutCallback() public override { - // Deploy mock pool that doesn't call back - MockNoCallbackPool mockPool = new MockNoCallbackPool(); - - // Setup test params - deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); - - vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); - - bytes memory swapData = _buildUniV3SwapData( - UniV3SwapParams({ - pool: address(mockPool), - direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER - }) - ); - - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - // Should revert because pool doesn't call back, leaving armed state - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route, - UniV3StyleFacet.UniV3SwapUnexpected.selector - ); - - vm.stopPrank(); - } } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 61d1f2cd8..b66415332 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -8,13 +8,12 @@ import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; -import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; -// ==== Helper Contracts ==== contract AlgebraLiquidityAdderHelper { - // ==== Storage Variables ==== address public immutable TOKEN_0; address public immutable TOKEN_1; @@ -100,9 +99,7 @@ contract AlgebraLiquidityAdderHelper { } } -// ==== Main Test Contract ==== -contract AlgebraFacetTest is BaseDexFacetTest { - // ==== Storage Variables ==== +contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { AlgebraFacet internal algebraFacet; // ==== Constants ==== @@ -112,8 +109,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; address private constant RANDOM_APE_ETH_HOLDER_APECHAIN = address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); - address private constant IMPOSSIBLE_POOL_ADDRESS = - 0x0000000000000000000000000000000000000001; // ==== Types ==== struct AlgebraSwapTestParams { @@ -145,9 +140,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens } - // ==== Errors ==== - error AlgebraSwapUnexpected(); - // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ @@ -335,35 +327,24 @@ contract AlgebraFacetTest is BaseDexFacetTest { _executeAndVerifyMultiHopSwap(state); } - function testRevert_SwapUnexpected() public { - // Transfer tokens from whale to user + function testRevert_SwapWithoutCallback() public override { + // Pool that does not call back for Algebra + address mockPool = _deployNoCallbackPool(); // your Algebra-specific or shared mock + + // Fund user from whale instead of deal() vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); - IERC20(tokenIn).transfer(USER_SENDER, _getDefaultAmountForTokenIn()); + IERC20(address(tokenIn)).transfer( + USER_SENDER, + _getDefaultAmountForTokenIn() + ); + // Approve and build route vm.startPrank(USER_SENDER); + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); - // Create invalid pool address - address invalidPool = address(0x999); - - // Mock token0() call on invalid pool - vm.mockCall( - invalidPool, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(tokenIn) - ); + bytes memory swapData = _buildCallbackSwapData(mockPool, USER_SENDER); - // Create a route with an invalid pool - bytes memory swapData = _buildAlgebraSwapData( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: address(tokenIn), - recipient: USER_SENDER, - pool: invalidPool, - supportsFeeOnTransfer: true - }) - ); - - bytes memory invalidRoute = _buildBaseRoute( + bytes memory route = _buildBaseRoute( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -376,15 +357,6 @@ contract AlgebraFacetTest is BaseDexFacetTest { swapData ); - // Mock the algebra pool to not reset lastCalledPool - vm.mockCall( - invalidPool, - abi.encodeWithSelector( - IAlgebraPool.swapSupportingFeeOnInputTokens.selector - ), - abi.encode(0, 0) - ); - _executeAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), @@ -395,12 +367,11 @@ contract AlgebraFacetTest is BaseDexFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - invalidRoute, - AlgebraSwapUnexpected.selector + route, + SwapCallbackNotExecuted.selector ); vm.stopPrank(); - vm.clearMockedCalls(); } function testRevert_AlgebraSwap_ZeroAddressPool() public { @@ -459,58 +430,26 @@ contract AlgebraFacetTest is BaseDexFacetTest { vm.clearMockedCalls(); } - // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { - // // Transfer tokens from whale to user - // vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); - // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - // vm.startPrank(USER_SENDER); - - // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS - // vm.mockCall( - // IMPOSSIBLE_POOL_ADDRESS, - // abi.encodeWithSelector(IAlgebraPool.token0.selector), - // abi.encode(APE_ETH_TOKEN) - // ); - - // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool - // bytes memory swapData = _buildAlgebraSwapData( - // AlgebraRouteParams({ - // commandCode: CommandType.ProcessUserERC20, - // tokenIn: APE_ETH_TOKEN, - // recipient: USER_SENDER, - // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address - // supportsFeeOnTransfer: true - // }) - // ); - - // bytes memory route = abi.encodePacked( - // uint8(CommandType.ProcessUserERC20), - // APE_ETH_TOKEN, - // uint8(1), // number of pools/splits - // FULL_SHARE, // 100% share - // uint16(swapData.length), // <--- Add the length prefix - // swapData - // ); - - // // Approve tokens - // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - - // // Expect revert with InvalidCallData - // vm.expectRevert(InvalidCallData.selector); - - // coreRouteFacet.processRoute( - // APE_ETH_TOKEN, - // 1 * 1e18, - // address(WETH_TOKEN), - // 0, - // USER_SENDER, - // route - // ); - - // vm.stopPrank(); - // vm.clearMockedCalls(); - // } + // ==== Overrides ==== + + function _getCallbackSelector() internal override returns (bytes4) { + return algebraFacet.algebraSwapCallback.selector; + } + + // Hook: build Algebra swap data [pool, direction(uint8), recipient, supportsFeeOnTransfer(uint8)] + function _buildCallbackSwapData( + address pool, + address recipient + ) internal pure override returns (bytes memory) { + return + abi.encodePacked( + AlgebraFacet.swapAlgebra.selector, + pool, + uint8(1), // Token0->Token1; only the callback arming/clearing is under test + recipient, + uint8(0) // no fee-on-transfer + ); + } function testRevert_AlgebraSwap_ZeroAddressRecipient() public { // Transfer tokens from whale to user diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 0b03c2ce8..75e22cb46 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -2,27 +2,25 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; +import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; -contract IzumiV3FacetTest is BaseDexFacetTest { +contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { IzumiV3Facet internal izumiV3Facet; - // structs - struct IzumiV3SwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; + // ==== Types ==== + struct IzumiV3SwapParams { + address pool; SwapDirection direction; + address recipient; } - error IzumiV3SwapUnexpected(); + // ==== Errors ==== error IzumiV3SwapCallbackNotPositiveAmount(); + // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "base", @@ -49,6 +47,14 @@ contract IzumiV3FacetTest is BaseDexFacetTest { izumiV3Facet = IzumiV3Facet(facetAddress); } + function _setupDexEnv() internal override { + tokenIn = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); // USDC + tokenMid = IERC20(0x4200000000000000000000000000000000000006); // WETH + tokenOut = IERC20(0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA); // USDB_C + poolInMid = 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; // WETH/USDC + poolMidOut = 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; // WETH/USDB_C + } + function _getDefaultAmountForTokenIn() internal pure @@ -58,12 +64,73 @@ contract IzumiV3FacetTest is BaseDexFacetTest { return 100 * 1e6; // 100 USDC with 6 decimals } - function _setupDexEnv() internal override { - tokenIn = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); // USDC - tokenMid = IERC20(0x4200000000000000000000000000000000000006); // WETH - tokenOut = IERC20(0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA); // USDB_C - poolInMid = 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; // WETH/USDC - poolMidOut = 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; // WETH/USDB_C + // ==== Callback Test Hooks ==== + function _getCallbackSelector() internal override returns (bytes4) { + return izumiV3Facet.swapX2YCallback.selector; + } + + function _deployNoCallbackPool() internal override returns (address) { + return address(new MockNoCallbackPool()); + } + + function _buildCallbackSwapData( + address pool, + address recipient + ) internal pure override returns (bytes memory) { + return + abi.encodePacked( + IzumiV3Facet.swapIzumiV3.selector, + pool, + uint8(1), // direction TOKEN0_TO_TOKEN1 + recipient + ); + } + + // ==== Test Cases ==== + function test_CanSwap() public override { + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + IERC20(tokenIn).approve( + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); + + bytes memory swapData = _buildIzumiV3SwapData( + IzumiV3SwapParams({ + pool: poolInMid, + direction: SwapDirection.Token1ToToken0, + recipient: USER_RECEIVER + }) + ); + + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_RECEIVER, + commandType: CommandType.ProcessUserERC20 + }), + route + ); + + vm.stopPrank(); } function test_CanSwap_FromDexAggregator() public override { @@ -184,148 +251,19 @@ contract IzumiV3FacetTest is BaseDexFacetTest { vm.stopPrank(); } - function test_CanSwap() public override { - deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); - - vm.startPrank(USER_SENDER); - IERC20(tokenIn).approve( - address(ldaDiamond), - _getDefaultAmountForTokenIn() - ); - - bytes memory swapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: poolInMid, - direction: SwapDirection.Token1ToToken0, - recipient: USER_RECEIVER - }) - ); - - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - route - ); - - vm.stopPrank(); - } - - function testRevert_IzumiV3SwapUnexpected() public { - deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); - - vm.startPrank(USER_SENDER); - - // create invalid pool address - address invalidPool = address(0x999); - - bytes memory swapData = _buildIzumiV3SwapData( - IzumiV3SwapParams({ - pool: invalidPool, - direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER - }) - ); - - // create a route with an invalid pool - bytes memory invalidRoute = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - // mock the iZiSwap pool to return without updating lastCalledPool - vm.mockCall( - invalidPool, - abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), - abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - invalidRoute, - IzumiV3SwapUnexpected.selector - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - function testRevert_UnexpectedCallbackSender() public { - deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); - - // Set up the expected callback sender through the diamond - vm.store( - address(ldaDiamond), - keccak256("com.lifi.lda.callbackmanager"), - bytes32(uint256(uint160(poolInMid))) - ); - - // Try to call callback from a different address than expected - address unexpectedCaller = address(0xdead); - vm.prank(unexpectedCaller); - vm.expectRevert( - abi.encodeWithSelector( - LibCallbackManager.UnexpectedCallbackSender.selector, - unexpectedCaller, - poolInMid - ) - ); - izumiV3Facet.swapY2XCallback(1, 1, abi.encode(tokenIn)); - } - + // ==== Revert Cases ==== function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); - // Set the expected callback sender through the diamond storage vm.store( address(ldaDiamond), keccak256("com.lifi.lda.callbackmanager"), bytes32(uint256(uint160(poolInMid))) ); - // try to call the callback with zero amount vm.prank(poolInMid); vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); - izumiV3Facet.swapY2XCallback( - 0, - 0, // zero amount should trigger the error - abi.encode(tokenIn) - ); + izumiV3Facet.swapY2XCallback(0, 0, abi.encode(tokenIn)); } function testRevert_FailsIfAmountInIsTooLarge() public { @@ -371,12 +309,7 @@ contract IzumiV3FacetTest is BaseDexFacetTest { vm.stopPrank(); } - struct IzumiV3SwapParams { - address pool; - SwapDirection direction; - address recipient; - } - + // ==== Helper Functions ==== function _buildIzumiV3SwapData( IzumiV3SwapParams memory params ) internal view returns (bytes memory) { diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 2c9b17a25..5f74c3efb 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -483,6 +483,18 @@ contract SyncSwapV2FacetTest is BaseDexFacetTest { vm.stopPrank(); } + /// @notice Empty test as SyncSwapV2 does not use callbacks + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + function testRevert_CallbackFromUnexpectedSender() public override { + // SyncSwapV2 does not use callbacks - test intentionally empty + } + + /// @notice Empty test as SyncSwapV2 does not use callbacks + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + function testRevert_SwapWithoutCallback() public override { + // SyncSwapV2 does not use callbacks - test intentionally empty + } + struct SyncSwapV2SwapParams { address pool; address to; diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 0d9eab827..b4bae70f3 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -637,6 +637,20 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { vm.clearMockedCalls(); } + /// @notice Empty test as VelodromeV2 does not use callbacks for regular swaps + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + /// @dev Note: While VelodromeV2 has flashloan callbacks, they are separate from swap callbacks + function testRevert_CallbackFromUnexpectedSender() public override { + // VelodromeV2 does not use callbacks for swaps - test intentionally empty + } + + /// @notice Empty test as VelodromeV2 does not use callbacks for regular swaps + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + /// @dev Note: While VelodromeV2 has flashloan callbacks, they are separate from swap callbacks + function testRevert_SwapWithoutCallback() public override { + // VelodromeV2 does not use callbacks for swaps - test intentionally empty + } + // ==== Helper Functions ==== /** diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol similarity index 100% rename from test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol rename to test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol similarity index 100% rename from test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol rename to test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol diff --git a/test/solidity/utils/MockNoCallbackPool.sol b/test/solidity/utils/MockNoCallbackPool.sol index 763098579..1bb5c8903 100644 --- a/test/solidity/utils/MockNoCallbackPool.sol +++ b/test/solidity/utils/MockNoCallbackPool.sol @@ -5,7 +5,7 @@ import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; /// @title MockNoCallbackPool /// @author LI.FI (https://li.fi) -/// @notice Mock pool that doesn't call back +/// @notice Mock pool that doesn't call back for any DEX-style swap /// @custom:version 1.0.0 contract MockNoCallbackPool is IUniV3StylePool { function token0() external pure returns (address) { @@ -26,4 +26,13 @@ contract MockNoCallbackPool is IUniV3StylePool { // Do nothing - don't call the callback return (0, 0); } + + // Generic fallback for any other swap function (Izumi swapX2Y or swapY2X) + fallback() external payable { + assembly { + mstore(0x00, 0) + mstore(0x20, 0) + return(0x00, 0x40) // (int256, int256) = (0,0) + } + } } From 450e913b45cdc3f34641be00dd9cb0f97ff31c5b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 20 Aug 2025 19:15:38 +0200 Subject: [PATCH 045/220] Add IUniV2StylePool interface for Uniswap V2 style pools, refactor UniV2StyleFacet to use the new interface, and update related tests for consistency. Remove legacy IUniswapV2Pair interface references --- src/Interfaces/IUniV2StylePool.sol | 40 ++++ src/Interfaces/IUniV3StylePool.sol | 18 ++ src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 22 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 11 +- .../Lda/BaseUniV3StyleDexFacet.t.sol | 2 +- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 218 +++++++++--------- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 6 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 69 ++---- test/solidity/utils/MockNoCallbackPool.sol | 23 +- 9 files changed, 210 insertions(+), 199 deletions(-) create mode 100644 src/Interfaces/IUniV2StylePool.sol diff --git a/src/Interfaces/IUniV2StylePool.sol b/src/Interfaces/IUniV2StylePool.sol new file mode 100644 index 000000000..253e5c504 --- /dev/null +++ b/src/Interfaces/IUniV2StylePool.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +/// @title IUniV2StylePool +/// @author LI.FI (https://li.fi) +/// @notice Interface for Uniswap V2 style pools (including SushiSwap, PancakeSwap V2, etc.) +/// @dev This interface represents the core functionality of AMMs that follow UniswapV2's pool design +/// Key characteristics: +/// - Uses x * y = k formula for pricing +/// - Maintains reserves for both tokens +/// - No callbacks during swaps (unlike V3-style pools) +/// @custom:version 1.0.0 +interface IUniV2StylePool { + /// @notice Returns the current reserves of the pool and the last block timestamp + /// @dev Values are stored as uint112 to fit into a single storage slot for gas optimization + /// @return reserve0 The reserve of token0 + /// @return reserve1 The reserve of token1 + /// @return blockTimestampLast The timestamp of the last block where reserves were updated + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + /// @notice Executes a swap in the pool + /// @dev Unlike V3-style pools, this doesn't use callbacks - tokens must be sent to pool before swap + /// @param amount0Out The amount of token0 to send to recipient (0 if sending token1) + /// @param amount1Out The amount of token1 to send to recipient (0 if sending token0) + /// @param to The address that will receive the output tokens + /// @param data Optional data parameter, usually unused in V2-style pools + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; +} diff --git a/src/Interfaces/IUniV3StylePool.sol b/src/Interfaces/IUniV3StylePool.sol index e02ab7e47..57d84fde3 100644 --- a/src/Interfaces/IUniV3StylePool.sol +++ b/src/Interfaces/IUniV3StylePool.sol @@ -6,6 +6,24 @@ pragma solidity ^0.8.17; /// @notice Interface for UniV3-style pools /// @custom:version 1.0.0 interface IUniV3StylePool { + /// @notice Returns the address of the token0 function token0() external view returns (address); + /// @notice Returns the address of the token1 function token1() external view returns (address); + + /// @notice Swaps tokens + /// @param recipient The address of the recipient + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, positive for exact input, negative for exact output + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit + /// @param data Any additional data required for the swap + /// @return amount0 The amount of token0 swapped + /// @return amount1 The amount of token1 swapped + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); } diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index d788d689a..89119a069 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -4,26 +4,10 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; -interface IUniswapV2Pair { - function getReserves() - external - view - returns ( - uint112 reserve0, - uint112 reserve1, - uint32 blockTimestampLast - ); - function swap( - uint256 amount0Out, - uint256 amount1Out, - address to, - bytes calldata data - ) external; -} - /// @title UniV2StyleFacet /// @author LI.FI (https://li.fi) /// @notice Handles UniswapV2-style swaps (UniV2, SushiSwap, PancakeV2, etc.) @@ -68,7 +52,7 @@ contract UniV2StyleFacet is BaseRouteConstants { } // Get reserves and calculate output - (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves(); + (uint256 r0, uint256 r1, ) = IUniV2StylePool(pool).getReserves(); if (r0 == 0 || r1 == 0) revert WrongPoolReserves(); (uint256 reserveIn, uint256 reserveOut) = direction @@ -86,7 +70,7 @@ contract UniV2StyleFacet is BaseRouteConstants { ? (uint256(0), amountOut) : (amountOut, uint256(0)); - IUniswapV2Pair(pool).swap( + IUniV2StylePool(pool).swap( amount0Out, amount1Out, recipient, diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 7c74bb8a6..d5d61b536 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -5,19 +5,10 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData, SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; -interface IUniV3StylePool { - function swap( - address recipient, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bytes calldata data - ) external returns (int256 amount0, int256 amount1); -} - /// @title UniV3StyleFacet /// @author LI.FI (https://li.fi) /// @notice Handles Uniswap V3 style swaps with callback verification diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index f37ddf24b..97ab54939 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -155,7 +155,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetWithCallbackTest { function _buildCallbackSwapData( address pool, address recipient - ) internal override returns (bytes memory) { + ) internal view override returns (bytes memory) { return _buildUniV3SwapData( UniV3SwapParams({ diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index b66415332..dca403794 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -13,92 +13,6 @@ import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; -contract AlgebraLiquidityAdderHelper { - address public immutable TOKEN_0; - address public immutable TOKEN_1; - - constructor(address _token0, address _token1) { - TOKEN_0 = _token0; - TOKEN_1 = _token1; - } - - // ==== External Functions ==== - function addLiquidity( - address pool, - int24 bottomTick, - int24 topTick, - uint128 amount - ) - external - returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) - { - // Get balances before - uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); - - // Call mint - (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( - address(this), - address(this), - bottomTick, - topTick, - amount, - abi.encode(TOKEN_0, TOKEN_1) - ); - - // Get balances after to account for fees - uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); - - // Calculate actual amounts transferred accounting for fees - amount0 = balance0Before - balance0After; - amount1 = balance1Before - balance1After; - - return (amount0, amount1, liquidityActual); - } - - function algebraMintCallback( - uint256 amount0Owed, - uint256 amount1Owed, - bytes calldata - ) external { - // Check token balances - uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); - - // Transfer what we can, limited by actual balance - if (amount0Owed > 0) { - uint256 amount0ToSend = amount0Owed > balance0 - ? balance0 - : amount0Owed; - uint256 balance0Before = IERC20(TOKEN_0).balanceOf( - address(msg.sender) - ); - IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); - uint256 balance0After = IERC20(TOKEN_0).balanceOf( - address(msg.sender) - ); - // solhint-disable-next-line gas-custom-errors - require(balance0After > balance0Before, "Transfer failed"); - } - - if (amount1Owed > 0) { - uint256 amount1ToSend = amount1Owed > balance1 - ? balance1 - : amount1Owed; - uint256 balance1Before = IERC20(TOKEN_1).balanceOf( - address(msg.sender) - ); - IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); - uint256 balance1After = IERC20(TOKEN_1).balanceOf( - address(msg.sender) - ); - // solhint-disable-next-line gas-custom-errors - require(balance1After > balance1Before, "Transfer failed"); - } - } -} - contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { AlgebraFacet internal algebraFacet; @@ -210,11 +124,6 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { function test_CanSwap_FeeOnTransferToken() public { vm.startPrank(RANDOM_APE_ETH_HOLDER_APECHAIN); - IERC20(tokenIn).approve( - address(ldaDiamond), - _getDefaultAmountForTokenIn() - ); - // Build route for algebra swap with command code 2 (user funds) bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ @@ -338,9 +247,7 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { _getDefaultAmountForTokenIn() ); - // Approve and build route vm.startPrank(USER_SENDER); - tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); bytes memory swapData = _buildCallbackSwapData(mockPool, USER_SENDER); @@ -432,7 +339,7 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { // ==== Overrides ==== - function _getCallbackSelector() internal override returns (bytes4) { + function _getCallbackSelector() internal view override returns (bytes4) { return algebraFacet.algebraSwapCallback.selector; } @@ -597,12 +504,6 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { ) private { vm.startPrank(USER_SENDER); - // Approve spending - IERC20(address(state.tokenA)).approve( - address(ldaDiamond), - state.amountIn - ); - // Build route and execute swap SwapTestParams[] memory swapParams = new SwapTestParams[](2); bytes[] memory swapData = new bytes[](2); @@ -762,20 +663,24 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { address pool = _getPool(params.tokenIn, params.tokenOut); // Get expected output from QuoterV2 - // uint256 expectedOutput = _getQuoteExactInput( - // params.tokenIn, - // params.tokenOut, - // params.amountIn - // ); + uint256 expectedOutput = _getQuoteExactInput( + params.tokenIn, + params.tokenOut, + params.amountIn + ); + + // Add 1 wei slippage buffer + uint256 minOutput = expectedOutput - 1; // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. CommandType commandCode = params.from == address(ldaDiamond) ? CommandType.ProcessMyERC20 : CommandType.ProcessUserERC20; - // 1. Pack the specific data for this swap + + // Pack the specific data for this swap bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ - commandCode: commandCode, // Placeholder, not used in this helper + commandCode: commandCode, tokenIn: params.tokenIn, recipient: params.to, pool: pool, @@ -783,18 +688,16 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { }) ); - // 4. Build the route inline and execute the swap to save stack space + // Build route with minOutput that includes slippage buffer bytes memory route = _buildBaseRoute( SwapTestParams({ tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, - minOut: 0, + minOut: minOutput, sender: params.from, recipient: params.to, - commandType: params.from == address(coreRouteFacet) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 + commandType: commandCode }), swapData ); @@ -804,7 +707,7 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, - minOut: 0, + minOut: minOutput, sender: params.from, recipient: params.to, commandType: params.from == address(ldaDiamond) @@ -812,7 +715,8 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { : CommandType.ProcessUserERC20 }), route, - true // This is a fee-on-transfer token + new ExpectedEvent[](0), + params.supportsFeeOnTransfer ); } @@ -838,3 +742,89 @@ contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { return amountOut; } } + +contract AlgebraLiquidityAdderHelper { + address public immutable TOKEN_0; + address public immutable TOKEN_1; + + constructor(address _token0, address _token1) { + TOKEN_0 = _token0; + TOKEN_1 = _token1; + } + + // ==== External Functions ==== + function addLiquidity( + address pool, + int24 bottomTick, + int24 topTick, + uint128 amount + ) + external + returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) + { + // Get balances before + uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); + + // Call mint + (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( + address(this), + address(this), + bottomTick, + topTick, + amount, + abi.encode(TOKEN_0, TOKEN_1) + ); + + // Get balances after to account for fees + uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); + + // Calculate actual amounts transferred accounting for fees + amount0 = balance0Before - balance0After; + amount1 = balance1Before - balance1After; + + return (amount0, amount1, liquidityActual); + } + + function algebraMintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata + ) external { + // Check token balances + uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); + uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); + + // Transfer what we can, limited by actual balance + if (amount0Owed > 0) { + uint256 amount0ToSend = amount0Owed > balance0 + ? balance0 + : amount0Owed; + uint256 balance0Before = IERC20(TOKEN_0).balanceOf( + address(msg.sender) + ); + IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); + uint256 balance0After = IERC20(TOKEN_0).balanceOf( + address(msg.sender) + ); + // solhint-disable-next-line gas-custom-errors + require(balance0After > balance0Before, "Transfer failed"); + } + + if (amount1Owed > 0) { + uint256 amount1ToSend = amount1Owed > balance1 + ? balance1 + : amount1Owed; + uint256 balance1Before = IERC20(TOKEN_1).balanceOf( + address(msg.sender) + ); + IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); + uint256 balance1After = IERC20(TOKEN_1).balanceOf( + address(msg.sender) + ); + // solhint-disable-next-line gas-custom-errors + require(balance1After > balance1Before, "Transfer failed"); + } + } +} diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 75e22cb46..ac0079340 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -65,7 +65,7 @@ contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { } // ==== Callback Test Hooks ==== - function _getCallbackSelector() internal override returns (bytes4) { + function _getCallbackSelector() internal view override returns (bytes4) { return izumiV3Facet.swapX2YCallback.selector; } @@ -91,10 +91,6 @@ contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); - IERC20(tokenIn).approve( - address(ldaDiamond), - _getDefaultAmountForTokenIn() - ); bytes memory swapData = _buildIzumiV3SwapData( IzumiV3SwapParams({ diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index b4bae70f3..f656595a9 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -// ==== Imports ==== import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; @@ -11,28 +10,7 @@ import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; -contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { - // ==== Events ==== - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - function hook( - address sender, - uint256 amount0, - uint256 amount1, - bytes calldata data - ) external { - emit HookCalled(sender, amount0, amount1, data); - } -} - -// ==== Main Test Contract ==== contract VelodromeV2FacetTest is BaseDexFacetTest { - // ==== Storage Variables ==== VelodromeV2Facet internal velodromeV2Facet; // ==== Constants ==== @@ -130,7 +108,6 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { // ==== Test Cases ==== - // no stable swap function test_CanSwap() public override { deal( address(tokenIn), @@ -223,7 +200,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } function test_CanSwap_FromDexAggregator() public override { - // // fund dex aggregator contract so that the contract holds USDC + // fund dex aggregator contract so that the contract holds USDC deal( address(tokenIn), address(ldaDiamond), @@ -236,9 +213,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { from: address(ldaDiamond), to: address(USER_SENDER), tokenIn: address(tokenIn), - amountIn: IERC20(address(tokenIn)).balanceOf( - address(ldaDiamond) - ) - 1, // adjust for slot undrain protection: subtract 1 token so that the + amountIn: _getDefaultAmountForTokenIn() - 1, // adjust for slot undrain protection: subtract 1 token so that the // aggregator's balance isn't completely drained, matching the contract's safeguard tokenOut: address(tokenOut), stable: false, @@ -274,7 +249,6 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { vm.stopPrank(); } - // Override the abstract test with VelodromeV2 implementation function test_CanSwap_MultiHop() public override { deal( address(tokenIn), @@ -496,11 +470,6 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { swapDataZeroPool ); - IERC20(address(tokenIn)).approve( - address(ldaDiamond), - _getDefaultAmountForTokenIn() - ); - _executeAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), @@ -667,11 +636,10 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { stable: params.stable, factory: address(VELODROME_V2_FACTORY_REGISTRY) }); - uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( + uint256[] memory expectedOutput = VELODROME_V2_ROUTER.getAmountsOut( params.amountIn, routes ); - emit log_named_uint("Expected amount out", amounts[1]); // Retrieve the pool address. address pool = VELODROME_V2_ROUTER.poolFor( @@ -680,7 +648,6 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { params.stable, VELODROME_V2_FACTORY_REGISTRY ); - emit log_named_uint("Pool address:", uint256(uint160(pool))); // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. CommandType commandCode = params.from == address(ldaDiamond) @@ -710,13 +677,6 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { swapData ); - // approve the aggregator to spend tokenIn. - IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - - // capture initial token balances. - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - emit log_named_uint("Initial tokenIn balance", initialTokenIn); - ExpectedEvent[] memory expectedEvents = new ExpectedEvent[](1); if (params.callbackStatus == CallbackStatus.Enabled) { bytes[] memory eventParams = new bytes[](4); @@ -746,7 +706,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, - minOut: amounts[1], + minOut: expectedOutput[1], sender: params.from, recipient: params.to, commandType: params.from == address(ldaDiamond) @@ -757,7 +717,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { expectedEvents, false, RouteEventVerification({ - expectedExactOut: amounts[1], + expectedExactOut: expectedOutput[1], checkData: true }) ); @@ -922,3 +882,22 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { } } } + +contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { + // ==== Events ==== + event HookCalled( + address sender, + uint256 amount0, + uint256 amount1, + bytes data + ); + + function hook( + address sender, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external { + emit HookCalled(sender, amount0, amount1, data); + } +} diff --git a/test/solidity/utils/MockNoCallbackPool.sol b/test/solidity/utils/MockNoCallbackPool.sol index 1bb5c8903..85128a110 100644 --- a/test/solidity/utils/MockNoCallbackPool.sol +++ b/test/solidity/utils/MockNoCallbackPool.sol @@ -5,17 +5,26 @@ import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; /// @title MockNoCallbackPool /// @author LI.FI (https://li.fi) -/// @notice Mock pool that doesn't call back for any DEX-style swap +/// @notice Mock pool that simulates successful swaps without executing callbacks +/// @dev Used to test callback verification in facets. This mock: +/// 1. Implements UniV3-style interface for base compatibility +/// 2. Returns dummy token addresses for token0/token1 +/// 3. Returns (0,0) for all swap calls without executing callbacks +/// 4. Catches all non-standard swap calls (like Izumi's swapX2Y) via fallback /// @custom:version 1.0.0 contract MockNoCallbackPool is IUniV3StylePool { + /// @notice Returns a dummy token0 address function token0() external pure returns (address) { return address(1); } + /// @notice Returns a dummy token1 address function token1() external pure returns (address) { return address(2); } + /// @notice UniV3-style swap that doesn't execute callbacks + /// @dev Always returns (0,0) to simulate successful swap without callback function swap( address, bool, @@ -23,16 +32,20 @@ contract MockNoCallbackPool is IUniV3StylePool { uint160, bytes calldata ) external pure returns (int256, int256) { - // Do nothing - don't call the callback + // Simulate successful swap without executing callback return (0, 0); } - // Generic fallback for any other swap function (Izumi swapX2Y or swapY2X) - fallback() external payable { + /// @notice Catch-all for non-standard swap functions (Izumi, Algebra, etc) + /// @dev Returns (0,0) encoded as bytes to match return types of various swap functions + fallback() external { assembly { mstore(0x00, 0) mstore(0x20, 0) - return(0x00, 0x40) // (int256, int256) = (0,0) + return(0x00, 0x40) // Return (0,0) for any swap function } } + + /// @notice Required to receive ETH from swaps if needed + receive() external payable {} } From 2bfdec6e0d8b2b06ca38ad7510ff65c6529b06d9 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 20 Aug 2025 20:20:10 +0200 Subject: [PATCH 046/220] Update version annotations in various contracts, remove unused LibInputStream library, and enhance comments for clarity. Refactor tests --- src/Helpers/ReentrancyGuard.sol | 2 +- src/Helpers/WithdrawablePeriphery.sol | 5 +- src/Libraries/LibCallbackManager.sol | 14 +- src/Libraries/LibInputStream.sol | 146 - src/Libraries/LibPackedStream.sol | 6 +- src/Libraries/LibUniV3Logic.sol | 4 +- src/Periphery/Lda/BaseRouteConstants.sol | 1 + src/Periphery/Lda/Facets/AlgebraFacet.sol | 2 +- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 2 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 1 + src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 7 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 1 + src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 6 +- src/Periphery/LiFiDEXAggregator.sol | 23 + .../Helpers/WithdrawablePeriphery.t.sol | 1 - .../Periphery/Lda/BaseCoreRouteTest.t.sol | 23 +- .../Lda/Facets/UniV3StyleFacet.t.sol | 28 - .../Lda/Facets/VelodromeV2Facet.t.sol | 4 +- .../Lda/LiFiDEXAggregatorUpgrade.t.sol | 3946 ----------------- .../Periphery/Lda/utils/LdaDiamondTest.sol | 25 +- 20 files changed, 76 insertions(+), 4171 deletions(-) delete mode 100644 src/Libraries/LibInputStream.sol delete mode 100644 test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol delete mode 100644 test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol diff --git a/src/Helpers/ReentrancyGuard.sol b/src/Helpers/ReentrancyGuard.sol index bd80a433a..32585e0c2 100644 --- a/src/Helpers/ReentrancyGuard.sol +++ b/src/Helpers/ReentrancyGuard.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: UNLICENSED -/// @custom:version 1.0.0 pragma solidity ^0.8.17; /// @title Reentrancy Guard /// @author LI.FI (https://li.fi) /// @notice Abstract contract to provide protection against reentrancy +/// @custom:version 1.0.0 abstract contract ReentrancyGuard { /// Storage /// diff --git a/src/Helpers/WithdrawablePeriphery.sol b/src/Helpers/WithdrawablePeriphery.sol index 60b35cb2e..eb2e5b334 100644 --- a/src/Helpers/WithdrawablePeriphery.sol +++ b/src/Helpers/WithdrawablePeriphery.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: LGPL-3.0-only -/// @custom:version 1.0.0 pragma solidity ^0.8.17; import { TransferrableOwnership } from "./TransferrableOwnership.sol"; @@ -7,6 +6,10 @@ import { LibAsset } from "../Libraries/LibAsset.sol"; import { ExternalCallFailed } from "../Errors/GenericErrors.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +/// @title WithdrawablePeriphery +/// @author LI.FI (https://li.fi) +/// @notice Abstract contract to provide a withdraw function for tokens +/// @custom:version 1.0.0 abstract contract WithdrawablePeriphery is TransferrableOwnership { using SafeTransferLib for address; diff --git a/src/Libraries/LibCallbackManager.sol b/src/Libraries/LibCallbackManager.sol index 90794eed6..fb98fbc6f 100644 --- a/src/Libraries/LibCallbackManager.sol +++ b/src/Libraries/LibCallbackManager.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-only -/// @custom:version 1.0.0 pragma solidity ^0.8.17; /// @title Callback Manager Library /// @author LI.FI (https://li.fi) /// @notice Provides functionality for managing callback validation in diamond-safe storage +/// @custom:version 1.0.0 library LibCallbackManager { /// Types /// - bytes32 internal constant NAMESPACE = keccak256("com.lifi.lda.callbackmanager"); + bytes32 internal constant NAMESPACE = + keccak256("com.lifi.lda.callbackmanager"); /// Storage /// struct CallbackStorage { @@ -48,11 +49,4 @@ library LibCallbackManager { revert UnexpectedCallbackSender(msg.sender, expected); } } - - /// @dev Modifier wrapper for callback verification and cleanup - modifier onlyExpectedCallback() { - verifyCallbackSender(); - _; - clear(); - } -} \ No newline at end of file +} diff --git a/src/Libraries/LibInputStream.sol b/src/Libraries/LibInputStream.sol deleted file mode 100644 index 7235e0743..000000000 --- a/src/Libraries/LibInputStream.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -/// @custom:version 1.0.0 -pragma solidity ^0.8.17; - -/// @title InputStream Library -/// @author LI.FI (https://li.fi) -/// @notice Provides functionality for reading data from packed byte streams with support for selectors -library LibInputStream { - /** @notice Creates stream from data - * @param data data - */ - function createStream( - bytes memory data - ) internal pure returns (uint256 stream) { - assembly { - stream := mload(0x40) - mstore(0x40, add(stream, 64)) - mstore(stream, data) - let length := mload(data) - mstore(add(stream, 32), add(data, length)) - } - } - - /** @notice Checks if stream is not empty - * @param stream stream - */ - function isNotEmpty(uint256 stream) internal pure returns (bool) { - uint256 pos; - uint256 finish; - assembly { - pos := mload(stream) - finish := mload(add(stream, 32)) - } - return pos < finish; - } - - /** @notice Reads uint8 from the stream - * @param stream stream - */ - function readUint8(uint256 stream) internal pure returns (uint8 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 1) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint16 from the stream - * @param stream stream - */ - function readUint16(uint256 stream) internal pure returns (uint16 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 2) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint24 from the stream - * @param stream stream - */ - function readUint24(uint256 stream) internal pure returns (uint24 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 3) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint32 from the stream - * @param stream stream - */ - function readUint32(uint256 stream) internal pure returns (uint32 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 4) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads bytes4 from the stream (for function selectors) - * @param stream stream - */ - function readBytes4(uint256 stream) internal pure returns (bytes4 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 4) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint256 from the stream - * @param stream stream - */ - function readUint(uint256 stream) internal pure returns (uint256 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 32) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads bytes32 from the stream - * @param stream stream - */ - function readBytes32(uint256 stream) internal pure returns (bytes32 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 32) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads address from the stream - * @param stream stream - */ - function readAddress(uint256 stream) internal pure returns (address res) { - assembly { - let pos := mload(stream) - pos := add(pos, 20) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads bytes from the stream - * @param stream stream - */ - function readBytes( - uint256 stream - ) internal pure returns (bytes memory res) { - assembly { - let pos := mload(stream) - res := add(pos, 32) - let length := mload(res) - mstore(stream, add(res, length)) - } - } -} \ No newline at end of file diff --git a/src/Libraries/LibPackedStream.sol b/src/Libraries/LibPackedStream.sol index b6acff9f0..7fd7b7acd 100644 --- a/src/Libraries/LibPackedStream.sol +++ b/src/Libraries/LibPackedStream.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.17; /// @title LibPackedStream -/// @author LI.FI -/// @notice Minimal byte-stream reader for compact calldata formats used by LDA v2. -/// @dev Public API is intentionally identical to the previous stream library. +/// @author LI.FI (https://li.fi) +/// @notice Minimal byte-stream reader for compact calldata formats +/// @custom:version 1.0.0 library LibPackedStream { /// @dev Returns the start and finish pointers for a bytes array. function _bounds( diff --git a/src/Libraries/LibUniV3Logic.sol b/src/Libraries/LibUniV3Logic.sol index ea07300fa..516745e9e 100644 --- a/src/Libraries/LibUniV3Logic.sol +++ b/src/Libraries/LibUniV3Logic.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-only -/// @custom:version 1.0.0 pragma solidity ^0.8.17; -import { LibInputStream } from "./LibInputStream.sol"; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title UniV3 Logic Library /// @author LI.FI (https://li.fi) /// @notice Shared logic for UniV3-style DEX protocols +/// @custom:version 1.0.0 library LibUniV3Logic { using SafeERC20 for IERC20; - using LibInputStream for uint256; /// @notice Handles a generic UniV3-style callback /// @param amount0Delta The amount of token0 owed to pool diff --git a/src/Periphery/Lda/BaseRouteConstants.sol b/src/Periphery/Lda/BaseRouteConstants.sol index 2c4c1ce82..a67a16482 100644 --- a/src/Periphery/Lda/BaseRouteConstants.sol +++ b/src/Periphery/Lda/BaseRouteConstants.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.17; /// @author LI.FI (https://li.fi) /// @notice Base contract providing common constants for DEX facets /// @dev Abstract contract with shared constants to avoid duplication across facets +/// @custom:version 1.0.0 abstract contract BaseRouteConstants { /// @dev Constant indicating swap direction from token0 to token1 uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 6172859ad..9b0a963d6 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -14,7 +14,6 @@ import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title AlgebraFacet /// @author LI.FI (https://li.fi) /// @notice Handles Algebra swaps with callback management -/// @dev Implements direct selector-callable swap function for Algebra pools /// @custom:version 1.0.0 contract AlgebraFacet is BaseRouteConstants { using LibPackedStream for uint256; @@ -42,6 +41,7 @@ contract AlgebraFacet is BaseRouteConstants { uint256 amountIn ) external { uint256 stream = LibPackedStream.createStream(swapData); + address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; address recipient = stream.readAddress(); diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index e0adb629a..81a6eef2a 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -172,7 +172,7 @@ contract CoreRouteFacet is } } - // ==== Private Functions - Command Handlers ==== + // ==== Private Functions ==== /// @notice Applies ERC20 permit for token approval /// @dev Reads permit parameters from the stream and calls permit on the token diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 37c00743b..b7ba3aa05 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -41,6 +41,7 @@ contract IzumiV3Facet is BaseRouteConstants { uint256 amountIn ) external { uint256 stream = LibPackedStream.createStream(swapData); + address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; // 0 = Y2X, 1 = X2Y address recipient = stream.readAddress(); diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index e2d67b2b7..879885e9a 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -31,9 +31,10 @@ contract SyncSwapV2Facet { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); - address to = stream.readAddress(); + address recipient = stream.readAddress(); - if (pool == address(0) || to == address(0)) revert InvalidCallData(); + if (pool == address(0) || recipient == address(0)) + revert InvalidCallData(); // withdrawMode meaning for SyncSwap via vault: // 1: Withdraw raw ETH (native) @@ -62,7 +63,7 @@ contract SyncSwapV2Facet { ISyncSwapVault(target).deposit(tokenIn, pool); } - bytes memory data = abi.encode(tokenIn, to, withdrawMode); + bytes memory data = abi.encode(tokenIn, recipient, withdrawMode); ISyncSwapPool(pool).swap(data, from, address(0), new bytes(0)); } diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index d5d61b536..08ed21b58 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -50,6 +50,7 @@ contract UniV3StyleFacet is BaseRouteConstants { uint256 amountIn ) external { uint256 stream = LibPackedStream.createStream(swapData); + address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; address recipient = stream.readAddress(); diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 4efb111f3..8055569c6 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -38,13 +38,17 @@ contract VelodromeV2Facet is BaseRouteConstants { uint256 amountIn ) external { uint256 stream = LibPackedStream.createStream(swapData); + address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; address recipient = stream.readAddress(); + if (pool == address(0) || recipient == address(0)) revert InvalidCallData(); + // solhint-disable-next-line max-line-length - bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee + bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. + // Will revert if contract (recipient) does not implement IVelodromeV2PoolCallee. if (from == INTERNAL_INPUT_SOURCE) { (uint256 reserve0, uint256 reserve1, ) = IVelodromeV2Pool(pool) diff --git a/src/Periphery/LiFiDEXAggregator.sol b/src/Periphery/LiFiDEXAggregator.sol index 92e5e1e45..6e5e16a10 100644 --- a/src/Periphery/LiFiDEXAggregator.sol +++ b/src/Periphery/LiFiDEXAggregator.sol @@ -1,5 +1,28 @@ // SPDX-License-Identifier: UNLICENSED +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. +/// TODO: remove this file. + pragma solidity ^0.8.17; import { SafeERC20, IERC20, IERC20Permit } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/test/solidity/Helpers/WithdrawablePeriphery.t.sol b/test/solidity/Helpers/WithdrawablePeriphery.t.sol index 08596cd67..d2df0fdce 100644 --- a/test/solidity/Helpers/WithdrawablePeriphery.t.sol +++ b/test/solidity/Helpers/WithdrawablePeriphery.t.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: LGPL-3.0-only -/// @custom:version 1.0.0 pragma solidity ^0.8.17; import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol"; diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index b974a6dcc..f68e46c1e 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -8,27 +8,6 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; -contract MockPullERC20Facet { - using SafeERC20 for IERC20; - - // Pulls `amountIn` from msg.sender if `from == msg.sender` - function pull( - bytes memory /*payload*/, - address from, - address tokenIn, - uint256 amountIn - ) external returns (uint256) { - if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(this), - amountIn - ); - } - return amountIn; - } -} - abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { using SafeERC20 for IERC20; @@ -70,7 +49,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { // ==== Constants ==== uint16 internal constant FULL_SHARE = 65535; - // ==== Storage Variables ==== + // ==== Variables ==== CoreRouteFacet internal coreRouteFacet; // ==== Events ==== diff --git a/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol b/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol deleted file mode 100644 index be1176120..000000000 --- a/test/solidity/Periphery/Lda/Facets/UniV3StyleFacet.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -// import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; -// import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; - -// contract UniV3StyleFacetTest is BaseDexFacetTest { -// UniV3StyleFacet internal uniV3StyleFacet; - -// function setUp() public { -// customBlockNumberForForking = 18277082; -// initTestBase(); - -// uniV3StyleFacet = new UniV3StyleFacet(); -// bytes4[] memory functionSelectors = new bytes4[](2); -// functionSelectors[0] = uniV3StyleFacet -// .swapUniV3 -// .selector; -// functionSelectors[1] = uniV3StyleFacet -// .uniswapV3SwapCallback -// .selector; - -// addFacet(address(ldaDiamond), address(uniV3StyleFacet), functionSelectors); -// uniV3StyleFacet = UniV3StyleFacet(address(ldaDiamond)); - -// setFacetAddressInTestBase(address(uniV3StyleFacet), "UniV3StyleFacet"); -// } -// } diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index f656595a9..0502758c3 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -93,7 +93,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { function _setupDexEnv() internal override { tokenIn = IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); // USDC tokenMid = IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); // STG - tokenOut = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); // STG + tokenOut = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); // USDC.e // pools vary by test; and they are fetched inside tests } @@ -191,7 +191,7 @@ contract VelodromeV2FacetTest is BaseDexFacetTest { tokenIn: address(tokenOut), amountIn: _getDefaultAmountForTokenIn() / 2, tokenOut: address(tokenIn), - stable: false, + stable: true, direction: SwapDirection.Token1ToToken0, callbackStatus: CallbackStatus.Disabled }) diff --git a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol b/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol deleted file mode 100644 index 0a2745917..000000000 --- a/test/solidity/Periphery/Lda/LiFiDEXAggregatorUpgrade.t.sol +++ /dev/null @@ -1,3946 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -// import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -// import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -// import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -// import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; -// import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; -// import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -// import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -// import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; -// import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; -// import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -// import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -// import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; -// import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -// import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; - -// import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; -// import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -// import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; -// import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; -// import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; -// import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; - -// import { TestToken as ERC20 } from "../../utils/TestToken.sol"; -// import { MockFeeOnTransferToken } from "../../utils/MockTokenFeeOnTransfer.sol"; -// import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; -// import { TestHelpers } from "../../utils/TestHelpers.sol"; - -// // Command codes for route processing -// enum CommandType { -// None, // 0 - not used -// ProcessMyERC20, // 1 - processMyERC20 -// ProcessUserERC20, // 2 - processUserERC20 -// ProcessNative, // 3 - processNative -// ProcessOnePool, // 4 - processOnePool -// ProcessInsideBento, // 5 - processInsideBento -// ApplyPermit // 6 - applyPermit -// } -// // Direction constants -// enum SwapDirection { -// Token1ToToken0, // 0 -// Token0ToToken1 // 1 -// } - -// // Callback constants -// enum CallbackStatus { -// Disabled, // 0 -// Enabled // 1 -// } - -// // Other constants -// uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps - -// contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { -// event HookCalled( -// address sender, -// uint256 amount0, -// uint256 amount1, -// bytes data -// ); - -// function hook( -// address sender, -// uint256 amount0, -// uint256 amount1, -// bytes calldata data -// ) external { -// emit HookCalled(sender, amount0, amount1, data); -// } -// } -// /** -// * @title LiFiDexAggregatorUpgradeTest -// * @notice Base test contract with common functionality and abstractions for DEX-specific tests -// */ -// abstract contract LiFiDexAggregatorUpgradeTest is LdaDiamondTest, TestHelpers { -// using SafeERC20 for IERC20; - -// CoreRouteFacet internal coreRouteFacet; - -// // Common events and errors -// event Route( -// address indexed from, -// address to, -// address indexed tokenIn, -// address indexed tokenOut, -// uint256 amountIn, -// uint256 amountOutMin, -// uint256 amountOut -// ); -// event HookCalled( -// address sender, -// uint256 amount0, -// uint256 amount1, -// bytes data -// ); - -// error WrongPoolReserves(); -// error PoolDoesNotExist(); - -// function _addDexFacet() internal virtual; - -// // Setup function for Apechain tests -// function setupApechain() internal { -// customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; -// customBlockNumberForForking = 12912470; -// } - -// function setupHyperEVM() internal { -// customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; -// customBlockNumberForForking = 4433562; -// } - -// function setupXDC() internal { -// customRpcUrlForForking = "ETH_NODE_URI_XDC"; -// customBlockNumberForForking = 89279495; -// } - -// function setupViction() internal { -// customRpcUrlForForking = "ETH_NODE_URI_VICTION"; -// customBlockNumberForForking = 94490946; -// } - -// function setupFlare() internal { -// customRpcUrlForForking = "ETH_NODE_URI_FLARE"; -// customBlockNumberForForking = 42652369; -// } - -// function setupLinea() internal { -// customRpcUrlForForking = "ETH_NODE_URI_LINEA"; -// customBlockNumberForForking = 20077881; -// } - -// function setUp() public virtual override { -// fork(); -// LdaDiamondTest.setUp(); -// _addCoreRouteFacet(); -// _addDexFacet(); -// } - -// function _addCoreRouteFacet() internal { -// coreRouteFacet = new CoreRouteFacet(); -// bytes4[] memory functionSelectors = new bytes4[](1); -// functionSelectors[0] = CoreRouteFacet.processRoute.selector; -// addFacet( -// address(ldaDiamond), -// address(coreRouteFacet), -// functionSelectors -// ); - -// coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); -// } - -// // function test_ContractIsSetUpCorrectly() public { -// // assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); -// // assertEq( -// // liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), -// // true -// // ); -// // assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); -// // } - -// // function testRevert_FailsIfOwnerIsZeroAddress() public { -// // vm.expectRevert(InvalidConfig.selector); - -// // liFiDEXAggregator = new LiFiDEXAggregator( -// // address(0xCAFE), -// // privileged, -// // address(0) -// // ); -// // } - -// // ============================ Abstract DEX Tests ============================ -// /** -// * @notice Abstract test for basic token swapping functionality -// * Each DEX implementation should override this -// */ -// function test_CanSwap() public virtual { -// // Each DEX implementation must override this -// // solhint-disable-next-line gas-custom-errors -// revert("test_CanSwap: Not implemented"); -// } - -// /** -// * @notice Abstract test for swapping tokens from the DEX aggregator -// * Each DEX implementation should override this -// */ -// function test_CanSwap_FromDexAggregator() public virtual { -// // Each DEX implementation must override this -// // solhint-disable-next-line gas-custom-errors -// revert("test_CanSwap_FromDexAggregator: Not implemented"); -// } - -// /** -// * @notice Abstract test for multi-hop swapping -// * Each DEX implementation should override this -// */ -// function test_CanSwap_MultiHop() public virtual { -// // Each DEX implementation must override this -// // solhint-disable-next-line gas-custom-errors -// revert("test_CanSwap_MultiHop: Not implemented"); -// } -// } - -// /** -// * @title VelodromeV2 tests -// * @notice Tests specific to Velodrome V2 -// */ -// contract LiFiDexAggregatorVelodromeV2UpgradeTest is -// LiFiDexAggregatorUpgradeTest -// { -// VelodromeV2Facet internal velodromeV2Facet; - -// // ==================== Velodrome V2 specific variables ==================== -// IVelodromeV2Router internal constant VELODROME_V2_ROUTER = -// IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router -// address internal constant VELODROME_V2_FACTORY_REGISTRY = -// 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; -// IERC20 internal constant USDC_TOKEN = -// IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); -// IERC20 internal constant STG_TOKEN = -// IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); -// IERC20 internal constant USDC_E_TOKEN = -// IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); - -// MockVelodromeV2FlashLoanCallbackReceiver -// internal mockFlashloanCallbackReceiver; - -// // Velodrome V2 structs -// struct VelodromeV2SwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// bool stable; -// SwapDirection direction; -// bool callback; -// } - -// struct MultiHopTestParams { -// address tokenIn; -// address tokenMid; -// address tokenOut; -// address pool1; -// address pool2; -// uint256[] amounts1; -// uint256[] amounts2; -// uint256 pool1Fee; -// uint256 pool2Fee; -// } - -// struct ReserveState { -// uint256 reserve0Pool1; -// uint256 reserve1Pool1; -// uint256 reserve0Pool2; -// uint256 reserve1Pool2; -// } - -// // Setup function for Optimism tests -// function setupOptimism() internal { -// customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; -// customBlockNumberForForking = 133999121; -// } - -// function setUp() public override { -// setupOptimism(); -// super.setUp(); - -// deal(address(USDC_TOKEN), address(USER_SENDER), 1_000 * 1e6); -// } - -// function _addDexFacet() internal override { -// velodromeV2Facet = new VelodromeV2Facet(); -// bytes4[] memory functionSelectors = new bytes4[](1); -// functionSelectors[0] = velodromeV2Facet.swapVelodromeV2.selector; -// addFacet( -// address(ldaDiamond), -// address(velodromeV2Facet), -// functionSelectors -// ); - -// velodromeV2Facet = VelodromeV2Facet(payable(address(ldaDiamond))); -// } - -// // ============================ Velodrome V2 Tests ============================ - -// // no stable swap -// function test_CanSwap() public override { -// vm.startPrank(USER_SENDER); - -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(USER_SENDER), -// to: address(USER_SENDER), -// tokenIn: address(USDC_TOKEN), -// amountIn: 1_000 * 1e6, -// tokenOut: address(STG_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_NoStable_Reverse() public { -// // first perform the forward swap. -// test_CanSwap(); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(STG_TOKEN), -// amountIn: 500 * 1e18, -// tokenOut: address(USDC_TOKEN), -// stable: false, -// direction: SwapDirection.Token1ToToken0, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_Stable() public { -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(USDC_TOKEN), -// amountIn: 1_000 * 1e6, -// tokenOut: address(USDC_E_TOKEN), -// stable: true, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_Stable_Reverse() public { -// // first perform the forward stable swap. -// test_CanSwap_Stable(); - -// vm.startPrank(USER_SENDER); - -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(USDC_E_TOKEN), -// amountIn: 500 * 1e6, -// tokenOut: address(USDC_TOKEN), -// stable: false, -// direction: SwapDirection.Token1ToToken0, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // fund dex aggregator contract so that the contract holds USDC -// deal(address(USDC_TOKEN), address(ldaDiamond), 100_000 * 1e6); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(ldaDiamond), -// to: address(USER_SENDER), -// tokenIn: address(USDC_TOKEN), -// amountIn: IERC20(address(USDC_TOKEN)).balanceOf( -// address(ldaDiamond) -// ) - 1, // adjust for slot undrain protection: subtract 1 token so that the -// // aggregator's balance isn't completely drained, matching the contract's safeguard -// tokenOut: address(USDC_E_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: false -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_FlashloanCallback() public { -// mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// VelodromeV2SwapTestParams({ -// from: address(USER_SENDER), -// to: address(mockFlashloanCallbackReceiver), -// tokenIn: address(USDC_TOKEN), -// amountIn: 1_000 * 1e6, -// tokenOut: address(USDC_E_TOKEN), -// stable: false, -// direction: SwapDirection.Token0ToToken1, -// callback: true -// }) -// ); -// vm.stopPrank(); -// } - -// // Override the abstract test with VelodromeV2 implementation -// function test_CanSwap_MultiHop() public override { -// vm.startPrank(USER_SENDER); - -// // Setup routes and get amounts -// MultiHopTestParams memory params = _setupRoutes( -// address(USDC_TOKEN), -// address(STG_TOKEN), -// address(USDC_E_TOKEN), -// false, -// false -// ); - -// // Get initial reserves BEFORE the swap -// ReserveState memory initialReserves; -// ( -// initialReserves.reserve0Pool1, -// initialReserves.reserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// initialReserves.reserve0Pool2, -// initialReserves.reserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( -// USER_SENDER -// ); - -// // Build route and execute swap -// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); - -// // Approve and execute -// IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// params.tokenIn, -// params.tokenOut, -// 1000 * 1e6, -// params.amounts2[1], -// params.amounts2[1] -// ); - -// coreRouteFacet.processRoute( -// params.tokenIn, -// 1000 * 1e6, -// params.tokenOut, -// params.amounts2[1], -// USER_SENDER, -// route -// ); - -// _verifyUserBalances(params, initialBalance1, initialBalance2); -// _verifyReserves(params, initialReserves); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop_WithStable() public { -// vm.startPrank(USER_SENDER); - -// // Setup routes and get amounts for stable->volatile path -// MultiHopTestParams memory params = _setupRoutes( -// address(USDC_TOKEN), -// address(USDC_E_TOKEN), -// address(STG_TOKEN), -// true, // stable pool for first hop -// false // volatile pool for second hop -// ); - -// // Get initial reserves BEFORE the swap -// ReserveState memory initialReserves; -// ( -// initialReserves.reserve0Pool1, -// initialReserves.reserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// initialReserves.reserve0Pool2, -// initialReserves.reserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// // Record initial balances -// uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( -// USER_SENDER -// ); - -// // Build route and execute swap -// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - -// // Approve and execute -// IERC20(params.tokenIn).approve(address(ldaDiamond), 1000 * 1e6); - -// // vm.expectEmit(true, true, true, true); -// // emit Route( -// // USER_SENDER, -// // USER_SENDER, -// // params.tokenIn, -// // params.tokenOut, -// // 1000 * 1e6, -// // params.amounts2[1], -// // params.amounts2[1] -// // ); - -// coreRouteFacet.processRoute( -// params.tokenIn, -// 1000 * 1e6, -// params.tokenOut, -// params.amounts2[1], -// USER_SENDER, -// route -// ); - -// _verifyUserBalances(params, initialBalance1, initialBalance2); -// _verifyReserves(params, initialReserves); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidPoolOrRecipient() public { -// vm.startPrank(USER_SENDER); - -// // Get a valid pool address first for comparison -// address validPool = VELODROME_V2_ROUTER.poolFor( -// address(USDC_TOKEN), -// address(STG_TOKEN), -// false, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// // --- Test case 1: Zero pool address --- -// // 1. Create the specific swap data blob -// bytes memory swapDataZeroPool = abi.encodePacked( -// VelodromeV2Facet.swapVelodromeV2.selector, -// address(0), // Invalid pool -// uint8(SwapDirection.Token1ToToken0), -// USER_SENDER, -// uint8(CallbackStatus.Disabled) -// ); - -// // 2. Create the full route with the length-prefixed swap data -// bytes memory routeWithZeroPool = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(USDC_TOKEN), -// uint8(1), -// FULL_SHARE, -// uint16(swapDataZeroPool.length), // Length prefix -// swapDataZeroPool -// ); - -// IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); - -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(USDC_TOKEN), -// 1000 * 1e6, -// address(STG_TOKEN), -// 0, -// USER_SENDER, -// routeWithZeroPool -// ); - -// // --- Test case 2: Zero recipient address --- -// bytes memory swapDataZeroRecipient = abi.encodePacked( -// VelodromeV2Facet.swapVelodromeV2.selector, -// validPool, -// uint8(SwapDirection.Token1ToToken0), -// address(0), // Invalid recipient -// uint8(CallbackStatus.Disabled) -// ); - -// bytes memory routeWithZeroRecipient = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(USDC_TOKEN), -// uint8(1), -// FULL_SHARE, -// uint16(swapDataZeroRecipient.length), // Length prefix -// swapDataZeroRecipient -// ); - -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(USDC_TOKEN), -// 1000 * 1e6, -// address(STG_TOKEN), -// 0, -// USER_SENDER, -// routeWithZeroRecipient -// ); - -// vm.stopPrank(); -// } - -// function testRevert_WrongPoolReserves() public { -// vm.startPrank(USER_SENDER); - -// // Setup multi-hop route: USDC -> STG -> USDC.e -// MultiHopTestParams memory params = _setupRoutes( -// address(USDC_TOKEN), -// address(STG_TOKEN), -// address(USDC_E_TOKEN), -// false, -// false -// ); - -// // Build multi-hop route -// bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - -// deal(address(USDC_TOKEN), USER_SENDER, 1000 * 1e6); - -// IERC20(address(USDC_TOKEN)).approve(address(ldaDiamond), 1000 * 1e6); - -// // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves -// vm.mockCall( -// params.pool2, -// abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), -// abi.encode(0, 0, block.timestamp) -// ); - -// vm.expectRevert(WrongPoolReserves.selector); - -// coreRouteFacet.processRoute( -// address(USDC_TOKEN), -// 1000 * 1e6, -// address(USDC_E_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// // ============================ Velodrome V2 Helper Functions ============================ - -// /** -// * @dev Helper function to test a VelodromeV2 swap. -// * Uses a struct to group parameters and reduce stack depth. -// */ -// function _testSwap(VelodromeV2SwapTestParams memory params) internal { -// // get expected output amounts from the router. -// IVelodromeV2Router.Route[] -// memory routes = new IVelodromeV2Router.Route[](1); -// routes[0] = IVelodromeV2Router.Route({ -// from: params.tokenIn, -// to: params.tokenOut, -// stable: params.stable, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( -// params.amountIn, -// routes -// ); -// emit log_named_uint("Expected amount out", amounts[1]); - -// // Retrieve the pool address. -// address pool = VELODROME_V2_ROUTER.poolFor( -// params.tokenIn, -// params.tokenOut, -// params.stable, -// VELODROME_V2_FACTORY_REGISTRY -// ); -// emit log_named_uint("Pool address:", uint256(uint160(pool))); - -// // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. -// CommandType commandCode = params.from == address(ldaDiamond) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; - -// // 1. Pack the data for the specific swap FIRST -// bytes memory swapData = abi.encodePacked( -// VelodromeV2Facet.swapVelodromeV2.selector, -// pool, -// params.direction, -// params.to, -// params.callback -// ? uint8(CallbackStatus.Enabled) -// : uint8(CallbackStatus.Disabled) -// ); -// // build the route. -// bytes memory route = abi.encodePacked( -// uint8(commandCode), -// params.tokenIn, -// uint8(1), // num splits -// FULL_SHARE, -// uint16(swapData.length), // <--- Add length prefix -// swapData -// ); - -// // approve the aggregator to spend tokenIn. -// IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - -// // capture initial token balances. -// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); -// emit log_named_uint("Initial tokenIn balance", initialTokenIn); - -// address from = params.from == address(ldaDiamond) -// ? USER_SENDER -// : params.from; -// if (params.callback == true) { -// vm.expectEmit(true, false, false, false); -// emit HookCalled( -// address(ldaDiamond), -// 0, -// 0, -// abi.encode(params.tokenIn) -// ); -// } -// vm.expectEmit(true, true, true, true); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// amounts[1], -// amounts[1] -// ); - -// // execute the swap -// coreRouteFacet.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// amounts[1], -// params.to, -// route -// ); - -// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); -// emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); -// emit log_named_uint( -// "TokenOut received", -// finalTokenOut - initialTokenOut -// ); - -// assertApproxEqAbs( -// initialTokenIn - finalTokenIn, -// params.amountIn, -// 1, // 1 wei tolerance -// "TokenIn amount mismatch" -// ); -// assertEq( -// finalTokenOut - initialTokenOut, -// amounts[1], -// "TokenOut amount mismatch" -// ); -// } - -// // Helper function to set up routes and get amounts -// function _setupRoutes( -// address tokenIn, -// address tokenMid, -// address tokenOut, -// bool isStableFirst, -// bool isStableSecond -// ) private view returns (MultiHopTestParams memory params) { -// params.tokenIn = tokenIn; -// params.tokenMid = tokenMid; -// params.tokenOut = tokenOut; - -// // Setup first hop route -// IVelodromeV2Router.Route[] -// memory routes1 = new IVelodromeV2Router.Route[](1); -// routes1[0] = IVelodromeV2Router.Route({ -// from: tokenIn, -// to: tokenMid, -// stable: isStableFirst, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( -// 1000 * 1e6, -// routes1 -// ); - -// // Setup second hop route -// IVelodromeV2Router.Route[] -// memory routes2 = new IVelodromeV2Router.Route[](1); -// routes2[0] = IVelodromeV2Router.Route({ -// from: tokenMid, -// to: tokenOut, -// stable: isStableSecond, -// factory: address(VELODROME_V2_FACTORY_REGISTRY) -// }); -// params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( -// params.amounts1[1], -// routes2 -// ); - -// // Get pool addresses -// params.pool1 = VELODROME_V2_ROUTER.poolFor( -// tokenIn, -// tokenMid, -// isStableFirst, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// params.pool2 = VELODROME_V2_ROUTER.poolFor( -// tokenMid, -// tokenOut, -// isStableSecond, -// VELODROME_V2_FACTORY_REGISTRY -// ); - -// // Get pool fees info -// params.pool1Fee = IVelodromeV2PoolFactory( -// VELODROME_V2_FACTORY_REGISTRY -// ).getFee(params.pool1, isStableFirst); -// params.pool2Fee = IVelodromeV2PoolFactory( -// VELODROME_V2_FACTORY_REGISTRY -// ).getFee(params.pool2, isStableSecond); - -// return params; -// } - -// // function to build first hop of the route -// function _buildFirstHop( -// address pool1, -// address pool2, // The recipient of the first hop is the next pool -// uint8 direction -// ) private pure returns (bytes memory) { -// return -// abi.encodePacked( -// VelodromeV2Facet.swapVelodromeV2.selector, -// pool1, -// direction, -// pool2, // Send intermediate tokens to the next pool for the second hop -// uint8(CallbackStatus.Disabled) -// ); -// } - -// // function to build second hop of the route -// function _buildSecondHop( -// address pool2, -// address recipient, -// uint8 direction -// ) private pure returns (bytes memory) { -// return -// abi.encodePacked( -// VelodromeV2Facet.swapVelodromeV2.selector, -// pool2, -// direction, -// recipient, // Final recipient -// uint8(CallbackStatus.Disabled) -// ); -// } - -// // route building function -// function _buildMultiHopRoute( -// MultiHopTestParams memory params, -// address recipient, -// uint8 firstHopDirection, -// uint8 secondHopDirection -// ) private pure returns (bytes memory) { -// // 1. Get the specific data for each hop -// bytes memory firstHopData = _buildFirstHop( -// params.pool1, -// params.pool2, -// firstHopDirection -// ); - -// bytes memory secondHopData = _buildSecondHop( -// params.pool2, -// recipient, -// secondHopDirection -// ); - -// // 2. Assemble the first command -// bytes memory firstCommand = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// params.tokenIn, -// uint8(1), // num splits -// FULL_SHARE, -// uint16(firstHopData.length), // <--- Add length prefix -// firstHopData -// ); - -// // 3. Assemble the second command -// // The second hop takes tokens already held by the diamond, so we use ProcessOnePool -// bytes memory secondCommand = abi.encodePacked( -// uint8(CommandType.ProcessOnePool), -// params.tokenMid, -// uint16(secondHopData.length), // <--- Add length prefix -// secondHopData -// ); - -// // 4. Concatenate the commands to create the final route -// return bytes.concat(firstCommand, secondCommand); -// } - -// function _verifyUserBalances( -// MultiHopTestParams memory params, -// uint256 initialBalance1, -// uint256 initialBalance2 -// ) private { -// // Verify token balances -// uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// assertApproxEqAbs( -// initialBalance1 - finalBalance1, -// 1000 * 1e6, -// 1, // 1 wei tolerance -// "Token1 spent amount mismatch" -// ); -// assertEq( -// finalBalance2 - initialBalance2, -// params.amounts2[1], -// "Token2 received amount mismatch" -// ); -// } - -// function _verifyReserves( -// MultiHopTestParams memory params, -// ReserveState memory initialReserves -// ) private { -// // Get reserves after swap -// ( -// uint256 finalReserve0Pool1, -// uint256 finalReserve1Pool1, - -// ) = IVelodromeV2Pool(params.pool1).getReserves(); -// ( -// uint256 finalReserve0Pool2, -// uint256 finalReserve1Pool2, - -// ) = IVelodromeV2Pool(params.pool2).getReserves(); - -// address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); -// address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); - -// // Calculate exact expected changes -// uint256 amountInAfterFees = 1000 * -// 1e6 - -// ((1000 * 1e6 * params.pool1Fee) / 10000); - -// // Assert exact reserve changes for Pool1 -// if (token0Pool1 == params.tokenIn) { -// // tokenIn is token0, so reserve0 should increase and reserve1 should decrease -// assertEq( -// finalReserve0Pool1 - initialReserves.reserve0Pool1, -// amountInAfterFees, -// "Pool1 reserve0 (tokenIn) change incorrect" -// ); -// assertEq( -// initialReserves.reserve1Pool1 - finalReserve1Pool1, -// params.amounts1[1], -// "Pool1 reserve1 (tokenMid) change incorrect" -// ); -// } else { -// // tokenIn is token1, so reserve1 should increase and reserve0 should decrease -// assertEq( -// finalReserve1Pool1 - initialReserves.reserve1Pool1, -// amountInAfterFees, -// "Pool1 reserve1 (tokenIn) change incorrect" -// ); -// assertEq( -// initialReserves.reserve0Pool1 - finalReserve0Pool1, -// params.amounts1[1], -// "Pool1 reserve0 (tokenMid) change incorrect" -// ); -// } - -// // Assert exact reserve changes for Pool2 -// if (token0Pool2 == params.tokenMid) { -// // tokenMid is token0, so reserve0 should increase and reserve1 should decrease -// assertEq( -// finalReserve0Pool2 - initialReserves.reserve0Pool2, -// params.amounts1[1] - -// ((params.amounts1[1] * params.pool2Fee) / 10000), -// "Pool2 reserve0 (tokenMid) change incorrect" -// ); -// assertEq( -// initialReserves.reserve1Pool2 - finalReserve1Pool2, -// params.amounts2[1], -// "Pool2 reserve1 (tokenOut) change incorrect" -// ); -// } else { -// // tokenMid is token1, so reserve1 should increase and reserve0 should decrease -// assertEq( -// finalReserve1Pool2 - initialReserves.reserve1Pool2, -// params.amounts1[1] - -// ((params.amounts1[1] * params.pool2Fee) / 10000), -// "Pool2 reserve1 (tokenMid) change incorrect" -// ); -// assertEq( -// initialReserves.reserve0Pool2 - finalReserve0Pool2, -// params.amounts2[1], -// "Pool2 reserve0 (tokenOut) change incorrect" -// ); -// } -// } -// } - -// contract AlgebraLiquidityAdderHelper { -// address public immutable TOKEN_0; -// address public immutable TOKEN_1; - -// constructor(address _token0, address _token1) { -// TOKEN_0 = _token0; -// TOKEN_1 = _token1; -// } - -// function addLiquidity( -// address pool, -// int24 bottomTick, -// int24 topTick, -// uint128 amount -// ) -// external -// returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) -// { -// // Get balances before -// uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Call mint -// (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( -// address(this), -// address(this), -// bottomTick, -// topTick, -// amount, -// abi.encode(TOKEN_0, TOKEN_1) -// ); - -// // Get balances after to account for fees -// uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Calculate actual amounts transferred accounting for fees -// amount0 = balance0Before - balance0After; -// amount1 = balance1Before - balance1After; - -// return (amount0, amount1, liquidityActual); -// } - -// function algebraMintCallback( -// uint256 amount0Owed, -// uint256 amount1Owed, -// bytes calldata -// ) external { -// // Check token balances -// uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); -// uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); - -// // Transfer what we can, limited by actual balance -// if (amount0Owed > 0) { -// uint256 amount0ToSend = amount0Owed > balance0 -// ? balance0 -// : amount0Owed; -// uint256 balance0Before = IERC20(TOKEN_0).balanceOf( -// address(msg.sender) -// ); -// IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); -// uint256 balance0After = IERC20(TOKEN_0).balanceOf( -// address(msg.sender) -// ); -// // solhint-disable-next-line gas-custom-errors -// require(balance0After > balance0Before, "Transfer failed"); -// } - -// if (amount1Owed > 0) { -// uint256 amount1ToSend = amount1Owed > balance1 -// ? balance1 -// : amount1Owed; -// uint256 balance1Before = IERC20(TOKEN_1).balanceOf( -// address(msg.sender) -// ); -// IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); -// uint256 balance1After = IERC20(TOKEN_1).balanceOf( -// address(msg.sender) -// ); -// // solhint-disable-next-line gas-custom-errors -// require(balance1After > balance1Before, "Transfer failed"); -// } -// } -// } - -// /** -// * @title Algebra tests -// * @notice Tests specific to Algebra -// */ -// contract LiFiDexAggregatorAlgebraUpgradeTest is LiFiDexAggregatorUpgradeTest { -// AlgebraFacet private algebraFacet; - -// address private constant APE_ETH_TOKEN = -// 0xcF800F4948D16F23333508191B1B1591daF70438; -// address private constant WETH_TOKEN = -// 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; -// address private constant ALGEBRA_FACTORY_APECHAIN = -// 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; -// address private constant ALGEBRA_QUOTER_V2_APECHAIN = -// 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; -// address private constant ALGEBRA_POOL_APECHAIN = -// 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; -// address private constant APE_ETH_HOLDER_APECHAIN = -// address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); - -// address private constant IMPOSSIBLE_POOL_ADDRESS = -// 0x0000000000000000000000000000000000000001; - -// struct AlgebraSwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// SwapDirection direction; -// bool supportsFeeOnTransfer; -// } - -// error AlgebraSwapUnexpected(); - -// function setUp() public override { -// setupApechain(); -// super.setUp(); -// } - -// function _addDexFacet() internal override { -// algebraFacet = new AlgebraFacet(); -// bytes4[] memory functionSelectors = new bytes4[](2); -// functionSelectors[0] = algebraFacet.swapAlgebra.selector; -// functionSelectors[1] = algebraFacet.algebraSwapCallback.selector; -// addFacet( -// address(ldaDiamond), -// address(algebraFacet), -// functionSelectors -// ); - -// algebraFacet = AlgebraFacet(payable(address(ldaDiamond))); -// } - -// // Override the abstract test with Algebra implementation -// function test_CanSwap_FromDexAggregator() public override { -// // Fund LDA from whale address -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(address(coreRouteFacet), 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: address(coreRouteFacet), -// to: address(USER_SENDER), -// tokenIn: APE_ETH_TOKEN, -// amountIn: IERC20(APE_ETH_TOKEN).balanceOf( -// address(coreRouteFacet) -// ) - 1, -// tokenOut: address(WETH_TOKEN), -// direction: SwapDirection.Token0ToToken1, -// supportsFeeOnTransfer: true -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FeeOnTransferToken() public { -// setupApechain(); - -// uint256 amountIn = 534451326669177; -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); - -// vm.startPrank(APE_ETH_HOLDER_APECHAIN); - -// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), amountIn); - -// // Build route for algebra swap with command code 2 (user funds) -// bytes memory swapData = _buildAlgebraSwapData( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: APE_ETH_HOLDER_APECHAIN, -// pool: ALGEBRA_POOL_APECHAIN, -// supportsFeeOnTransfer: true -// }) -// ); - -// // 2. Build the final route with the command and length-prefixed swapData -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// APE_ETH_TOKEN, -// uint8(1), // number of pools/splits -// FULL_SHARE, // 100% share -// uint16(swapData.length), // <--- Add the length prefix -// swapData -// ); - -// // Track initial balance -// uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( -// APE_ETH_HOLDER_APECHAIN -// ); - -// // Execute the swap -// coreRouteFacet.processRoute( -// APE_ETH_TOKEN, -// amountIn, -// WETH_TOKEN, -// 0, // minOut = 0 for this test -// APE_ETH_HOLDER_APECHAIN, -// route -// ); - -// // Verify balances -// uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( -// APE_ETH_HOLDER_APECHAIN -// ); -// assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); - -// vm.stopPrank(); -// } - -// function test_CanSwap() public override { -// vm.startPrank(APE_ETH_HOLDER_APECHAIN); - -// // Transfer tokens from whale to USER_SENDER -// uint256 amountToTransfer = 100 * 1e18; -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); - -// vm.stopPrank(); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: APE_ETH_TOKEN, -// amountIn: 10 * 1e18, -// tokenOut: address(WETH_TOKEN), -// direction: SwapDirection.Token0ToToken1, -// supportsFeeOnTransfer: true -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_Reverse() public { -// test_CanSwap(); - -// vm.startPrank(USER_SENDER); - -// _testAlgebraSwap( -// AlgebraSwapTestParams({ -// from: USER_SENDER, -// to: USER_SENDER, -// tokenIn: address(WETH_TOKEN), -// amountIn: 5 * 1e18, -// tokenOut: APE_ETH_TOKEN, -// direction: SwapDirection.Token1ToToken0, -// supportsFeeOnTransfer: false -// }) -// ); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { -// MultiHopTestState memory state; -// state.isFeeOnTransfer = true; - -// // Setup tokens and pools -// state = _setupTokensAndPools(state); - -// // Execute and verify swap -// _executeAndVerifyMultiHopSwap(state); -// } - -// function test_CanSwap_MultiHop() public override { -// MultiHopTestState memory state; -// state.isFeeOnTransfer = false; - -// // Setup tokens and pools -// state = _setupTokensAndPools(state); - -// // Execute and verify swap -// _executeAndVerifyMultiHopSwap(state); -// } - -// // Test that the proper error is thrown when algebra swap fails -// function testRevert_SwapUnexpected() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Create invalid pool address -// address invalidPool = address(0x999); - -// // Mock token0() call on invalid pool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Create a route with an invalid pool -// bytes memory swapData = _buildAlgebraSwapData( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: invalidPool, -// supportsFeeOnTransfer: true -// }) -// ); - -// bytes memory invalidRoute = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// APE_ETH_TOKEN, -// uint8(1), // number of pools/splits -// FULL_SHARE, // 100% share -// uint16(swapData.length), // <--- Add the length prefix -// swapData -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - -// // Mock the algebra pool to not reset lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSelector( -// IAlgebraPool.swapSupportingFeeOnInputTokens.selector -// ), -// abi.encode(0, 0) -// ); - -// // Expect the AlgebraSwapUnexpected error -// vm.expectRevert(AlgebraSwapUnexpected.selector); - -// coreRouteFacet.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// invalidRoute -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// // Helper function to setup tokens and pools -// function _setupTokensAndPools( -// MultiHopTestState memory state -// ) private returns (MultiHopTestState memory) { -// // Create tokens -// ERC20 tokenA = new ERC20( -// "Token A", -// state.isFeeOnTransfer ? "FTA" : "TA", -// 18 -// ); -// IERC20 tokenB; -// ERC20 tokenC = new ERC20( -// "Token C", -// state.isFeeOnTransfer ? "FTC" : "TC", -// 18 -// ); - -// if (state.isFeeOnTransfer) { -// tokenB = IERC20( -// address( -// new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) -// ) -// ); -// } else { -// tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); -// } - -// state.tokenA = IERC20(address(tokenA)); -// state.tokenB = tokenB; -// state.tokenC = IERC20(address(tokenC)); - -// // Label addresses -// vm.label(address(state.tokenA), "Token A"); -// vm.label(address(state.tokenB), "Token B"); -// vm.label(address(state.tokenC), "Token C"); - -// // Mint initial token supplies -// tokenA.mint(address(this), 1_000_000 * 1e18); -// if (!state.isFeeOnTransfer) { -// ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); -// } else { -// MockFeeOnTransferToken(address(tokenB)).mint( -// address(this), -// 1_000_000 * 1e18 -// ); -// } -// tokenC.mint(address(this), 1_000_000 * 1e18); - -// // Create pools -// state.pool1 = _createAlgebraPool( -// address(state.tokenA), -// address(state.tokenB) -// ); -// state.pool2 = _createAlgebraPool( -// address(state.tokenB), -// address(state.tokenC) -// ); - -// vm.label(state.pool1, "Pool 1"); -// vm.label(state.pool2, "Pool 2"); - -// // Add liquidity -// _addLiquidityToPool( -// state.pool1, -// address(state.tokenA), -// address(state.tokenB) -// ); -// _addLiquidityToPool( -// state.pool2, -// address(state.tokenB), -// address(state.tokenC) -// ); - -// state.amountToTransfer = 100 * 1e18; -// state.amountIn = 50 * 1e18; - -// // Transfer tokens to USER_SENDER -// IERC20(address(state.tokenA)).transfer( -// USER_SENDER, -// state.amountToTransfer -// ); - -// return state; -// } - -// // Helper function to execute and verify the swap -// function _executeAndVerifyMultiHopSwap( -// MultiHopTestState memory state -// ) private { -// vm.startPrank(USER_SENDER); - -// uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( -// USER_SENDER -// ); - -// // Approve spending -// IERC20(address(state.tokenA)).approve( -// address(ldaDiamond), -// state.amountIn -// ); - -// // Build route -// bytes memory route = _buildMultiHopRouteForTest(state); - -// // Execute swap -// coreRouteFacet.processRoute( -// address(state.tokenA), -// state.amountIn, -// address(state.tokenC), -// 0, // No minimum amount out for testing -// USER_SENDER, -// route -// ); - -// // Verify results -// _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); - -// vm.stopPrank(); -// } - -// // Helper function to build the multi-hop route for test -// function _buildMultiHopRouteForTest( -// MultiHopTestState memory state -// ) private view returns (bytes memory) { -// // 1. Get the specific data payload for each hop -// bytes memory firstHopData = _buildAlgebraSwapData( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: address(state.tokenA), -// recipient: address(ldaDiamond), // Hop 1 sends to the contract itself -// pool: state.pool1, -// supportsFeeOnTransfer: false -// }) -// ); - -// bytes memory secondHopData = _buildAlgebraSwapData( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessMyERC20, -// tokenIn: address(state.tokenB), -// recipient: USER_SENDER, // Hop 2 sends to the final user -// pool: state.pool2, -// supportsFeeOnTransfer: state.isFeeOnTransfer -// }) -// ); - -// // 2. Assemble the first full command with its length prefix -// bytes memory firstCommand = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// state.tokenA, -// uint8(1), -// FULL_SHARE, -// uint16(firstHopData.length), -// firstHopData -// ); - -// // 3. Assemble the second full command with its length prefix -// bytes memory secondCommand = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// state.tokenB, -// uint8(1), // num splits for the second hop -// FULL_SHARE, // full share for the second hop -// uint16(secondHopData.length), -// secondHopData -// ); - -// // 4. Concatenate the commands to create the final route -// return bytes.concat(firstCommand, secondCommand); -// } - -// // Helper function to verify multi-hop results -// function _verifyMultiHopResults( -// MultiHopTestState memory state, -// uint256 initialBalanceA, -// uint256 initialBalanceC -// ) private { -// uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( -// USER_SENDER -// ); -// uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( -// USER_SENDER -// ); - -// assertApproxEqAbs( -// initialBalanceA - finalBalanceA, -// state.amountIn, -// 1, // 1 wei tolerance -// "TokenA spent amount mismatch" -// ); -// assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); - -// emit log_named_uint( -// state.isFeeOnTransfer -// ? "Output amount with fee tokens" -// : "Output amount with regular tokens", -// finalBalanceC - initialBalanceC -// ); -// } - -// // Helper function to create an Algebra pool -// function _createAlgebraPool( -// address tokenA, -// address tokenB -// ) internal returns (address pool) { -// // Call the actual Algebra factory to create a pool -// pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( -// tokenA, -// tokenB -// ); -// return pool; -// } - -// // Helper function to add liquidity to a pool -// function _addLiquidityToPool( -// address pool, -// address token0, -// address token1 -// ) internal { -// // For fee-on-transfer tokens, we need to send more to account for the fee -// // We'll use a small amount and send extra to cover fees -// uint256 initialAmount0 = 1e17; // 0.1 token -// uint256 initialAmount1 = 1e17; // 0.1 token - -// // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) -// uint256 transferAmount0 = (initialAmount0 * 110) / 100; -// uint256 transferAmount1 = (initialAmount1 * 110) / 100; - -// // Initialize with 1:1 price ratio (Q64.96 format) -// uint160 initialPrice = uint160(1 << 96); -// IAlgebraPool(pool).initialize(initialPrice); - -// // Create AlgebraLiquidityAdderHelper with safe transfer logic -// AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( -// token0, -// token1 -// ); - -// // Transfer tokens with extra amounts to account for fees -// IERC20(token0).transfer( -// address(algebraLiquidityAdderHelper), -// transferAmount0 -// ); -// IERC20(token1).transfer( -// address(algebraLiquidityAdderHelper), -// transferAmount1 -// ); - -// // Get actual balances to use for liquidity, accounting for any fees -// uint256 actualBalance0 = IERC20(token0).balanceOf( -// address(algebraLiquidityAdderHelper) -// ); -// uint256 actualBalance1 = IERC20(token1).balanceOf( -// address(algebraLiquidityAdderHelper) -// ); - -// // Use the smaller of the two balances for liquidity amount -// uint128 liquidityAmount = uint128( -// actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 -// ); - -// // Add liquidity using the actual token amounts we have -// algebraLiquidityAdderHelper.addLiquidity( -// pool, -// -887220, -// 887220, -// liquidityAmount / 2 // Use half of available liquidity to ensure success -// ); -// } - -// struct MultiHopTestState { -// IERC20 tokenA; -// IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken -// IERC20 tokenC; -// address pool1; -// address pool2; -// uint256 amountIn; -// uint256 amountToTransfer; -// bool isFeeOnTransfer; -// } - -// struct AlgebraRouteParams { -// CommandType commandCode; // 1 for contract funds, 2 for user funds -// address tokenIn; // Input token address -// address recipient; // Address receiving the output tokens -// address pool; // Algebra pool address -// bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens -// } - -// // Helper function to build route for Apechain Algebra swap -// function _buildAlgebraSwapData( -// AlgebraRouteParams memory params -// ) private view returns (bytes memory) { -// address token0 = IAlgebraPool(params.pool).token0(); -// bool zeroForOne = (params.tokenIn == token0); -// SwapDirection direction = zeroForOne -// ? SwapDirection.Token0ToToken1 -// : SwapDirection.Token1ToToken0; - -// // This data blob is what the AlgebraFacet will receive and parse -// return -// abi.encodePacked( -// AlgebraFacet.swapAlgebra.selector, -// params.pool, -// uint8(direction), -// params.recipient, -// params.supportsFeeOnTransfer ? uint8(1) : uint8(0) -// ); -// } - -// // Helper function to test an Algebra swap -// function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { -// // Find or create a pool -// address pool = _getPool(params.tokenIn, params.tokenOut); -// vm.label(pool, "AlgebraPool"); - -// // Get token0 from pool for labeling -// address token0 = IAlgebraPool(pool).token0(); -// if (params.tokenIn == token0) { -// vm.label( -// params.tokenIn, -// string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") -// ); -// vm.label( -// params.tokenOut, -// string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") -// ); -// } else { -// vm.label( -// params.tokenIn, -// string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") -// ); -// vm.label( -// params.tokenOut, -// string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") -// ); -// } - -// // Record initial balances -// uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - -// // Get expected output from QuoterV2 -// uint256 expectedOutput = _getQuoteExactInput( -// params.tokenIn, -// params.tokenOut, -// params.amountIn -// ); - -// // 1. Pack the specific data for this swap -// bytes memory swapData = _buildAlgebraSwapData( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, // Placeholder, not used in this helper -// tokenIn: params.tokenIn, -// recipient: params.to, -// pool: pool, -// supportsFeeOnTransfer: params.supportsFeeOnTransfer -// }) -// ); - -// // 2. Approve tokens -// IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - -// // 3. Set up event expectations -// address fromAddress = params.from == address(coreRouteFacet) -// ? USER_SENDER -// : params.from; - -// vm.expectEmit(true, true, true, false); -// emit Route( -// fromAddress, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// expectedOutput, -// expectedOutput -// ); - -// // 4. Build the route inline and execute the swap to save stack space -// coreRouteFacet.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// (expectedOutput * 995) / 1000, // minOut calculated inline -// params.to, -// abi.encodePacked( -// uint8( -// params.from == address(coreRouteFacet) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20 -// ), -// params.tokenIn, -// uint8(1), -// FULL_SHARE, -// uint16(swapData.length), -// swapData -// ) -// ); - -// // 5. Verify final balances -// uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - -// assertApproxEqAbs( -// initialTokenIn - finalTokenIn, -// params.amountIn, -// 1, -// "TokenIn amount mismatch" -// ); -// assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); -// } - -// function _getPool( -// address tokenA, -// address tokenB -// ) private view returns (address pool) { -// pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( -// tokenA, -// tokenB -// ); -// if (pool == address(0)) revert PoolDoesNotExist(); -// return pool; -// } - -// function _getQuoteExactInput( -// address tokenIn, -// address tokenOut, -// uint256 amountIn -// ) private returns (uint256 amountOut) { -// (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) -// .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); -// return amountOut; -// } - -// function testRevert_AlgebraSwap_ZeroAddressPool() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on address(0) -// vm.mockCall( -// address(0), -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with address(0) as pool -// bytes memory swapData = _buildAlgebraSwapData( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: USER_SENDER, -// pool: address(0), // Zero address pool -// supportsFeeOnTransfer: true -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// APE_ETH_TOKEN, -// uint8(1), // number of pools/splits -// FULL_SHARE, // 100% share -// uint16(swapData.length), // <--- Add the length prefix -// swapData -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// coreRouteFacet.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// // function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { -// // // Transfer tokens from whale to user -// // vm.prank(APE_ETH_HOLDER_APECHAIN); -// // IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// // vm.startPrank(USER_SENDER); - -// // // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS -// // vm.mockCall( -// // IMPOSSIBLE_POOL_ADDRESS, -// // abi.encodeWithSelector(IAlgebraPool.token0.selector), -// // abi.encode(APE_ETH_TOKEN) -// // ); - -// // // Build route with IMPOSSIBLE_POOL_ADDRESS as pool -// // bytes memory swapData = _buildAlgebraSwapData( -// // AlgebraRouteParams({ -// // commandCode: CommandType.ProcessUserERC20, -// // tokenIn: APE_ETH_TOKEN, -// // recipient: USER_SENDER, -// // pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address -// // supportsFeeOnTransfer: true -// // }) -// // ); - -// // bytes memory route = abi.encodePacked( -// // uint8(CommandType.ProcessUserERC20), -// // APE_ETH_TOKEN, -// // uint8(1), // number of pools/splits -// // FULL_SHARE, // 100% share -// // uint16(swapData.length), // <--- Add the length prefix -// // swapData -// // ); - -// // // Approve tokens -// // IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - -// // // Expect revert with InvalidCallData -// // vm.expectRevert(InvalidCallData.selector); - -// // coreRouteFacet.processRoute( -// // APE_ETH_TOKEN, -// // 1 * 1e18, -// // address(WETH_TOKEN), -// // 0, -// // USER_SENDER, -// // route -// // ); - -// // vm.stopPrank(); -// // vm.clearMockedCalls(); -// // } - -// function testRevert_AlgebraSwap_ZeroAddressRecipient() public { -// // Transfer tokens from whale to user -// vm.prank(APE_ETH_HOLDER_APECHAIN); -// IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - -// vm.startPrank(USER_SENDER); - -// // Mock token0() call on the pool -// vm.mockCall( -// ALGEBRA_POOL_APECHAIN, -// abi.encodeWithSelector(IAlgebraPool.token0.selector), -// abi.encode(APE_ETH_TOKEN) -// ); - -// // Build route with address(0) as recipient -// bytes memory swapData = _buildAlgebraSwapData( -// AlgebraRouteParams({ -// commandCode: CommandType.ProcessUserERC20, -// tokenIn: APE_ETH_TOKEN, -// recipient: address(0), // Zero address recipient -// pool: ALGEBRA_POOL_APECHAIN, -// supportsFeeOnTransfer: true -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// APE_ETH_TOKEN, -// uint8(1), // number of pools/splits -// FULL_SHARE, // 100% share -// uint16(swapData.length), // <--- Add the length prefix -// swapData -// ); - -// // Approve tokens -// IERC20(APE_ETH_TOKEN).approve(address(ldaDiamond), 1 * 1e18); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); - -// coreRouteFacet.processRoute( -// APE_ETH_TOKEN, -// 1 * 1e18, -// address(WETH_TOKEN), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } -// } - -// /** -// * @title LiFiDexAggregatorIzumiV3UpgradeTest -// * @notice Tests specific to iZiSwap V3 selector -// */ -// contract LiFiDexAggregatorIzumiV3UpgradeTest is LiFiDexAggregatorUpgradeTest { -// IzumiV3Facet private izumiV3Facet; - -// // ==================== iZiSwap V3 specific variables ==================== -// // Base constants -// address internal constant USDC = -// 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; -// address internal constant WETH = -// 0x4200000000000000000000000000000000000006; -// address internal constant USDB_C = -// 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; - -// // iZiSwap pools -// address internal constant IZUMI_WETH_USDC_POOL = -// 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; -// address internal constant IZUMI_WETH_USDB_C_POOL = -// 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; - -// // Test parameters -// uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals -// uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals - -// // structs -// struct IzumiV3SwapTestParams { -// address from; -// address to; -// address tokenIn; -// uint256 amountIn; -// address tokenOut; -// SwapDirection direction; -// } - -// struct MultiHopTestParams { -// address tokenIn; -// address tokenMid; -// address tokenOut; -// address pool1; -// address pool2; -// uint256 amountIn; -// SwapDirection direction1; -// SwapDirection direction2; -// } - -// error IzumiV3SwapUnexpected(); -// error IzumiV3SwapCallbackUnknownSource(); -// error IzumiV3SwapCallbackNotPositiveAmount(); - -// // Setup function for Base tests -// function setupBase() internal { -// customRpcUrlForForking = "ETH_NODE_URI_BASE"; -// customBlockNumberForForking = 29831758; -// } - -// function setUp() public override { -// setupBase(); -// super.setUp(); - -// deal(address(USDC), address(USER_SENDER), 1_000 * 1e6); -// } - -// function _addDexFacet() internal override { -// izumiV3Facet = new IzumiV3Facet(); -// bytes4[] memory functionSelectors = new bytes4[](3); -// functionSelectors[0] = izumiV3Facet.swapIzumiV3.selector; -// functionSelectors[1] = izumiV3Facet.swapX2YCallback.selector; -// functionSelectors[2] = izumiV3Facet.swapY2XCallback.selector; -// addFacet( -// address(ldaDiamond), -// address(izumiV3Facet), -// functionSelectors -// ); - -// izumiV3Facet = IzumiV3Facet(payable(address(ldaDiamond))); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // Test USDC -> WETH -// deal(USDC, address(coreRouteFacet), AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); -// _testSwap( -// IzumiV3SwapTestParams({ -// from: address(coreRouteFacet), -// to: USER_SENDER, -// tokenIn: USDC, -// amountIn: AMOUNT_USDC, -// tokenOut: WETH, -// direction: SwapDirection.Token1ToToken0 -// }) -// ); -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// _testMultiHopSwap( -// MultiHopTestParams({ -// tokenIn: USDC, -// tokenMid: WETH, -// tokenOut: USDB_C, -// pool1: IZUMI_WETH_USDC_POOL, -// pool2: IZUMI_WETH_USDB_C_POOL, -// amountIn: AMOUNT_USDC, -// direction1: SwapDirection.Token1ToToken0, -// direction2: SwapDirection.Token0ToToken1 -// }) -// ); -// } - -// function test_CanSwap() public override { -// deal(address(USDC), USER_SENDER, AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); -// IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); - -// bytes memory swapData = _buildIzumiV3SwapData( -// IzumiV3SwapParams({ -// pool: IZUMI_WETH_USDC_POOL, -// direction: SwapDirection.Token1ToToken0, -// recipient: USER_RECEIVER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// USDC, -// uint8(1), // number of pools/splits -// FULL_SHARE, // 100% share -// uint16(swapData.length), // length prefix -// swapData -// ); - -// vm.expectEmit(true, true, true, false); -// emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); - -// coreRouteFacet.processRoute( -// USDC, -// AMOUNT_USDC, -// WETH, -// 0, -// USER_RECEIVER, -// route -// ); - -// vm.stopPrank(); -// } - -// function testRevert_IzumiV3SwapUnexpected() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// vm.startPrank(USER_SENDER); - -// // create invalid pool address -// address invalidPool = address(0x999); - -// bytes memory swapData = _buildIzumiV3SwapData( -// IzumiV3SwapParams({ -// pool: invalidPool, -// direction: SwapDirection.Token1ToToken0, -// recipient: USER_SENDER -// }) -// ); - -// // create a route with an invalid pool -// bytes memory invalidRoute = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// USDC, -// uint8(1), // number of pools (1) -// FULL_SHARE, // 100% share -// uint16(swapData.length), // length prefix -// swapData -// ); - -// IERC20(USDC).approve(address(ldaDiamond), AMOUNT_USDC); - -// // mock the iZiSwap pool to return without updating lastCalledPool -// vm.mockCall( -// invalidPool, -// abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), -// abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool -// ); - -// vm.expectRevert(IzumiV3SwapUnexpected.selector); - -// coreRouteFacet.processRoute( -// USDC, -// AMOUNT_USDC, -// WETH, -// 0, -// USER_SENDER, -// invalidRoute -// ); - -// vm.stopPrank(); -// vm.clearMockedCalls(); -// } - -// function testRevert_UnexpectedCallbackSender() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// // Set up the expected callback sender through the diamond -// vm.store( -// address(ldaDiamond), -// keccak256("com.lifi.lda.callbackmanager"), -// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) -// ); - -// // Try to call callback from a different address than expected -// address unexpectedCaller = address(0xdead); -// vm.prank(unexpectedCaller); -// vm.expectRevert( -// abi.encodeWithSelector( -// LibCallbackManager.UnexpectedCallbackSender.selector, -// unexpectedCaller, -// IZUMI_WETH_USDC_POOL -// ) -// ); -// izumiV3Facet.swapY2XCallback(1, 1, abi.encode(USDC)); -// } - -// function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { -// deal(USDC, USER_SENDER, AMOUNT_USDC); - -// // Set the expected callback sender through the diamond storage -// vm.store( -// address(ldaDiamond), -// keccak256("com.lifi.lda.callbackmanager"), -// bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) -// ); - -// // try to call the callback with zero amount -// vm.prank(IZUMI_WETH_USDC_POOL); -// vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); -// izumiV3Facet.swapY2XCallback( -// 0, -// 0, // zero amount should trigger the error -// abi.encode(USDC) -// ); -// } - -// function testRevert_FailsIfAmountInIsTooLarge() public { -// deal(address(WETH), USER_SENDER, type(uint256).max); - -// vm.startPrank(USER_SENDER); -// IERC20(WETH).approve(address(ldaDiamond), type(uint256).max); - -// bytes memory swapData = _buildIzumiV3SwapData( -// IzumiV3SwapParams({ -// pool: IZUMI_WETH_USDC_POOL, -// direction: SwapDirection.Token0ToToken1, -// recipient: USER_RECEIVER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// WETH, -// uint8(1), // number of pools (1) -// FULL_SHARE, // 100% share -// uint16(swapData.length), // length prefix -// swapData -// ); - -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// WETH, -// type(uint216).max, -// USDC, -// 0, -// USER_RECEIVER, -// route -// ); - -// vm.stopPrank(); -// } - -// function _testSwap(IzumiV3SwapTestParams memory params) internal { -// // Fund the sender with tokens if not the contract itself -// if (params.from != address(coreRouteFacet)) { -// deal(params.tokenIn, params.from, params.amountIn); -// } - -// // Capture initial token balances -// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( -// params.from -// ); -// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( -// params.to -// ); - -// // Build the route based on the command type -// CommandType commandCode = params.from == address(coreRouteFacet) -// ? CommandType.ProcessMyERC20 -// : CommandType.ProcessUserERC20; - -// bytes memory swapData = _buildIzumiV3SwapData( -// IzumiV3SwapParams({ -// pool: IZUMI_WETH_USDC_POOL, -// direction: params.direction == SwapDirection.Token0ToToken1 -// ? SwapDirection.Token0ToToken1 -// : SwapDirection.Token1ToToken0, -// recipient: params.to -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(commandCode), -// params.tokenIn, -// uint8(1), // number of pools (1) -// FULL_SHARE, // 100% share -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Approve tokens if necessary -// if (params.from == USER_SENDER) { -// vm.startPrank(USER_SENDER); -// IERC20(params.tokenIn).approve( -// address(ldaDiamond), -// params.amountIn -// ); -// } - -// // Expect the Route event emission -// address from = params.from == address(coreRouteFacet) -// ? USER_SENDER -// : params.from; - -// vm.expectEmit(true, true, true, false); -// emit Route( -// from, -// params.to, -// params.tokenIn, -// params.tokenOut, -// params.amountIn, -// 0, // No minimum amount enforced in test -// 0 // Actual amount will be checked after the swap -// ); - -// // Execute the swap -// uint256 amountOut = coreRouteFacet.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// 0, // No minimum amount for testing -// params.to, -// route -// ); - -// if (params.from == USER_SENDER) { -// vm.stopPrank(); -// } - -// // Verify balances have changed correctly -// uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); -// uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); - -// assertApproxEqAbs( -// initialBalanceIn - finalBalanceIn, -// params.amountIn, -// 1, // 1 wei tolerance because of undrain protection for dex aggregator -// "TokenIn amount mismatch" -// ); -// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); -// assertEq( -// amountOut, -// finalBalanceOut - initialBalanceOut, -// "AmountOut mismatch" -// ); - -// emit log_named_uint("Amount In", params.amountIn); -// emit log_named_uint("Amount Out", amountOut); -// } - -// function _testMultiHopSwap(MultiHopTestParams memory params) internal { -// // Fund the sender with tokens -// deal(params.tokenIn, USER_SENDER, params.amountIn); - -// // Capture initial token balances -// uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( -// USER_SENDER -// ); -// uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( -// USER_SENDER -// ); - -// // Build first swap data -// bytes memory firstSwapData = _buildIzumiV3SwapData( -// IzumiV3SwapParams({ -// pool: params.pool1, -// direction: params.direction1, -// recipient: address(coreRouteFacet) -// }) -// ); - -// bytes memory firstHop = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// params.tokenIn, -// uint8(1), // number of pools/splits -// FULL_SHARE, // 100% share -// uint16(firstSwapData.length), // length prefix -// firstSwapData -// ); - -// // Build second swap data -// bytes memory secondSwapData = _buildIzumiV3SwapData( -// IzumiV3SwapParams({ -// pool: params.pool2, -// direction: params.direction2, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory secondHop = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// params.tokenMid, -// uint8(1), // number of pools/splits -// FULL_SHARE, // 100% share -// uint16(secondSwapData.length), // length prefix -// secondSwapData -// ); - -// // Combine into route -// bytes memory route = bytes.concat(firstHop, secondHop); - -// // Approve tokens -// vm.startPrank(USER_SENDER); -// IERC20(params.tokenIn).approve(address(ldaDiamond), params.amountIn); - -// // Execute the swap -// uint256 amountOut = coreRouteFacet.processRoute( -// params.tokenIn, -// params.amountIn, -// params.tokenOut, -// 0, // No minimum amount for testing -// USER_SENDER, -// route -// ); -// vm.stopPrank(); - -// // Verify balances have changed correctly -// uint256 finalBalanceIn; -// uint256 finalBalanceOut; - -// finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); -// finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - -// assertEq( -// initialBalanceIn - finalBalanceIn, -// params.amountIn, -// "TokenIn amount mismatch" -// ); -// assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); -// assertEq( -// amountOut, -// finalBalanceOut - initialBalanceOut, -// "AmountOut mismatch" -// ); -// } - -// function _buildIzumiV3Route( -// CommandType commandCode, -// address tokenIn, -// uint8 direction, -// address pool, -// address recipient -// ) internal pure returns (bytes memory) { -// return -// abi.encodePacked( -// uint8(commandCode), -// tokenIn, -// uint8(1), // number of pools (1) -// FULL_SHARE, // 100% share -// IzumiV3Facet.swapIzumiV3.selector, -// pool, -// uint8(direction), -// recipient -// ); -// } - -// function _buildIzumiV3MultiHopRoute( -// MultiHopTestParams memory params -// ) internal view returns (bytes memory) { -// // First hop: USER_ERC20 -> LDA -// bytes memory firstHop = _buildIzumiV3Route( -// CommandType.ProcessUserERC20, -// params.tokenIn, -// uint8(params.direction1), -// params.pool1, -// address(coreRouteFacet) -// ); - -// // Second hop: MY_ERC20 (LDA) -> pool2 -// bytes memory secondHop = _buildIzumiV3Route( -// CommandType.ProcessMyERC20, -// params.tokenMid, -// uint8(params.direction2), -// params.pool2, -// USER_SENDER // final recipient -// ); - -// // Combine the two hops -// return bytes.concat(firstHop, secondHop); -// } - -// struct IzumiV3SwapParams { -// address pool; -// SwapDirection direction; -// address recipient; -// } - -// function _buildIzumiV3SwapData( -// IzumiV3SwapParams memory params -// ) internal view returns (bytes memory) { -// return -// abi.encodePacked( -// izumiV3Facet.swapIzumiV3.selector, -// params.pool, -// uint8(params.direction), -// params.recipient -// ); -// } -// } - -// // ----------------------------------------------------------------------------- -// // HyperswapV3 on HyperEVM -// // ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorHyperswapV3UpgradeTest is -// LiFiDexAggregatorUpgradeTest -// { -// using SafeERC20 for IERC20; - -// UniV3StyleFacet internal uniV3StyleFacet; - -// /// @dev HyperswapV3 router on HyperEVM chain -// IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = -// IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); -// /// @dev HyperswapV3 quoter on HyperEVM chain -// IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = -// IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); - -// /// @dev a liquid USDT on HyperEVM -// IERC20 internal constant USDT0 = -// IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); -// /// @dev WHYPE on HyperEVM -// IERC20 internal constant WHYPE = -// IERC20(0x5555555555555555555555555555555555555555); - -// struct HyperswapV3Params { -// CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 -// address tokenIn; // Input token address -// address recipient; // Address receiving the output tokens -// address pool; // HyperswapV3 pool address -// bool zeroForOne; // Direction of the swap -// } - -// function setUp() public override { -// setupHyperEVM(); -// super.setUp(); - -// deal(address(USDT0), address(USER_SENDER), 1_000 * 1e6); -// } - -// function _addDexFacet() internal override { -// uniV3StyleFacet = new UniV3StyleFacet(); -// bytes4[] memory functionSelectors = new bytes4[](2); -// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; -// functionSelectors[1] = uniV3StyleFacet -// .hyperswapV3SwapCallback -// .selector; -// addFacet( -// address(ldaDiamond), -// address(uniV3StyleFacet), -// functionSelectors -// ); - -// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - -// deal(address(USDT0), USER_SENDER, amountIn); - -// // user approves -// vm.prank(USER_SENDER); -// USDT0.approve(address(ldaDiamond), amountIn); - -// // fetch the real pool and quote -// address pool = HYPERSWAP_FACTORY.getPool( -// address(USDT0), -// address(WHYPE), -// 3000 -// ); - -// // Create the params struct for quoting -// IHyperswapV3QuoterV2.QuoteExactInputSingleParams -// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ -// tokenIn: address(USDT0), -// tokenOut: address(WHYPE), -// amountIn: amountIn, -// fee: 3000, -// sqrtPriceLimitX96: 0 -// }); - -// // Get the quote using the struct -// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( -// params -// ); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: pool, -// direction: SwapDirection.Token1ToToken0, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(USDT0), -// uint8(1), // 1 pool -// FULL_SHARE, // FULL_SHARE -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // expect the Route event -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// address(USDT0), -// address(WHYPE), -// amountIn, -// quoted, -// quoted -// ); - -// // execute -// vm.prank(USER_SENDER); -// coreRouteFacet.processRoute( -// address(USDT0), -// amountIn, -// address(WHYPE), -// quoted, -// USER_SENDER, -// route -// ); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - -// // Fund dex aggregator contract -// deal(address(USDT0), address(ldaDiamond), amountIn); - -// // fetch the real pool and quote -// address pool = HYPERSWAP_FACTORY.getPool( -// address(USDT0), -// address(WHYPE), -// 3000 -// ); - -// // Create the params struct for quoting -// IHyperswapV3QuoterV2.QuoteExactInputSingleParams -// memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ -// tokenIn: address(USDT0), -// tokenOut: address(WHYPE), -// amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection -// fee: 3000, -// sqrtPriceLimitX96: 0 -// }); - -// // Get the quote using the struct -// (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( -// params -// ); - -// // Build route using our helper function -// // bytes memory route = _buildHyperswapV3Route( -// // HyperswapV3Params({ -// // commandCode: CommandType.ProcessMyERC20, -// // tokenIn: address(USDT0), -// // recipient: USER_SENDER, -// // pool: pool, -// // zeroForOne: true // USDT0 < WHYPE -// // }) -// // ); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: pool, -// direction: SwapDirection.Token1ToToken0, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(USDT0), -// uint8(1), // number of pools (1) -// FULL_SHARE, // 100% share -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // expect the Route event -// vm.expectEmit(true, true, true, true); -// emit Route( -// USER_SENDER, -// USER_SENDER, -// address(USDT0), -// address(WHYPE), -// amountIn - 1, // Account for slot undrain protection -// quoted, -// quoted -// ); - -// // execute -// vm.prank(USER_SENDER); -// coreRouteFacet.processRoute( -// address(USDT0), -// amountIn - 1, // Account for slot undrain protection -// address(WHYPE), -// quoted, -// USER_SENDER, -// route -// ); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. -// // HyperswapV3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. HyperswapV3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// // function _buildHyperswapV3Route( -// // HyperswapV3Params memory params -// // ) internal pure returns (bytes memory route) { -// // route = abi.encodePacked( -// // uint8(params.commandCode), -// // params.tokenIn, -// // uint8(1), // 1 pool -// // FULL_SHARE, // 65535 - 100% share -// // uint8(PoolType.UniV3), // POOL_TYPE_UNIV3 = 1 -// // params.pool, -// // uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false -// // params.recipient -// // ); - -// // return route; -// // } - -// struct UniV3SwapParams { -// address pool; -// SwapDirection direction; -// address recipient; -// } - -// function _buildUniV3SwapData( -// UniV3SwapParams memory params -// ) internal returns (bytes memory) { -// return -// abi.encodePacked( -// uniV3StyleFacet.swapUniV3.selector, -// params.pool, -// uint8(params.direction), -// params.recipient -// ); -// } -// } - -// // ----------------------------------------------------------------------------- -// // LaminarV3 on HyperEVM -// // ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorLaminarV3UpgradeTest is -// LiFiDexAggregatorUpgradeTest -// { -// UniV3StyleFacet internal uniV3StyleFacet; -// using SafeERC20 for IERC20; - -// IERC20 internal constant WHYPE = -// IERC20(0x5555555555555555555555555555555555555555); -// IERC20 internal constant LHYPE = -// IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); - -// address internal constant WHYPE_LHYPE_POOL = -// 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; - -// function setUp() public override { -// setupHyperEVM(); -// super.setUp(); -// } - -// function _addDexFacet() internal override { -// uniV3StyleFacet = new UniV3StyleFacet(); -// bytes4[] memory functionSelectors = new bytes4[](2); -// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; -// functionSelectors[1] = uniV3StyleFacet.laminarV3SwapCallback.selector; -// addFacet( -// address(ldaDiamond), -// address(uniV3StyleFacet), -// functionSelectors -// ); - -// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // Fund the user with WHYPE -// deal(address(WHYPE), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WHYPE.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: WHYPE_LHYPE_POOL, -// direction: SwapDirection.Token0ToToken1, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(WHYPE), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Record balances -// uint256 inBefore = WHYPE.balanceOf(USER_SENDER); -// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - -// // Execute swap (minOut = 0 for test) -// coreRouteFacet.processRoute( -// address(WHYPE), -// amountIn, -// address(LHYPE), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify -// uint256 inAfter = WHYPE.balanceOf(USER_SENDER); -// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the aggregator directly -// deal(address(WHYPE), address(uniV3StyleFacet), amountIn); - -// vm.startPrank(USER_SENDER); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: WHYPE_LHYPE_POOL, -// direction: SwapDirection.Token0ToToken1, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(WHYPE), -// uint8(1), -// FULL_SHARE, -// uint16(swapData.length), // length prefix -// swapData -// ); - -// uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - -// // Withdraw 1 wei to avoid slot-undrain protection -// coreRouteFacet.processRoute( -// address(WHYPE), -// amountIn - 1, -// address(LHYPE), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 outAfter = LHYPE.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. -// // Laminar V3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. Laminar V3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// struct UniV3SwapParams { -// address pool; -// SwapDirection direction; -// address recipient; -// } - -// function _buildUniV3SwapData( -// UniV3SwapParams memory params -// ) internal returns (bytes memory) { -// return -// abi.encodePacked( -// uniV3StyleFacet.swapUniV3.selector, -// params.pool, -// uint8(params.direction), -// params.recipient -// ); -// } -// } - -// contract LiFiDexAggregatorXSwapV3UpgradeTest is LiFiDexAggregatorUpgradeTest { -// using SafeERC20 for IERC20; - -// UniV3StyleFacet internal uniV3StyleFacet; - -// address internal constant USDC_E_WXDC_POOL = -// 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; - -// /// @dev our two tokens: USDC.e and wrapped XDC -// IERC20 internal constant USDC_E = -// IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); -// IERC20 internal constant WXDC = -// IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); - -// function setUp() public override { -// setupXDC(); -// super.setUp(); -// } - -// function _addDexFacet() internal override { -// uniV3StyleFacet = new UniV3StyleFacet(); -// bytes4[] memory functionSelectors = new bytes4[](2); -// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; -// functionSelectors[1] = uniV3StyleFacet.xswapCallback.selector; -// addFacet( -// address(ldaDiamond), -// address(uniV3StyleFacet), -// functionSelectors -// ); - -// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e6; -// deal(address(USDC_E), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// USDC_E.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: USDC_E_WXDC_POOL, -// direction: SwapDirection.Token0ToToken1, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(USDC_E), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Record balances before swap -// uint256 inBefore = USDC_E.balanceOf(USER_SENDER); -// uint256 outBefore = WXDC.balanceOf(USER_SENDER); - -// // Execute swap (minOut = 0 for test) -// coreRouteFacet.processRoute( -// address(USDC_E), -// amountIn, -// address(WXDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify balances after swap -// uint256 inAfter = USDC_E.balanceOf(USER_SENDER); -// uint256 outAfter = WXDC.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - -// vm.stopPrank(); -// } - -// /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 5_000 * 1e6; - -// // fund the aggregator -// deal(address(USDC_E), address(uniV3StyleFacet), amountIn); - -// vm.startPrank(USER_SENDER); - -// // Account for slot-undrain protection -// uint256 swapAmount = amountIn - 1; - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: USDC_E_WXDC_POOL, -// direction: SwapDirection.Token0ToToken1, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(USDC_E), -// uint8(1), -// FULL_SHARE, -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Record balances before swap -// uint256 outBefore = WXDC.balanceOf(USER_SENDER); - -// coreRouteFacet.processRoute( -// address(USDC_E), -// swapAmount, -// address(WXDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify balances after swap -// uint256 outAfter = WXDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. -// // XSwap V3 does not support a "one-pool" second hop today, because -// // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. XSwap V3's swap() immediately reverts on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// struct UniV3SwapParams { -// address pool; -// SwapDirection direction; -// address recipient; -// } - -// function _buildUniV3SwapData( -// UniV3SwapParams memory params -// ) internal returns (bytes memory) { -// return -// abi.encodePacked( -// uniV3StyleFacet.swapUniV3.selector, -// params.pool, -// uint8(params.direction), -// params.recipient -// ); -// } -// } - -// // ----------------------------------------------------------------------------- -// // RabbitSwap on Viction -// // ----------------------------------------------------------------------------- -// contract LiFiDexAggregatorRabbitSwapUpgradeTest is -// LiFiDexAggregatorUpgradeTest -// { -// using SafeERC20 for IERC20; - -// UniV3StyleFacet internal uniV3StyleFacet; - -// // Constants for RabbitSwap on Viction -// IERC20 internal constant SOROS = -// IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); -// IERC20 internal constant C98 = -// IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); -// address internal constant SOROS_C98_POOL = -// 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; - -// function setUp() public override { -// setupViction(); -// super.setUp(); -// } - -// function _addDexFacet() internal override { -// uniV3StyleFacet = new UniV3StyleFacet(); -// bytes4[] memory functionSelectors = new bytes4[](2); -// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; -// functionSelectors[1] = uniV3StyleFacet -// .rabbitSwapV3SwapCallback -// .selector; -// addFacet( -// address(ldaDiamond), -// address(uniV3StyleFacet), -// functionSelectors -// ); - -// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); -// } - -// function test_CanSwap() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the user with SOROS -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: SOROS_C98_POOL, -// direction: SwapDirection.Token1ToToken0, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // record balances before swap -// uint256 inBefore = SOROS.balanceOf(USER_SENDER); -// uint256 outBefore = C98.balanceOf(USER_SENDER); - -// // execute swap (minOut = 0 for test) -// coreRouteFacet.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// // verify balances after swap -// uint256 inAfter = SOROS.balanceOf(USER_SENDER); -// uint256 outAfter = C98.balanceOf(USER_SENDER); -// assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive C98"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// uint256 amountIn = 1_000 * 1e18; - -// // fund the aggregator directly -// deal(address(SOROS), address(uniV3StyleFacet), amountIn); - -// vm.startPrank(USER_SENDER); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: SOROS_C98_POOL, -// direction: SwapDirection.Token1ToToken0, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint16(swapData.length), // length prefix -// swapData -// ); - -// uint256 outBefore = C98.balanceOf(USER_SENDER); - -// // withdraw 1 wei less to avoid slot-undrain protection -// coreRouteFacet.processRoute( -// address(SOROS), -// amountIn - 1, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 outAfter = C98.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive C98"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. -// // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, -// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. UniV3-style pools immediately revert on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// function testRevert_RabbitSwapInvalidPool() public { -// uint256 amountIn = 1_000 * 1e18; -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: address(0), -// direction: SwapDirection.Token1ToToken0, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint16(swapData.length), // length prefix -// swapData -// ); - -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } - -// function testRevert_RabbitSwapInvalidRecipient() public { -// uint256 amountIn = 1_000 * 1e18; -// deal(address(SOROS), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// SOROS.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: SOROS_C98_POOL, -// direction: SwapDirection.Token1ToToken0, -// recipient: address(0) -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), -// address(SOROS), -// uint8(1), -// FULL_SHARE, -// uint16(swapData.length), // length prefix -// swapData -// ); - -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(SOROS), -// amountIn, -// address(C98), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } - -// struct UniV3SwapParams { -// address pool; -// SwapDirection direction; -// address recipient; -// } - -// function _buildUniV3SwapData( -// UniV3SwapParams memory params -// ) internal returns (bytes memory) { -// return -// abi.encodePacked( -// uniV3StyleFacet.swapUniV3.selector, -// params.pool, -// uint8(params.direction), -// params.recipient -// ); -// } -// } - -// // ---------------------------------------------- -// // EnosysDexV3 on Flare -// // ---------------------------------------------- -// contract LiFiDexAggregatorEnosysDexV3UpgradeTest is -// LiFiDexAggregatorUpgradeTest -// { -// using SafeERC20 for IERC20; - -// UniV3StyleFacet internal uniV3StyleFacet; - -// /// @dev HLN token on Flare -// IERC20 internal constant HLN = -// IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); - -// /// @dev USDT0 token on Flare -// IERC20 internal constant USDT0 = -// IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); - -// /// @dev The single EnosysDexV3 pool for HLN–USDT0 -// address internal constant ENOSYS_V3_POOL = -// 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; - -// function setUp() public override { -// setupFlare(); -// super.setUp(); -// } - -// function _addDexFacet() internal override { -// uniV3StyleFacet = new UniV3StyleFacet(); -// bytes4[] memory functionSelectors = new bytes4[](2); -// functionSelectors[0] = uniV3StyleFacet.swapUniV3.selector; -// functionSelectors[1] = uniV3StyleFacet -// .enosysdexV3SwapCallback -// .selector; -// addFacet( -// address(ldaDiamond), -// address(uniV3StyleFacet), -// functionSelectors -// ); - -// uniV3StyleFacet = UniV3StyleFacet(payable(address(ldaDiamond))); -// } - -// /// @notice Single‐pool swap: USER sends HLN → receives USDT0 -// function test_CanSwap() public override { -// // Mint 1 000 HLN to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(HLN), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// HLN.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: ENOSYS_V3_POOL, -// direction: SwapDirection.Token0ToToken1, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(HLN), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Record balances before swap -// uint256 inBefore = HLN.balanceOf(USER_SENDER); -// uint256 outBefore = USDT0.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// coreRouteFacet.processRoute( -// address(HLN), -// amountIn, -// address(USDT0), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that HLN was spent and some USDT0 was received -// uint256 inAfter = HLN.balanceOf(USER_SENDER); -// uint256 outAfter = USDT0.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - -// vm.stopPrank(); -// } - -// /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 -// function test_CanSwap_FromDexAggregator() public override { -// // Fund the aggregator with 1 000 HLN -// uint256 amountIn = 1_000 * 1e18; -// deal(address(HLN), address(coreRouteFacet), amountIn); - -// vm.startPrank(USER_SENDER); - -// bytes memory swapData = _buildUniV3SwapData( -// UniV3SwapParams({ -// pool: ENOSYS_V3_POOL, -// direction: SwapDirection.Token0ToToken1, -// recipient: USER_SENDER -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(HLN), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDT0.balanceOf(USER_SENDER); - -// coreRouteFacet.processRoute( -// address(HLN), -// swapAmount, -// address(USDT0), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDT0 was received -// uint256 outAfter = USDT0.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. -// // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, -// // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into -// // the pool.swap call. UniV3-style pools immediately revert on -// // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools -// // in a single processRoute invocation. -// } - -// struct UniV3SwapParams { -// address pool; -// SwapDirection direction; -// address recipient; -// } - -// function _buildUniV3SwapData( -// UniV3SwapParams memory params -// ) internal view returns (bytes memory) { -// return -// abi.encodePacked( -// uniV3StyleFacet.swapUniV3.selector, -// params.pool, -// uint8(params.direction), -// params.recipient -// ); -// } -// } - -// // ---------------------------------------------- -// // SyncSwapV2 on Linea -// // ---------------------------------------------- -// contract LiFiDexAggregatorSyncSwapV2UpgradeTest is -// LiFiDexAggregatorUpgradeTest -// { -// using SafeERC20 for IERC20; - -// SyncSwapV2Facet internal syncSwapV2Facet; - -// IERC20 internal constant USDC = -// IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); -// IERC20 internal constant WETH = -// IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); -// address internal constant USDC_WETH_POOL_V1 = -// address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); -// address internal constant SYNC_SWAP_VAULT = -// address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); - -// address internal constant USDC_WETH_POOL_V2 = -// address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); - -// IERC20 internal constant USDT = -// IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); -// address internal constant USDC_USDT_POOL_V1 = -// address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); - -// function setUp() public override { -// setupLinea(); -// super.setUp(); -// } - -// function _addDexFacet() internal override { -// syncSwapV2Facet = new SyncSwapV2Facet(); -// bytes4[] memory functionSelectors = new bytes4[](1); -// functionSelectors[0] = syncSwapV2Facet.swapSyncSwapV2.selector; -// addFacet( -// address(ldaDiamond), -// address(syncSwapV2Facet), -// functionSelectors -// ); - -// syncSwapV2Facet = SyncSwapV2Facet(payable(address(ldaDiamond))); -// } - -// /// @notice Single‐pool swap: USER sends WETH → receives USDC -// function test_CanSwap() public override { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V1, -// to: address(USER_SENDER), -// withdrawMode: 2, -// isV1Pool: 1, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Record balances before swap -// uint256 inBefore = WETH.balanceOf(USER_SENDER); -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// coreRouteFacet.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that WETH was spent and some USDC_C was received -// uint256 inAfter = WETH.balanceOf(USER_SENDER); -// uint256 outAfter = USDC.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_PoolV2() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V2, -// to: address(USER_SENDER), -// withdrawMode: 2, -// isV1Pool: 0, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Record balances before swap -// uint256 inBefore = WETH.balanceOf(USER_SENDER); -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// // Execute the swap (minOut = 0 for test) -// coreRouteFacet.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that WETH was spent and some USDC_C was received -// uint256 inAfter = WETH.balanceOf(USER_SENDER); -// uint256 outAfter = USDC.balanceOf(USER_SENDER); - -// assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator() public override { -// // Fund the aggregator with 1 000 WETH -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), address(ldaDiamond), amountIn); - -// vm.startPrank(USER_SENDER); - -// bytes memory swapData = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V1, -// to: address(USER_SENDER), -// withdrawMode: 2, -// isV1Pool: 1, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// coreRouteFacet.processRoute( -// address(WETH), -// swapAmount, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDC was received -// uint256 outAfter = USDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_FromDexAggregator_PoolV2() public { -// // Fund the aggregator with 1 000 WETH -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), address(ldaDiamond), amountIn); - -// vm.startPrank(USER_SENDER); - -// bytes memory swapData = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V2, -// to: address(USER_SENDER), -// withdrawMode: 2, -// isV1Pool: 0, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessMyERC20), // aggregator's funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Subtract 1 to protect against slot‐undrain -// uint256 swapAmount = amountIn - 1; -// uint256 outBefore = USDC.balanceOf(USER_SENDER); - -// coreRouteFacet.processRoute( -// address(WETH), -// swapAmount, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// // Verify that some USDC was received -// uint256 outAfter = USDC.balanceOf(USER_SENDER); -// assertGt(outAfter - outBefore, 0, "Should receive USDC"); - -// vm.stopPrank(); -// } - -// function test_CanSwap_MultiHop() public override { -// uint256 amountIn = 1_000e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(ldaDiamond), amountIn); - -// uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); -// uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); - -// // -// // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) -// // -// bytes memory swapData = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V1, -// to: SYNC_SWAP_VAULT, -// withdrawMode: 2, -// isV1Pool: 1, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory routeHop1 = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // -// // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 -// // -// bytes memory swapDataHop2 = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_USDT_POOL_V1, -// to: address(USER_SENDER), -// withdrawMode: 2, -// isV1Pool: 1, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory routeHop2 = abi.encodePacked( -// uint8(CommandType.ProcessOnePool), -// address(USDC), -// uint16(swapDataHop2.length), // length prefix -// swapDataHop2 -// ); - -// bytes memory route = bytes.concat(routeHop1, routeHop2); - -// uint256 amountOut = coreRouteFacet.processRoute( -// address(WETH), -// amountIn, -// address(USDT), -// 0, -// USER_SENDER, -// route -// ); - -// uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); -// uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); - -// assertEq( -// initialBalanceIn - afterBalanceIn, -// amountIn, -// "WETH spent mismatch" -// ); -// assertEq( -// amountOut, -// afterBalanceOut - initialBalanceOut, -// "USDT amountOut mismatch" -// ); -// vm.stopPrank(); -// } - -// function testRevert_V1PoolMissingVaultAddress() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V1, -// to: address(USER_SENDER), -// withdrawMode: 2, -// isV1Pool: 1, -// vault: address(0) -// }) -// ); - -// bytes memory route = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// route -// ); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidPoolOrRecipient() public { -// // Transfer 1 000 WETH from whale to USER_SENDER -// uint256 amountIn = 1_000 * 1e18; -// deal(address(WETH), USER_SENDER, amountIn); - -// vm.startPrank(USER_SENDER); -// WETH.approve(address(ldaDiamond), amountIn); - -// bytes memory swapData = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: address(0), -// to: address(USER_SENDER), -// withdrawMode: 2, -// isV1Pool: 1, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory routeWithInvalidPool = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapData.length), // length prefix -// swapData -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidPool -// ); - -// bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V1, -// to: address(0), -// withdrawMode: 2, -// isV1Pool: 1, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory routeWithInvalidRecipient = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapDataWithInvalidRecipient.length), // length prefix -// swapDataWithInvalidRecipient -// ); - -// // Expect revert with InvalidCallData -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(WETH), -// amountIn, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidRecipient -// ); - -// vm.stopPrank(); -// } - -// function testRevert_InvalidWithdrawMode() public { -// vm.startPrank(USER_SENDER); - -// bytes -// memory swapDataWithInvalidWithdrawMode = _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams({ -// pool: USDC_WETH_POOL_V1, -// to: address(USER_SENDER), -// withdrawMode: 3, -// isV1Pool: 1, -// vault: SYNC_SWAP_VAULT -// }) -// ); - -// bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( -// uint8(CommandType.ProcessUserERC20), // user funds -// address(WETH), // tokenIn -// uint8(1), // one pool -// FULL_SHARE, // 100% -// uint16(swapDataWithInvalidWithdrawMode.length), // length prefix -// swapDataWithInvalidWithdrawMode -// ); - -// // Expect revert with InvalidCallData because withdrawMode is invalid -// vm.expectRevert(InvalidCallData.selector); -// coreRouteFacet.processRoute( -// address(WETH), -// 1, -// address(USDC), -// 0, -// USER_SENDER, -// routeWithInvalidWithdrawMode -// ); - -// vm.stopPrank(); -// } - -// struct SyncSwapV2SwapParams { -// address pool; -// address to; -// uint8 withdrawMode; -// uint8 isV1Pool; -// address vault; -// } - -// function _buildSyncSwapV2SwapData( -// SyncSwapV2SwapParams memory params -// ) internal view returns (bytes memory) { -// return -// abi.encodePacked( -// syncSwapV2Facet.swapSyncSwapV2.selector, -// params.pool, -// params.to, -// params.withdrawMode, -// params.isV1Pool, -// params.vault -// ); -// } -// } diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 1f56b1958..8fcd780f4 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -5,6 +5,8 @@ import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; @@ -12,16 +14,20 @@ contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { LiFiDiamond internal ldaDiamond; function setUp() public virtual { - ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER); + ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER, USER_PAUSER); } function createLdaDiamond( - address _diamondOwner + address _diamondOwner, + address _pauserWallet ) internal returns (LiFiDiamond) { vm.startPrank(_diamondOwner); DiamondCutFacet diamondCut = new DiamondCutFacet(); DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); OwnershipFacet ownership = new OwnershipFacet(); + EmergencyPauseFacet emergencyPause = new EmergencyPauseFacet( + _pauserWallet + ); LiFiDiamond diamond = new LiFiDiamond( _diamondOwner, address(diamondCut) @@ -33,6 +39,21 @@ contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { // Add Ownership _addOwnershipSelectors(address(ownership)); + // Add PeripheryRegistry TODO?!?!? + + // Add EmergencyPause + bytes4[] memory functionSelectors = new bytes4[](3); + functionSelectors[0] = emergencyPause.removeFacet.selector; + functionSelectors[1] = emergencyPause.pauseDiamond.selector; + functionSelectors[2] = emergencyPause.unpauseDiamond.selector; + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(emergencyPause), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); delete cut; vm.stopPrank(); From 6eb87c9f9f1ae24c613a32532b2c58d3905d25f7 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 20 Aug 2025 23:25:04 +0200 Subject: [PATCH 047/220] Refactor addFacet calls in test contracts to remove redundant address(diamond) --- templates/facetTest.template.hbs | 2 +- test/solidity/Facets/AccessManagerFacet.t.sol | 4 ++-- test/solidity/Facets/AcrossFacet.t.sol | 2 +- test/solidity/Facets/AcrossFacetPacked.t.sol | 2 +- .../solidity/Facets/AcrossFacetPackedV3.t.sol | 2 +- test/solidity/Facets/AcrossFacetV3.t.sol | 2 +- test/solidity/Facets/AllBridgeFacet.t.sol | 2 +- .../solidity/Facets/ArbitrumBridgeFacet.t.sol | 2 +- test/solidity/Facets/CBridge/CBridge.t.sol | 2 +- .../CBridge/CBridgeAndFeeCollection.t.sol | 2 +- .../Facets/CBridge/CBridgeFacetPacked.t.sol | 2 +- .../Facets/CBridge/CBridgeRefund.t.sol | 2 +- .../Facets/CelerCircleBridgeFacet.t.sol | 2 +- test/solidity/Facets/ChainflipFacet.t.sol | 2 +- test/solidity/Facets/DeBridgeDlnFacet.t.sol | 2 +- test/solidity/Facets/DexManagerFacet.t.sol | 4 ++-- test/solidity/Facets/GasZipFacet.t.sol | 2 +- test/solidity/Facets/GenericSwapFacet.t.sol | 2 +- test/solidity/Facets/GenericSwapFacetV3.t.sol | 6 ++--- test/solidity/Facets/GlacisFacet.t.sol | 2 +- test/solidity/Facets/GnosisBridgeFacet.t.sol | 2 +- test/solidity/Facets/HopFacet.t.sol | 6 ++--- .../HopFacetOptimizedL1.t.sol | 2 +- .../HopFacetOptimizedL2.t.sol | 2 +- .../HopFacetPacked/HopFacetPackedL1.t.sol | 4 ++-- .../HopFacetPacked/HopFacetPackedL2.t.sol | 4 ++-- test/solidity/Facets/MayanFacet.t.sol | 2 +- .../OmniBridgeFacet/OmniBridgeFacet.t.sol | 2 +- .../OmniBridgeFacet/OmniBridgeL2Facet.t.sol | 2 +- .../solidity/Facets/OptimismBridgeFacet.t.sol | 2 +- test/solidity/Facets/PioneerFacet.t.sol | 2 +- test/solidity/Facets/PolygonBridgeFacet.t.sol | 2 +- test/solidity/Facets/RelayFacet.t.sol | 2 +- test/solidity/Facets/SquidFacet.t.sol | 2 +- test/solidity/Facets/StargateFacetV2.t.sol | 2 +- test/solidity/Facets/SymbiosisFacet.t.sol | 2 +- test/solidity/Facets/ThorSwapFacet.t.sol | 2 +- .../Gas/CBridgeFacetPackedARB.gas.t.sol | 2 +- .../Gas/CBridgeFacetPackedETH.gas.t.sol | 6 ++--- test/solidity/Gas/Hop.t.sol | 2 +- test/solidity/Gas/HopFacetPackedARB.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedETH.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedPOL.gas.t.sol | 2 +- test/solidity/Helpers/SwapperV2.t.sol | 2 +- test/solidity/Periphery/GasZipPeriphery.t.sol | 2 +- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 2 +- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 2 +- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 6 ++--- .../Periphery/LidoWrapper/LidoWrapper.t.sol | 4 ++-- test/solidity/utils/BaseDiamondTest.sol | 23 +++++++++++++------ 50 files changed, 78 insertions(+), 69 deletions(-) diff --git a/templates/facetTest.template.hbs b/templates/facetTest.template.hbs index e9d84b022..8b9771e31 100644 --- a/templates/facetTest.template.hbs +++ b/templates/facetTest.template.hbs @@ -45,7 +45,7 @@ contract {{titleCase name}}FacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address({{camelCase name}}Facet), functionSelectors); + addFacet(diamond, address({{camelCase name}}Facet), functionSelectors); {{camelCase name}}Facet = Test{{titleCase name}}Facet(address(diamond)); {{camelCase name}}Facet.addDex(ADDRESS_UNISWAP); {{camelCase name}}Facet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AccessManagerFacet.t.sol b/test/solidity/Facets/AccessManagerFacet.t.sol index 9bef61f12..c2308850d 100644 --- a/test/solidity/Facets/AccessManagerFacet.t.sol +++ b/test/solidity/Facets/AccessManagerFacet.t.sol @@ -29,11 +29,11 @@ contract AccessManagerFacetTest is TestBase { allowedFunctionSelectors[1] = accessMgr .addressCanExecuteMethod .selector; - addFacet(address(diamond), address(accessMgr), allowedFunctionSelectors); + addFacet(diamond, address(accessMgr), allowedFunctionSelectors); bytes4[] memory restrictedFunctionSelectors = new bytes4[](1); restrictedFunctionSelectors[0] = restricted.restrictedMethod.selector; - addFacet(address(diamond), address(restricted), restrictedFunctionSelectors); + addFacet(diamond, address(restricted), restrictedFunctionSelectors); accessMgr = AccessManagerFacet(address(diamond)); restricted = RestrictedContract(address(diamond)); diff --git a/test/solidity/Facets/AcrossFacet.t.sol b/test/solidity/Facets/AcrossFacet.t.sol index a44332d55..d0ad2444d 100644 --- a/test/solidity/Facets/AcrossFacet.t.sol +++ b/test/solidity/Facets/AcrossFacet.t.sol @@ -50,7 +50,7 @@ contract AcrossFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(acrossFacet), functionSelectors); + addFacet(diamond, address(acrossFacet), functionSelectors); acrossFacet = TestAcrossFacet(address(diamond)); acrossFacet.addDex(ADDRESS_UNISWAP); acrossFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AcrossFacetPacked.t.sol b/test/solidity/Facets/AcrossFacetPacked.t.sol index 57c190a0e..bb12e9644 100644 --- a/test/solidity/Facets/AcrossFacetPacked.t.sol +++ b/test/solidity/Facets/AcrossFacetPacked.t.sol @@ -108,7 +108,7 @@ contract AcrossFacetPackedTest is TestBase { .selector; // add facet to diamond - addFacet(address(diamond), address(acrossFacetPacked), functionSelectors); + addFacet(diamond, address(acrossFacetPacked), functionSelectors); acrossFacetPacked = AcrossFacetPacked(payable(address(diamond))); /// Prepare parameters diff --git a/test/solidity/Facets/AcrossFacetPackedV3.t.sol b/test/solidity/Facets/AcrossFacetPackedV3.t.sol index 48defc167..0ccec4640 100644 --- a/test/solidity/Facets/AcrossFacetPackedV3.t.sol +++ b/test/solidity/Facets/AcrossFacetPackedV3.t.sol @@ -115,7 +115,7 @@ contract AcrossFacetPackedV3Test is TestBase { .selector; // add facet to diamond - addFacet(address(diamond), address(acrossFacetPackedV3), functionSelectors); + addFacet(diamond, address(acrossFacetPackedV3), functionSelectors); acrossFacetPackedV3 = AcrossFacetPackedV3(payable(address(diamond))); /// Prepare parameters diff --git a/test/solidity/Facets/AcrossFacetV3.t.sol b/test/solidity/Facets/AcrossFacetV3.t.sol index 0f3910f57..2d3c4911f 100644 --- a/test/solidity/Facets/AcrossFacetV3.t.sol +++ b/test/solidity/Facets/AcrossFacetV3.t.sol @@ -56,7 +56,7 @@ contract AcrossFacetV3Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(acrossFacetV3), functionSelectors); + addFacet(diamond, address(acrossFacetV3), functionSelectors); acrossFacetV3 = TestAcrossFacetV3(address(diamond)); acrossFacetV3.addDex(ADDRESS_UNISWAP); acrossFacetV3.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AllBridgeFacet.t.sol b/test/solidity/Facets/AllBridgeFacet.t.sol index 17aa5d692..b2c42cf5b 100644 --- a/test/solidity/Facets/AllBridgeFacet.t.sol +++ b/test/solidity/Facets/AllBridgeFacet.t.sol @@ -78,7 +78,7 @@ contract AllBridgeFacetTest is TestBaseFacet, LiFiData { .selector; functionSelectors[4] = allBridgeFacet.getAllBridgeChainId.selector; - addFacet(address(diamond), address(allBridgeFacet), functionSelectors); + addFacet(diamond, address(allBridgeFacet), functionSelectors); allBridgeFacet = TestAllBridgeFacet(address(diamond)); allBridgeFacet.addDex(ADDRESS_UNISWAP); allBridgeFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol index 9b6d27e3a..4d60827c3 100644 --- a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol +++ b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol @@ -57,7 +57,7 @@ contract ArbitrumBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(arbitrumBridgeFacet), functionSelectors); + addFacet(diamond, address(arbitrumBridgeFacet), functionSelectors); arbitrumBridgeFacet = TestArbitrumBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/CBridge/CBridge.t.sol b/test/solidity/Facets/CBridge/CBridge.t.sol index 2ace03982..b7e5bedb9 100644 --- a/test/solidity/Facets/CBridge/CBridge.t.sol +++ b/test/solidity/Facets/CBridge/CBridge.t.sol @@ -89,7 +89,7 @@ contract CBridgeFacetTest is TestBaseFacet { functionSelectors[3] = cBridge.setFunctionApprovalBySignature.selector; functionSelectors[4] = cBridge.triggerRefund.selector; - addFacet(address(diamond), address(cBridge), functionSelectors); + addFacet(diamond, address(cBridge), functionSelectors); cBridge = TestCBridgeFacet(address(diamond)); cBridge.addDex(address(uniswap)); diff --git a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol index 8fd7d1ff0..c892b350c 100644 --- a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol @@ -43,7 +43,7 @@ contract CBridgeAndFeeCollectionTest is TestBase { functionSelectors[2] = cBridge.addDex.selector; functionSelectors[3] = cBridge.setFunctionApprovalBySignature.selector; - addFacet(address(diamond), address(cBridge), functionSelectors); + addFacet(diamond, address(cBridge), functionSelectors); cBridge = TestCBridgeFacet(address(diamond)); cBridge.addDex(address(uniswap)); diff --git a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol index 542acd2c8..5330e87f4 100644 --- a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol @@ -86,7 +86,7 @@ contract CBridgeFacetPackedTest is TestBase { .selector; functionSelectors[8] = cBridgeFacetPacked.triggerRefund.selector; - addFacet(address(diamond), address(cBridgeFacetPacked), functionSelectors); + addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare parameters diff --git a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol index 227bb1854..96b3faecb 100644 --- a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol @@ -93,7 +93,7 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { selector[0] = withdrawFacet.executeCallAndWithdraw.selector; vm.startPrank(OWNER_ADDRESS); - addFacet(address(diamond), address(withdrawFacet), selector); + addFacet(diamond, address(withdrawFacet), selector); vm.stopPrank(); withdrawFacet = WithdrawFacet(address(diamond)); diff --git a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol index 273b10e4c..95c8d67bb 100644 --- a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol +++ b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol @@ -54,7 +54,7 @@ contract CelerCircleBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(celerCircleBridgeFacet), functionSelectors); + addFacet(diamond, address(celerCircleBridgeFacet), functionSelectors); celerCircleBridgeFacet = TestCelerCircleBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/ChainflipFacet.t.sol b/test/solidity/Facets/ChainflipFacet.t.sol index 972329d52..115b73697 100644 --- a/test/solidity/Facets/ChainflipFacet.t.sol +++ b/test/solidity/Facets/ChainflipFacet.t.sol @@ -59,7 +59,7 @@ contract ChainflipFacetTest is TestBaseFacet, LiFiData { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(chainflipFacet), functionSelectors); + addFacet(diamond, address(chainflipFacet), functionSelectors); chainflipFacet = TestChainflipFacet(address(diamond)); chainflipFacet.addDex(ADDRESS_UNISWAP); chainflipFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/DeBridgeDlnFacet.t.sol b/test/solidity/Facets/DeBridgeDlnFacet.t.sol index 0c0529c48..d98da4bd8 100644 --- a/test/solidity/Facets/DeBridgeDlnFacet.t.sol +++ b/test/solidity/Facets/DeBridgeDlnFacet.t.sol @@ -62,7 +62,7 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { functionSelectors[5] = deBridgeDlnFacet.getDeBridgeChainId.selector; functionSelectors[6] = DeBridgeDlnFacet.initDeBridgeDln.selector; - addFacet(address(diamond), address(deBridgeDlnFacet), functionSelectors); + addFacet(diamond, address(deBridgeDlnFacet), functionSelectors); deBridgeDlnFacet = TestDeBridgeDlnFacet(address(diamond)); deBridgeDlnFacet.addDex(ADDRESS_UNISWAP); deBridgeDlnFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/DexManagerFacet.t.sol b/test/solidity/Facets/DexManagerFacet.t.sol index 405755beb..2cbfc7107 100644 --- a/test/solidity/Facets/DexManagerFacet.t.sol +++ b/test/solidity/Facets/DexManagerFacet.t.sol @@ -49,7 +49,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { .selector; functionSelectors[7] = DexManagerFacet.isFunctionApproved.selector; - addFacet(address(diamond), address(dexMgr), functionSelectors); + addFacet(diamond, address(dexMgr), functionSelectors); // add AccessManagerFacet to be able to whitelist addresses for execution of protected functions accessMgr = new AccessManagerFacet(); @@ -57,7 +57,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { functionSelectors = new bytes4[](2); functionSelectors[0] = accessMgr.setCanExecute.selector; functionSelectors[1] = accessMgr.addressCanExecuteMethod.selector; - addFacet(address(diamond), address(accessMgr), functionSelectors); + addFacet(diamond, address(accessMgr), functionSelectors); accessMgr = AccessManagerFacet(address(diamond)); dexMgr = DexManagerFacet(address(diamond)); diff --git a/test/solidity/Facets/GasZipFacet.t.sol b/test/solidity/Facets/GasZipFacet.t.sol index 10e0739a8..af5eff276 100644 --- a/test/solidity/Facets/GasZipFacet.t.sol +++ b/test/solidity/Facets/GasZipFacet.t.sol @@ -67,7 +67,7 @@ contract GasZipFacetTest is TestBaseFacet { functionSelectors[5] = gasZipFacet .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(gasZipFacet), functionSelectors); + addFacet(diamond, address(gasZipFacet), functionSelectors); gasZipFacet = TestGasZipFacet(payable(address(diamond))); diff --git a/test/solidity/Facets/GenericSwapFacet.t.sol b/test/solidity/Facets/GenericSwapFacet.t.sol index a2e4bf983..bedd98665 100644 --- a/test/solidity/Facets/GenericSwapFacet.t.sol +++ b/test/solidity/Facets/GenericSwapFacet.t.sol @@ -40,7 +40,7 @@ contract GenericSwapFacetTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(genericSwapFacet), functionSelectors); + addFacet(diamond, address(genericSwapFacet), functionSelectors); genericSwapFacet = TestGenericSwapFacet(address(diamond)); genericSwapFacet.addDex(address(uniswap)); diff --git a/test/solidity/Facets/GenericSwapFacetV3.t.sol b/test/solidity/Facets/GenericSwapFacetV3.t.sol index 2fa066682..7c9c4e089 100644 --- a/test/solidity/Facets/GenericSwapFacetV3.t.sol +++ b/test/solidity/Facets/GenericSwapFacetV3.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { GenericSwapFacet } from "lifi/Facets/GenericSwapFacet.sol"; import { GenericSwapFacetV3 } from "lifi/Facets/GenericSwapFacetV3.sol"; import { ContractCallNotAllowed, CumulativeSlippageTooHigh, NativeAssetTransferFailed } from "lifi/Errors/GenericErrors.sol"; -import { TestHelpers, MockUniswapDEX, NonETHReceiver } from "../utils/TestHelpers.sol"; +import { MockUniswapDEX, NonETHReceiver } from "../utils/TestHelpers.sol"; import { ERC20, SafeTransferLib } from "solmate/utils/SafeTransferLib.sol"; import { LibAllowList, LibSwap, TestBase } from "../utils/TestBase.sol"; @@ -73,7 +73,7 @@ contract GenericSwapFacetV3Test is TestBase { functionSelectors[3] = genericSwapFacet .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(genericSwapFacet), functionSelectors); + addFacet(diamond, address(genericSwapFacet), functionSelectors); // add genericSwapFacet (v3) to diamond bytes4[] memory functionSelectorsV3 = new bytes4[](6); @@ -96,7 +96,7 @@ contract GenericSwapFacetV3Test is TestBase { .swapTokensMultipleV3NativeToERC20 .selector; - addFacet(address(diamond), address(genericSwapFacetV3), functionSelectorsV3); + addFacet(diamond, address(genericSwapFacetV3), functionSelectorsV3); genericSwapFacet = TestGenericSwapFacet(address(diamond)); genericSwapFacetV3 = TestGenericSwapFacetV3(address(diamond)); diff --git a/test/solidity/Facets/GlacisFacet.t.sol b/test/solidity/Facets/GlacisFacet.t.sol index d82aed5b9..4426fbd2a 100644 --- a/test/solidity/Facets/GlacisFacet.t.sol +++ b/test/solidity/Facets/GlacisFacet.t.sol @@ -59,7 +59,7 @@ abstract contract GlacisFacetTestBase is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(glacisFacet), functionSelectors); + addFacet(diamond, address(glacisFacet), functionSelectors); glacisFacet = TestGlacisFacet(address(diamond)); glacisFacet.addDex(ADDRESS_UNISWAP); glacisFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/GnosisBridgeFacet.t.sol b/test/solidity/Facets/GnosisBridgeFacet.t.sol index cd9de45f0..d72ea4d4c 100644 --- a/test/solidity/Facets/GnosisBridgeFacet.t.sol +++ b/test/solidity/Facets/GnosisBridgeFacet.t.sol @@ -62,7 +62,7 @@ contract GnosisBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(gnosisBridgeFacet), functionSelectors); + addFacet(diamond, address(gnosisBridgeFacet), functionSelectors); gnosisBridgeFacet = TestGnosisBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacet.t.sol b/test/solidity/Facets/HopFacet.t.sol index ee7454c0e..0b7fe4042 100644 --- a/test/solidity/Facets/HopFacet.t.sol +++ b/test/solidity/Facets/HopFacet.t.sol @@ -54,7 +54,7 @@ contract HopFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(hopFacet), functionSelectors); + addFacet(diamond, address(hopFacet), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](3); configs[0] = HopFacet.Config(ADDRESS_USDC, USDC_BRIDGE); @@ -247,7 +247,7 @@ contract HopFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(hopFacet2), functionSelectors); + addFacet(diamond, address(hopFacet2), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](3); configs[0] = HopFacet.Config(ADDRESS_USDC, USDC_BRIDGE); @@ -282,7 +282,7 @@ contract HopFacetTest is TestBaseFacet { functionSelectors[2] = hopFacet2.initHop.selector; functionSelectors[3] = hopFacet2.registerBridge.selector; - addFacet(address(diamond), address(hopFacet2), functionSelectors); + addFacet(diamond, address(hopFacet2), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](1); configs[0] = HopFacet.Config(addressUSDCPolygon, ammWrapperPolygon); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol index 929805cc2..5d1258289 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol @@ -56,7 +56,7 @@ contract HopFacetOptimizedL1Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(hopFacet), functionSelectors); + addFacet(diamond, address(hopFacet), functionSelectors); hopFacet = TestHopFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol index 25ff28ef9..2d5d78582 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol @@ -60,7 +60,7 @@ contract HopFacetOptimizedL2Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(hopFacet), functionSelectors); + addFacet(diamond, address(hopFacet), functionSelectors); hopFacet = TestHopFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol index ef7e3baab..5c2840fe2 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol @@ -113,7 +113,7 @@ contract HopFacetPackedL1Test is TestBase { .encode_startBridgeTokensViaHopL1ERC20Packed .selector; - addFacet(address(diamond), address(hopFacetPacked), functionSelectors); + addFacet(diamond, address(hopFacetPacked), functionSelectors); hopFacetPacked = HopFacetPacked(address(diamond)); /// Approval @@ -131,7 +131,7 @@ contract HopFacetPackedL1Test is TestBase { .setApprovalForBridges .selector; addFacet( - address(diamond), + diamond, address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol index 4b1ded25a..d3e30cae7 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol @@ -120,7 +120,7 @@ contract HopFacetPackedL2Test is TestBase { .encode_startBridgeTokensViaHopL1ERC20Packed .selector; - addFacet(address(diamond), address(hopFacetPacked), functionSelectors); + addFacet(diamond, address(hopFacetPacked), functionSelectors); hopFacetPacked = HopFacetPacked(address(diamond)); /// Approval @@ -150,7 +150,7 @@ contract HopFacetPackedL2Test is TestBase { .setApprovalForBridges .selector; addFacet( - address(diamond), + diamond, address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Facets/MayanFacet.t.sol b/test/solidity/Facets/MayanFacet.t.sol index a5e1c6bda..d56f8a5b3 100644 --- a/test/solidity/Facets/MayanFacet.t.sol +++ b/test/solidity/Facets/MayanFacet.t.sol @@ -111,7 +111,7 @@ contract MayanFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(mayanBridgeFacet), functionSelectors); + addFacet(diamond, address(mayanBridgeFacet), functionSelectors); mayanBridgeFacet = TestMayanFacet(address(diamond)); mayanBridgeFacet.addDex(ADDRESS_UNISWAP); mayanBridgeFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol index b033fbfc6..ae3eab85c 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol @@ -53,7 +53,7 @@ contract OmniBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(omniBridgeFacet), functionSelectors); + addFacet(diamond, address(omniBridgeFacet), functionSelectors); omniBridgeFacet = TestOmniBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol index a7193d270..1c1e458b5 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol @@ -62,7 +62,7 @@ contract OmniBridgeL2FacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(omniBridgeFacet), functionSelectors); + addFacet(diamond, address(omniBridgeFacet), functionSelectors); omniBridgeFacet = TestOmniBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/OptimismBridgeFacet.t.sol b/test/solidity/Facets/OptimismBridgeFacet.t.sol index 30ff689e9..c9e7c1b71 100644 --- a/test/solidity/Facets/OptimismBridgeFacet.t.sol +++ b/test/solidity/Facets/OptimismBridgeFacet.t.sol @@ -63,7 +63,7 @@ contract OptimismBridgeFacetTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(optimismBridgeFacet), functionSelectors); + addFacet(diamond, address(optimismBridgeFacet), functionSelectors); OptimismBridgeFacet.Config[] memory configs = new OptimismBridgeFacet.Config[](1); diff --git a/test/solidity/Facets/PioneerFacet.t.sol b/test/solidity/Facets/PioneerFacet.t.sol index 6675c65d7..17143740b 100644 --- a/test/solidity/Facets/PioneerFacet.t.sol +++ b/test/solidity/Facets/PioneerFacet.t.sol @@ -50,7 +50,7 @@ contract PioneerFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(basePioneerFacet), functionSelectors); + addFacet(diamond, address(basePioneerFacet), functionSelectors); pioneerFacet = TestPioneerFacet(address(diamond)); pioneerFacet.addDex(ADDRESS_UNISWAP); pioneerFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/PolygonBridgeFacet.t.sol b/test/solidity/Facets/PolygonBridgeFacet.t.sol index d7131e7e0..dbecd9f57 100644 --- a/test/solidity/Facets/PolygonBridgeFacet.t.sol +++ b/test/solidity/Facets/PolygonBridgeFacet.t.sol @@ -53,7 +53,7 @@ contract PolygonBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(polygonBridgeFacet), functionSelectors); + addFacet(diamond, address(polygonBridgeFacet), functionSelectors); polygonBridgeFacet = TestPolygonBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 6d94e5db1..133f64c2b 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -68,7 +68,7 @@ contract RelayFacetTest is TestBaseFacet, LiFiData { functionSelectors[4] = relayFacet.getMappedChainId.selector; functionSelectors[5] = relayFacet.setConsumedId.selector; - addFacet(address(diamond), address(relayFacet), functionSelectors); + addFacet(diamond, address(relayFacet), functionSelectors); relayFacet = TestRelayFacet(address(diamond)); relayFacet.addDex(ADDRESS_UNISWAP); relayFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/SquidFacet.t.sol b/test/solidity/Facets/SquidFacet.t.sol index 9b371c28b..76354282f 100644 --- a/test/solidity/Facets/SquidFacet.t.sol +++ b/test/solidity/Facets/SquidFacet.t.sol @@ -58,7 +58,7 @@ contract SquidFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(squidFacet), functionSelectors); + addFacet(diamond, address(squidFacet), functionSelectors); squidFacet = TestSquidFacet(address(diamond)); squidFacet.addDex(ADDRESS_UNISWAP); squidFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/StargateFacetV2.t.sol b/test/solidity/Facets/StargateFacetV2.t.sol index 6a415a579..299513b7a 100644 --- a/test/solidity/Facets/StargateFacetV2.t.sol +++ b/test/solidity/Facets/StargateFacetV2.t.sol @@ -75,7 +75,7 @@ contract StargateFacetV2Test is TestBaseFacet { .selector; functionSelectors[4] = stargateFacetV2.tokenMessaging.selector; - addFacet(address(diamond), address(stargateFacetV2), functionSelectors); + addFacet(diamond, address(stargateFacetV2), functionSelectors); stargateFacetV2 = TestStargateFacetV2(payable(address(diamond))); // whitelist DEX and feeCollector addresses and function selectors in diamond diff --git a/test/solidity/Facets/SymbiosisFacet.t.sol b/test/solidity/Facets/SymbiosisFacet.t.sol index 2beb9cb19..7742e213d 100644 --- a/test/solidity/Facets/SymbiosisFacet.t.sol +++ b/test/solidity/Facets/SymbiosisFacet.t.sol @@ -49,7 +49,7 @@ contract SymbiosisFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(symbiosisFacet), functionSelectors); + addFacet(diamond, address(symbiosisFacet), functionSelectors); symbiosisFacet = TestSymbiosisFacet(address(diamond)); diff --git a/test/solidity/Facets/ThorSwapFacet.t.sol b/test/solidity/Facets/ThorSwapFacet.t.sol index 5174a7d3e..2d8aff2e3 100644 --- a/test/solidity/Facets/ThorSwapFacet.t.sol +++ b/test/solidity/Facets/ThorSwapFacet.t.sol @@ -43,7 +43,7 @@ contract ThorSwapFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(thorSwapFacet), functionSelectors); + addFacet(diamond, address(thorSwapFacet), functionSelectors); thorSwapFacet = TestThorSwapFacet(address(diamond)); thorSwapFacet.addDex(ADDRESS_UNISWAP); diff --git a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol index 0ad9826ad..d492404c7 100644 --- a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol @@ -57,7 +57,7 @@ contract CBridgeGasARBTest is TestBase { .encode_startBridgeTokensViaCBridgeERC20Packed .selector; - addFacet(address(diamond), address(cBridgeFacetPacked), functionSelectors); + addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare parameters diff --git a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol index 0cfc47158..43810c59f 100644 --- a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol @@ -71,7 +71,7 @@ contract CBridgeGasETHTest is TestBase { .encode_startBridgeTokensViaCBridgeERC20Packed .selector; - addFacet(address(diamond), address(cBridgeFacetPacked), functionSelectors); + addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare CBridgeFacet @@ -82,7 +82,7 @@ contract CBridgeGasETHTest is TestBase { .startBridgeTokensViaCBridge .selector; - addFacet(address(diamond), address(cBridgeFacet), functionSelectors2); + addFacet(diamond, address(cBridgeFacet), functionSelectors2); cBridgeFacet = CBridgeFacet(address(diamond)); /// Perpare parameters @@ -170,7 +170,7 @@ contract CBridgeGasETHTest is TestBase { .setApprovalForBridges .selector; addFacet( - address(diamond), + diamond, address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Gas/Hop.t.sol b/test/solidity/Gas/Hop.t.sol index 3dcaf0431..5785539ef 100644 --- a/test/solidity/Gas/Hop.t.sol +++ b/test/solidity/Gas/Hop.t.sol @@ -25,7 +25,7 @@ contract HopGasTest is TestBase { functionSelectors[0] = hopFacet.initHop.selector; functionSelectors[1] = hopFacet.startBridgeTokensViaHop.selector; - addFacet(address(diamond), address(hopFacet), functionSelectors); + addFacet(diamond, address(hopFacet), functionSelectors); hopFacet = HopFacet(address(diamond)); HopFacet.Config[] memory config = new HopFacet.Config[](1); diff --git a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol index 8a2ed50bd..e590bbc04 100644 --- a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol @@ -98,7 +98,7 @@ pragma solidity ^0.8.17; // .encoder_startBridgeTokensViaHopL2ERC20Packed // .selector; -// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); +// addFacet(diamond, address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol index e707c644b..38c992f81 100644 --- a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol @@ -70,7 +70,7 @@ pragma solidity ^0.8.17; // .startBridgeTokensViaHopL1ERC20Min // .selector; -// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); +// addFacet(diamond, address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol index 8f0e894ee..daefa092a 100644 --- a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol @@ -99,7 +99,7 @@ pragma solidity ^0.8.17; // .encoder_startBridgeTokensViaHopL2ERC20Packed // .selector; -// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); +// addFacet(diamond, address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Helpers/SwapperV2.t.sol b/test/solidity/Helpers/SwapperV2.t.sol index 218ba838b..2a6a177cc 100644 --- a/test/solidity/Helpers/SwapperV2.t.sol +++ b/test/solidity/Helpers/SwapperV2.t.sol @@ -51,7 +51,7 @@ contract SwapperV2Test is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(swapper), functionSelectors); + addFacet(diamond, address(swapper), functionSelectors); swapper = TestSwapperV2(address(diamond)); swapper.addDex(address(amm)); diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index b276e8496..03fe4323e 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -471,7 +471,7 @@ contract GasZipPeripheryTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(address(diamond), address(_gnosisBridgeFacet), functionSelectors); + addFacet(diamond, address(_gnosisBridgeFacet), functionSelectors); _gnosisBridgeFacet = TestGnosisBridgeFacet(address(diamond)); diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index f68e46c1e..07280ea50 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -78,7 +78,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { coreRouteFacet = new CoreRouteFacet(USER_DIAMOND_OWNER); bytes4[] memory selectors = new bytes4[](1); selectors[0] = CoreRouteFacet.processRoute.selector; - addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); + addFacet(ldaDiamond, address(coreRouteFacet), selectors); coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); } diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 5843c0f48..b997768f9 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -121,7 +121,7 @@ abstract contract BaseDexFacetTest is BaseCoreRouteTest { bytes4[] memory functionSelectors ) = _createFacetAndSelectors(); - addFacet(address(ldaDiamond), facetAddress, functionSelectors); + addFacet(ldaDiamond, facetAddress, functionSelectors); _setFacetInstance(payable(address(ldaDiamond))); } diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 0189f93f2..657ced313 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -22,7 +22,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { MockPullERC20Facet mockPull = new MockPullERC20Facet(); bytes4[] memory sel = new bytes4[](1); sel[0] = MockPullERC20Facet.pull.selector; - addFacet(address(ldaDiamond), address(mockPull), sel); + addFacet(ldaDiamond, address(mockPull), sel); pullSel = sel[0]; } @@ -31,14 +31,14 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { MockNativeFacet mock = new MockNativeFacet(); bytes4[] memory selectors = new bytes4[](1); selectors[0] = MockNativeFacet.handleNative.selector; - addFacet(address(ldaDiamond), address(mock), selectors); + addFacet(ldaDiamond, address(mock), selectors); } function _addMockPullFacet() internal returns (bytes4 sel) { MockPullERC20Facet mock = new MockPullERC20Facet(); bytes4[] memory selectors = new bytes4[](1); selectors[0] = MockPullERC20Facet.pull.selector; - addFacet(address(ldaDiamond), address(mock), selectors); + addFacet(ldaDiamond, address(mock), selectors); return selectors[0]; } diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol index b87063a88..56cb2e768 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol @@ -99,7 +99,7 @@ contract LidoWrapperTest is TestBase, LiFiData { functionSelectors[4] = relayFacet.getMappedChainId.selector; functionSelectors[5] = relayFacet.setConsumedId.selector; - addFacet(address(diamond), address(relayFacet), functionSelectors); + addFacet(diamond, address(relayFacet), functionSelectors); // slither-disable-next-line reentrancy-no-eth relayFacet = TestRelayFacet(address(diamond)); @@ -505,7 +505,7 @@ contract LidoWrapperTest is TestBase, LiFiData { .selector; // add facet to diamond and store diamond with facet interface in variable - addFacet(address(diamond), address(genericSwapFacetV3), functionSelectors); + addFacet(diamond, address(genericSwapFacetV3), functionSelectors); genericSwapFacetV3 = GenericSwapFacetV3(address(diamond)); } } diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol index 42b20bc7c..31c59f473 100644 --- a/test/solidity/utils/BaseDiamondTest.sol +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -5,6 +5,7 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { Test } from "forge-std/Test.sol"; abstract contract BaseDiamondTest is Test { @@ -13,7 +14,9 @@ abstract contract BaseDiamondTest is Test { // Common function to add Diamond Loupe selectors function _addDiamondLoupeSelectors(address _diamondLoupe) internal { bytes4[] memory functionSelectors = new bytes4[](5); - functionSelectors[0] = DiamondLoupeFacet.facetFunctionSelectors.selector; + functionSelectors[0] = DiamondLoupeFacet + .facetFunctionSelectors + .selector; functionSelectors[1] = DiamondLoupeFacet.facets.selector; functionSelectors[2] = DiamondLoupeFacet.facetAddress.selector; functionSelectors[3] = DiamondLoupeFacet.facetAddresses.selector; @@ -33,7 +36,9 @@ abstract contract BaseDiamondTest is Test { bytes4[] memory functionSelectors = new bytes4[](4); functionSelectors[0] = OwnershipFacet.transferOwnership.selector; functionSelectors[1] = OwnershipFacet.cancelOwnershipTransfer.selector; - functionSelectors[2] = OwnershipFacet.confirmOwnershipTransfer.selector; + functionSelectors[2] = OwnershipFacet + .confirmOwnershipTransfer + .selector; functionSelectors[3] = OwnershipFacet.owner.selector; cut.push( @@ -46,7 +51,7 @@ abstract contract BaseDiamondTest is Test { } function addFacet( - address _diamond, + LiFiDiamond _diamond, address _facet, bytes4[] memory _selectors ) public virtual { @@ -54,7 +59,7 @@ abstract contract BaseDiamondTest is Test { } function addFacet( - address _diamond, + LiFiDiamond _diamond, address _facet, bytes4[] memory _selectors, address _init, @@ -64,13 +69,13 @@ abstract contract BaseDiamondTest is Test { } function _addFacet( - address _diamond, + LiFiDiamond _diamond, address _facet, bytes4[] memory _selectors, address _init, bytes memory _initCallData ) internal virtual { - vm.startPrank(OwnershipFacet(_diamond).owner()); + vm.startPrank(OwnershipFacet(address(_diamond)).owner()); cut.push( LibDiamond.FacetCut({ facetAddress: _facet, @@ -79,7 +84,11 @@ abstract contract BaseDiamondTest is Test { }) ); - DiamondCutFacet(_diamond).diamondCut(cut, _init, _initCallData); + DiamondCutFacet(address(_diamond)).diamondCut( + cut, + _init, + _initCallData + ); delete cut; vm.stopPrank(); From df1cad8579eba370b311d380cece65d30d69bf3d Mon Sep 17 00:00:00 2001 From: Daniel <77058885+0xDEnYO@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:46:04 +0700 Subject: [PATCH 048/220] Update test/solidity/utils/TestBaseForksConstants.sol Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- test/solidity/utils/TestBaseForksConstants.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solidity/utils/TestBaseForksConstants.sol b/test/solidity/utils/TestBaseForksConstants.sol index ef5dc7e55..cc990a6c6 100644 --- a/test/solidity/utils/TestBaseForksConstants.sol +++ b/test/solidity/utils/TestBaseForksConstants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; abstract contract TestBaseForksConstants { From 303be01ca67161679bd03ac68fb370babef7a46a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 21 Aug 2025 09:48:20 +0200 Subject: [PATCH 049/220] Remove new-lda-2.0.md file and update interface annotations in ICurve and ICurveLegacy for clarity --- new-lda-2.0.md | 1291 ----------------- src/Interfaces/ICurve.sol | 1 + src/Interfaces/ICurveLegacy.sol | 1 + .../Periphery/Lda/BaseCoreRouteTest.t.sol | 2 +- test/solidity/utils/MockNoCallbackPool.sol | 2 +- 5 files changed, 4 insertions(+), 1293 deletions(-) delete mode 100644 new-lda-2.0.md diff --git a/new-lda-2.0.md b/new-lda-2.0.md deleted file mode 100644 index 51441f757..000000000 --- a/new-lda-2.0.md +++ /dev/null @@ -1,1291 +0,0 @@ -## **Part I: Architectural Decision** - -## **1. Core Architecture Comparison** - -### **1.1 High-Level Architecture** - -| **Pattern** | **OLD Monolithic** | **NEW Diamond + Registry** | -| --- | --- | --- | -| **Proxy Style** | Transparent proxy. One huge contract | EIP-2535 Diamond. Minimal proxy + many facets | -| **Code-Size** | All logic in one file → size limits | Each facet separate → no init-code size hits | -| **Upgrade Scope** | Redeploy entire contract | Upgrade individual facet | -| **Modularity** | One `swap(...)` with 10+ branches | One facet per dex + dynamic registry dispatch | -| **Tests** | One massive suite | Per-facet suites (`CoreRouteFacet`, `FooFacet`) | -| **Callback Guards** | `lastCalledPool = ...` | `CallbackManager.arm()/verify()/clear()` (see 4.1) | -| **Transfers** | `IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));` | // In facet implementations: `import { LibAsset } from "../Libraries/LibAsset.sol"; LibAsset.transferAsset(tokenIn, payable(recipient), amount); LibAsset.transferFromERC20(tokenIn, from, recipient, amount); LibAsset.depositAsset(tokenIn, amount); | - ---- - -### **1.2. Facet Breakdown & Naming** - -The entry point remains exactly the same: users still call `processRoute(...)`, but under the new architecture that logic now lives in **CoreRouteFacet.sol**, which looks up the target in the registry and forwards execution to the appropriate dex facet. All DEX-related facets will follow the naming convention: **`{DexName}Facet.sol`**. - -| **OLD (standalone functions)** | **NEW Facet** | **Notes** | -| --- | --- | --- | -| `processRoute(...)`, `transferValueAnd…(...)` | **CoreRouteFacet.sol** | Entrypoints + registry + helpers (`applyPermit`, `distributeAndSwap`, `dispatchSwap`) | -| `swapUniV2(...)` | **UniswapV2StyleFacet.sol** | Handles UniV2, SushiSwap, PancakeV2, TraderJoe V1, and other router-based DEXs | -| `swapUniV3(...)` + `uniswapV3SwapCallback(...)` | **UniV3Facet.sol** | Uniswap V3 logic and callbacks | -| `pancakeV3SwapCallback(...)` | **PancakeV3Facet.sol** | May or may not include swap function depending on approach (see sections 3.1 and 3.2) | - -## **3. Two New Architectural Approaches** - -This document presents **two approaches** to replace the monolithic if/else dispatch system from the original `LiFiDEXAggregator.sol`. Both approaches leverage the Diamond pattern to overcome contract size limitations and provide extensibility. - -**Context**: The original monolithic design required `poolTypes` because in this way we could reduce contract size limit and gas costs. With the Diamond pattern, we can explore better architectures that prioritize user gas costs and deployment simplicity. - -### **3.1 Approach 1: Registry driven dispatch** - -This approach uses a dynamic registry where DEX types (previously named as `poolTypes`) are registered with their corresponding function selectors, enabling runtime dispatch without hardcoded if/else chains. - -### **OLD (LiFiDEXAggregator.sol monolithic if/else):** - -```solidity -uint8 t = stream.readUint8(); -if (t == POOL_TYPE_UNIV2) swapUniV2(...); -else if (t == POOL_TYPE_UNIV3) swapUniV3(...); -else if (t == POOL_TYPE_VELODROME_V2) swapVelodromeV2(...); -else if (t == POOL_TYPE_ALGEBRA) swapAlgebra(...); -// ... 20+ more else-if statements -// Growing chain = increasing gas costs - -``` - -### **NEW (Registry driven dispatch):** - -```solidity -// DRAFT code of CoreRouteFacet contract -// DRAFT code of CoreRouteFacet contract -// DRAFT code of CoreRouteFacet contract - -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibDiamond } from "../Libraries/LibDiamond.sol"; -import { LibAccess } from "../Libraries/LibAccess.sol"; -import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -/// --- Custom Errors --- /// -error UnknownDexType(); -error SwapFailed(); -error DexTypeAlreadyRegistered(); -error CannotRemoveUnknownDexType(); -error MismatchedArrayLength(); - -/// @title CoreRouteFacet -/// @notice Handles DEX type registration and swap dispatching via selector registry -contract CoreRouteFacet { - using EnumerableSet for EnumerableSet.UintSet; - - /// --- Storage Namespace --- /// - bytes32 internal constant NAMESPACE = keccak256("com.lifi.lda.facets.core.route"); - - struct Storage { - mapping(uint8 => bytes4) swapSelectorByDex; - mapping(bytes4 => uint8) dexTypeBySelector; - EnumerableSet.UintSet dexTypes; - } - - function getStorage() internal pure returns (Storage storage s) { - bytes32 namespace = NAMESPACE; - assembly { - s.slot := namespace - } - } - - /// --- Events --- /// - event DexTypeRegistered(uint8 indexed dexType, bytes4 indexed selector); - event DexTypeRemoved(uint8 indexed dexType); - - /// --- Admin Functions --- /// - - function registerDexType(uint8 dexType, bytes4 selector) external { - if (msg.sender != LibDiamond.contractOwner()) { - LibAccess.enforceAccessControl(); - } - - Storage storage s = getStorage(); - if (s.dexTypes.contains(dexType)) revert DexTypeAlreadyRegistered(); - - s.swapSelectorByDex[dexType] = selector; - s.dexTypeBySelector[selector] = dexType; - s.dexTypes.add(dexType); - - emit DexTypeRegistered(dexType, selector); - } - - function batchRegisterDexTypes( - uint8[] calldata dexTypes_, - bytes4[] calldata selectors - ) external { - if (msg.sender != LibDiamond.contractOwner()) { - LibAccess.enforceAccessControl(); - } - - if (dexTypes_.length != selectors.length) { - revert MismatchedArrayLength(); - } - - Storage storage s = getStorage(); - - for (uint256 i = 0; i < dexTypes_.length; ) { - uint8 dexType = dexTypes_[i]; - bytes4 selector = selectors[i]; - s.swapSelectorByDex[dexType] = selector; - s.dexTypeBySelector[selector] = dexType; - s.dexTypes.add(dexType); - emit DexTypeRegistered(dexType, selector); - unchecked { - ++i; - } - } - } - - function removeDexType(uint8 dexType) external { - if (msg.sender != LibDiamond.contractOwner()) { - LibAccess.enforceAccessControl(); - } - - Storage storage s = getStorage(); - bytes4 selector = s.swapSelectorByDex[dexType]; - if (selector == bytes4(0)) revert CannotRemoveUnknownDexType(); - - delete s.swapSelectorByDex[dexType]; - delete s.dexTypeBySelector[selector]; - s.dexTypes.remove(dexType); - - emit DexTypeRemoved(dexType); - } - - function batchRemoveDexTypes(uint8[] calldata dexTypes_) external { - if (msg.sender != LibDiamond.contractOwner()) { - LibAccess.enforceAccessControl(); - } - - Storage storage s = getStorage(); - - for (uint256 i = 0; i < dexTypes_.length; ) { - uint8 dexType = dexTypes_[i]; - bytes4 selector = s.swapSelectorByDex[dexType]; - if (selector != bytes4(0)) { - delete s.swapSelectorByDex[dexType]; - delete s.dexTypeBySelector[selector]; - s.dexTypes.remove(dexType); - emit DexTypeRemoved(dexType); - } - unchecked { - ++i; - } - } - } - - /// --- Internal Logic --- /// - - function dispatchSwap( - uint8 dexType, - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) internal returns (uint256 amountOut) { - Storage storage s = getStorage(); - bytes4 sel = s.swapSelectorByDex[dexType]; - if (sel == 0) revert UnknownDexType(); - - bytes memory data = abi.encodePacked(sel, stream, from, tokenIn, amountIn); - (bool ok, bytes memory ret) = address(this).delegatecall(data); - if (!ok) revert SwapFailed(); - - return abi.decode(ret, (uint256)); - } - - /// --- View Functions --- /// - - function getSwapSelectorByDex(uint8 dexType) external view returns (bytes4) { - return getStorage().swapSelectorByDex[dexType]; - } - - function getDexTypeBySelector(bytes4 selector) external view returns (uint8) { - return getStorage().dexTypeBySelector[selector]; - } - - function getAllDexTypes() external view returns (uint8[] memory result) { - Storage storage s = getStorage(); - uint256 len = s.dexTypes.length(); - result = new uint8[](len); - for (uint256 i = 0; i < len; ++i) { - result[i] = uint8(s.dexTypes.at(i)); - } - } - - function getAllDexTypesWithSelectors() - external - view - returns (uint8[] memory dexTypesOut, bytes4[] memory selectors) - { - Storage storage s = getStorage(); - uint256 len = s.dexTypes.length(); - dexTypesOut = new uint8[](len); - selectors = new bytes4[](len); - - for (uint256 i = 0; i < len; ++i) { - uint8 dexType = uint8(s.dexTypes.at(i)); - dexTypesOut[i] = dexType; - selectors[i] = s.swapSelectorByDex[dexType]; - } - } -} - -``` - -### **Adding New DEX (Approach 1):** - -```solidity -// 1. Deploy FooFacet -// 2. Add diamondCut with FooFacet -// 3. Register new DEX type (optionally) -await coreRouteFacet.registerDexType(DEX_TYPE_FOO, FooFacet.swapFoo.selector); - -``` - -### **NO Backend Changes (Approach 1):** - -poolType (newly named dexTypes) stays the same like for the old version of LDA - ---- - -### **3.2 Approach 2: Selector based dispatch** - -This approach eliminates the registry entirely by having the backend directly specify function selectors in the route data, achieving the lowest possible gas costs and deployment complexity - -### **OLD (LiFiDEXAggregator.sol monolithic if/else):** - -```solidity -uint8 t = stream.readUint8(); -if (t == POOL_TYPE_UNIV2) swapUniV2(...); -else if (t == POOL_TYPE_UNIV3) swapUniV3(...); -else if (t == POOL_TYPE_VELODROME_V2) swapVelodromeV2(...); -else if (t == POOL_TYPE_ALGEBRA) swapAlgebra(...); -// ... 20+ more else-if statements -// Growing chain = increasing gas costs - -``` - -### **NEW (Selector based dispatch):** - -```solidity -function swap( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn -) private { - bytes4 selector = stream.readBytes4(); - - (bool success, bytes memory result) = address(this).call( - abi.encodePacked(selector, stream, from, tokenIn, amountIn) - ); - if (!success) revert SwapFailed(); -} - -``` - -### **Adding New DEX (Approach 2):** - -```solidity -// 1. Deploy FooFacet -// 2. Add diamondCut with FooFacet - -``` - -### **Backend Changes (Approach 2):** - -```tsx -// OLD route encoding -const routeData = encodeRoute({ - command: ProcessUserERC20, - token: tokenIn, - pools: [{ - poolType: POOL_TYPE_UNIV3, - poolAddress: poolAddress, - direction: direction, - recipient: recipient - }] -}); - -// NEW route encoding -const routeData = encodeRoute({ - command: ProcessUserERC20, - token: tokenIn, - pools: [{ - selector: UniV3Facet.swapUniV3.selector, // <== selector instead of poolType - poolAddress: poolAddress, - direction: direction, - recipient: recipient - }] -}); - -``` - ---- - -## **4. Implementation Details** - -### **4.1 Facet Dependencies: Different for Each Approach** - -### **Approach 1 (Registry): Dependencies Exist** - -With the registry approach, compatible DEXs share the same `dexType` (previously `poolType`), creating dependencies: - -```solidity -// UniV3Facet.sol - Main implementation -contract UniV3Facet { - function swapUniV3(...) external returns (uint256) { - // Full UniV3 swap logic implementation - } - - function uniswapV3SwapCallback(...) external { - // UniswapV3-specific callback logic - } -} - -// PancakeV3Facet.sol - Callback-only (DEPENDS on UniV3Facet) -contract PancakeV3Facet { - // NO swapPancakeV3 function - reuses UniV3Facet.swapUniV3() - - function pancakeV3SwapCallback(...) external { - // Forward to UniV3 callback logic - IUniV3Facet(address(this)).uniswapV3SwapCallback( - amount0Delta, - amount1Delta, - data - ); - } -} - -``` - -**Registry Approach Dependencies:** - -- `PancakeV3Facet` **depends on** `UniV3Facet` -- Both use same `DEX_TYPE_UNIV3` dexType -- Deployment order matters: `UniV3Facet` must be deployed first -- PancakeV3 only provides callback wrapper - -**Adding PancakeV3 (Registry Approach):** - -```bash -# 1. Deploy UniV3Facet first (if not already deployed) -# 2. Register: registerDexType(DEX_TYPE_UNIV3, UniV3Facet.swapUniV3.selector) -# 3. Deploy PancakeV3Facet (callback only) -# 4. Add PancakeV3Facet to diamond (for callback) -# Backend uses DEX_TYPE_UNIV3 for both UniV3 and PancakeV3 - -``` - ---- - -### **Approach 2 (Selector): Zero Dependencies** - -With the selector approach, each DEX has its own selector, enabling complete independence: - -```solidity -// UniV3Facet.sol - Complete implementation -contract UniV3Facet { - function swapUniV3(...) external returns (uint256) { - // Full UniV3 swap logic implementation - } - - function uniswapV3SwapCallback(...) external { - // UniswapV3-specific callback logic - } -} - -// PancakeV3Facet.sol - Complete implementation (INDEPENDENT) -contract PancakeV3Facet { - function swapPancakeV3(...) external returns (uint256) { - // Full swap logic (can reuse UniV3 logic via libraries) - return LibUniV3Logic.executeSwap(...); - } - - function pancakeV3SwapCallback(...) external { - // PancakeV3-specific callback logic - } -} - -``` - -**Selector Approach Dependencies:** - -- **Zero dependencies** between facets -- Each facet is completely self-contained -- Deploy in any order -- **Compatible DEXs can share selectors** (e.g., UniV2 forks share `UniswapV2StyleFacet.swapUniV2.selector`) - -**Adding PancakeV3 (Selector Approach):** - -```bash -# 1. Deploy PancakeV3Facet (complete implementation) -# 2. Add PancakeV3Facet to diamond -# Backend immediately uses PancakeV3Facet.swapPancakeV3.selector - -``` - ---- - -## **5. Adding a New DEX** - -### **5.1 Approach 1 (Registry): Onboarding Process** - -### **For Uniswap V3-compatible forks (no new dexType needed):** - -Many Uniswap V3 forks (eg Pancake V3) use exactly the Uniswap V3 swap signature but different callback semantics. You don't need a whole new facet or dexType. Only new smaller facet (without swap function) with callback forward: - -```solidity -/// DRAFT file for PancakeV3Facet.sol -/// DRAFT file for PancakeV3Facet.sol -/// DRAFT file for PancakeV3Facet.sol - -// interface with callback in seperated file -interface IUniV3Facet { - function uniswapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external; -} - -// example for PancakeV3 facet -contract PancakeV3Facet { - function pancakeV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - // keep the same logic of handling callback from uniswap v3 - IUniV3Facet(address(this)).uniswapV3SwapCallback( - amount0Delta, - amount1Delta, - data - ); - } -} - -``` - -**Steps:** - -1. DiamondCut in `PancakeV3CallbackFacet.pancakeV3SwapCallback`. -2. No new dexType registration, since it reuses `DEX_TYPE_UNIV3`. - -### **For completely new DEX (new dexType needed):** - -```solidity -/// DRAFT file for FooFacet.sol -/// DRAFT file for FooFacet.sol -/// DRAFT file for FooFacet.sol - -import { InputStream } from "./InputStream.sol"; -import { CallbackManager } from "../Libraries/CallbackManager.sol"; -interface IFooPool { /* … */ } // in seperated file - -contract FooFacet { - using CallbackManager for *; - uint8 public constant DEX_TYPE = 4; // New dexType - - function swapFoo( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) external returns (uint256 amountOut) { - CallbackManager.arm(pool); // if pool does callback - /// IFooPool(pool).swap ... - CallbackManager.verify(); // if pool does callback - } - - // callback implemented only if pool does callback - function fooSwapCallback(bytes calldata data) external { - // validate msg.sender==pool… - CallbackManager.clear(); - // transfer funds… - } -} - -``` - -**Steps:** - -1. DiamondCut in `FooFacet.swapFoo` and/or `fooSwapCallback`. -2. Register new type: - - ``` - await coreRouteFacet.registerDexType(DEX_TYPE_FOO, FooFacet.swapFoo.selector); - - ``` - - ---- - -### **5.2 Approach 2 (Selector): Onboarding Process** - -### **For Uniswap V3-compatible forks:** - -Each V3 fork typically gets its own facet due to callback differences: - -```solidity -/// DRAFT file for PancakeV3Facet.sol -/// DRAFT file for PancakeV3Facet.sol -/// DRAFT file for PancakeV3Facet.sol - -contract PancakeV3Facet { - function swapPancakeV3( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) external returns (uint256 amountOut) { - // Complete swap implementation (can reuse LibUniV3Logic) - return LibUniV3Logic.executeSwap(...); - } - - function pancakeV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - // PancakeV3-specific callback logic - } -} - -``` - -**Steps:** - -1. Deploy `PancakeV3Facet` -2. DiamondCut in `PancakeV3Facet.swapPancakeV3` and `pancakeV3SwapCallback` -3. Backend immediately uses `PancakeV3Facet.swapPancakeV3.selector` - -### **For completely new DEX:** - -```solidity -/// DRAFT file for FooFacet.sol -/// DRAFT file for FooFacet.sol -/// DRAFT file for FooFacet.sol - -contract FooFacet { - function swapFoo( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) external returns (uint256 amountOut) { - // Complete swap implementation - } - - function fooSwapCallback(bytes calldata data) external { - // Callback logic if needed - } -} - -``` - -**Steps:** - -1. Deploy `FooFacet` -2. DiamondCut in `FooFacet.swapFoo` and/or `fooSwapCallback` -3. Backend immediately uses `FooFacet.swapFoo.selector` - -### **5.3 Comparison: Adding PancakeV3** - -| **Step** | **Approach 1 (Registry)** | **Approach 2 (Selector)** | -| --- | --- | --- | -| **1. Deploy** | Deploy callback-only facet | Deploy complete facet | -| **2. Registration** | No registration (reuses DEX_TYPE_UNIV3) | No registration needed | -| **3. Backend** | Uses existing DEX_TYPE_UNIV3 | Uses PancakeV3Facet.swapPancakeV3.selector | -| **4. Dependencies** | Requires UniV3Facet deployed first | Zero dependencies | -| **5. Code Reuse** | Interface call to UniV3Facet | Library-based code reuse | - ---- - -## **6. Facet & Callback Dependencies** - -### **6.1 Approach 1 (Registry): Dependencies Exist** - -**Problem:** Some facets require other facets to already be cut & registered due to shared dex types. - -**Example:** `PancakeV3Facet` requires `UniV3Facet` because: - -- Both use `DEX_TYPE_UNIV3` -- PancakeV3 forwards calls to UniV3 swap function -- PancakeV3 only provides callback wrapper - -**How to handle:** - -- Track via `deps:` comment at the top of facet source file: - - ```solidity - // deps: UniV3Facet - contract PancakeV3Facet { - // callback-only implementation - } - - ``` - -- Add facet dependency validation to `DeployFacet.s.sol` -- Ensure dependency order in deployment scripts -- Document dependencies in deployment runbook - ---- - -### **6.2 Approach 2 (Selector): Zero Dependencies** - -**Benefit:** Each facet is completely self-contained with no dependencies. - -**Example:** `PancakeV3Facet` is independent because: - -- Has its own unique selector -- Contains complete swap implementation -- No shared state with other facets -- Can be deployed in any order - -**How to handle:** - -- No dependency tracking needed -- Deploy facets in any order -- Each facet includes complete implementation -- Use libraries for code reuse without dependencies - ---- - -## **7. Testing, Deployment & Migration Workflow** - -### **7.1 Approach 1: Registry based Testing** - -### **Directory Structure** - -``` -test/solidity/Lda/ -├── Facets/ -│ ├── LdaTestBase.t.sol # Registry specific base class -│ ├── CoreRouteFacet.t.sol # Registry and dispatch tests -│ ├── FooFacet.t.sol # Foo dex integration tests - -``` - -### **LdaTestBase.t.sol** - -```solidity -/// DRAFT file for LdaTestBase.t.sol -/// DRAFT file for LdaTestBase.t.sol -/// DRAFT file for LdaTestBase.t.sol - -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import { TestBase } from "../utils/TestBase.sol"; -import { LdaDiamond } from "lifi/Lda/LdaDiamond.sol"; -import { CoreRouteFacet } from "lifi/Lda/Facets/CoreRouteFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/** - * @title LdaTestBase - * @notice Abstract base test contract for LDA facet tests - * @dev Provides Diamond setup - */ -abstract contract LdaTestBase is TestBase { - // Core Diamond components - LdaDiamond internal ldaDiamond; - CoreRouteFacet internal coreRouteFacet; - - // Common events - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - - // Common errors - error UnknownDexType(); - error SwapFailed(); - error InvalidCallData(); - - function setUp() public virtual { - initTestBase(); - vm.label(USER_SENDER, "USER_SENDER"); - setupLdaDiamond(); - } - - function setupLdaDiamond() internal { - ldaDiamond = new LdaDiamond(USER_DIAMOND_OWNER); - coreRouteFacet = new CoreRouteFacet(); - - // Add CoreRouteFacet with registry functions - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - bytes4[] memory selectors = new bytes4[](8); - selectors[0] = CoreRouteFacet.processRoute.selector; - selectors[1] = CoreRouteFacet.registerDexType.selector; - selectors[2] = CoreRouteFacet.removeDexType.selector; - selectors[3] = CoreRouteFacet.batchRegisterDexTypes.selector; - selectors[4] = CoreRouteFacet.batchRemoveDexTypes.selector; - selectors[5] = CoreRouteFacet.getSwapSelectorByDex.selector; - selectors[6] = CoreRouteFacet.getDexTypeBySelector.selector; - selectors[7] = CoreRouteFacet.getAllDexTypes.selector; - - cut[0] = LibDiamond.FacetCut({ - facetAddress: address(coreRouteFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectors - }); - - vm.prank(USER_DIAMOND_OWNER); - LibDiamond.diamondCut(cut, address(0), ""); - - coreRouteFacet = CoreRouteFacet(address(ldaDiamond)); - vm.label(address(ldaDiamond), "LdaDiamond"); - vm.label(address(coreRouteFacet), "CoreRouteFacet"); - } - - function addFacetAndRegister( - address facetAddress, - bytes4 swapSelector, - uint8 dexType - ) internal { - // Add facet to Diamond - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = swapSelector; - - cut[0] = LibDiamond.FacetCut({ - facetAddress: facetAddress, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectors - }); - - vm.prank(USER_DIAMOND_OWNER); - LibDiamond.diamondCut(cut, address(0), ""); - - // Register dexType - vm.prank(USER_DIAMOND_OWNER); - coreRouteFacet.registerDexType(dexType, swapSelector); - } - - function buildRoute( - address tokenIn, - uint256 amountIn, - uint8 dexType, - bytes memory poolData - ) internal pure returns (bytes memory) { - return abi.encodePacked( - uint8(2), // processUserERC20 - tokenIn, - uint8(1), // number of pools - uint16(65535), // full share - dexType, - poolData - ); - } - - // Abstract test functions - function test_CanSwap() public virtual; - function test_CanSwap_FromDiamond() public virtual; - function test_CanSwap_MultiHop() public virtual; - function test_FieldValidation() public virtual; - - // DEX type constants - uint8 internal constant DEX_TYPE_UNIV2 = 0; - uint8 internal constant DEX_TYPE_UNIV3 = 1; - uint8 internal constant DEX_TYPE_VELODROME_V2 = 6; - uint8 internal constant DEX_TYPE_ALGEBRA = 7; - uint8 internal constant DEX_TYPE_IZUMI_V3 = 8; - uint8 internal constant DEX_TYPE_SYNCSWAP = 9; -} - -``` - -### **Example FooFacet.t.sol** - -```solidity -/// DRAFT file for FooFacet.t.sol -/// DRAFT file for FooFacet.t.sol -/// DRAFT file for FooFacet.t.sol - -contract FooFacetTest is LdaTestBase { - FooFacet internal fooFacet; - uint8 internal constant DEX_TYPE_FOO = 10; - - function setUp() public override { - super.setUp(); - fooFacet = new FooFacet(); - addFacetAndRegister(address(fooFacet), fooFacet.swapFoo.selector, DEX_TYPE_FOO); - } - - function test_CanSwap() public override { - bytes memory route = buildRoute( - address(tokenA), - 1000e18, - DEX_TYPE_FOO, - abi.encodePacked(address(pool), uint8(1), address(USER_RECEIVER)) - ); - - vm.prank(USER_SENDER); - uint256 amountOut = coreRouteFacet.processRoute( - address(tokenA), 1000e18, address(tokenB), 950e18, USER_RECEIVER, route - ); - - assertGt(amountOut, 950e18); - } - - function test_CanSwap_FromDiamond() public override { - // TODO: - } - - function test_CanSwap_MultiHop() public override { - // TODO: - } - - function test_FieldValidation() public override { - // TODO: - } -} - -``` - -### **Deployment Scripts** - -- `script/deploy/lda/facets/DeployXFacet.s.sol` -- `scriptMaster.sh` — calls `registerDexType(...)` after facet deployment - -### **Migration Path** - -- `script/lda/migrateDexTypes.ts` — registry migration - ---- - -### **7.2 Approach 2: Selector based Testing** - -### **Directory Structure** - -``` -test/solidity/Lda/ -├── Facets/ -│ ├── LdaTestBase.t.sol # Selector specific base class -│ ├── CoreRouteFacet.t.sol # Basic routing tests -│ ├── FooFacet.t.sol # Foo dex integration tests - -``` - -### **LdaTestBase.t.sol** - -```solidity -/// DRAFT file for LdaTestBase.t.sol -/// DRAFT file for LdaTestBase.t.sol -/// DRAFT file for LdaTestBase.t.sol - -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import { TestBase } from "../utils/TestBase.sol"; -import { LdaDiamond } from "lifi/Lda/LdaDiamond.sol"; -import { CoreRouteFacet } from "lifi/Lda/Facets/CoreRouteFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/** - * @title LdaTestBase - * @notice Abstract base test contract - * @dev Provides Diamond setup - */ -abstract contract LdaTestBase is TestBase { - // Core Diamond components - LdaDiamond internal ldaDiamond; - CoreRouteFacet internal coreRouteFacet; - - // Common events - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - - // Common errors - error SwapFailed(); - error InvalidCallData(); - - function setUp() public virtual { - initTestBase(); - vm.label(USER_SENDER, "USER_SENDER"); - setupLdaDiamond(); - } - - function setupLdaDiamond() internal { - ldaDiamond = new LdaDiamond(USER_DIAMOND_OWNER); - coreRouteFacet = new CoreRouteFacet(); - - // Add CoreRouteFacet with only processRoute (no registry functions) - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = CoreRouteFacet.processRoute.selector; - - cut[0] = LibDiamond.FacetCut({ - facetAddress: address(coreRouteFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectors - }); - - vm.prank(USER_DIAMOND_OWNER); - LibDiamond.diamondCut(cut, address(0), ""); - - coreRouteFacet = CoreRouteFacet(address(ldaDiamond)); - vm.label(address(ldaDiamond), "LdaDiamond"); - vm.label(address(coreRouteFacet), "CoreRouteFacet"); - } - - function addFacet(address facetAddress, bytes4 swapSelector) internal { - // Simple facet addition - no registration needed - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = swapSelector; - - cut[0] = LibDiamond.FacetCut({ - facetAddress: facetAddress, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectors - }); - - vm.prank(USER_DIAMOND_OWNER); - LibDiamond.diamondCut(cut, address(0), ""); - } - - function buildRoute( - address tokenIn, - uint256 amountIn, - bytes4 selector, - bytes memory poolData - ) internal pure returns (bytes memory) { - return abi.encodePacked( - uint8(2), // processUserERC20 - tokenIn, - uint8(1), // number of pools - uint16(65535), // full share - selector, - poolData - ); - } - - // Abstract test functions - function test_CanSwap() public virtual; - function test_CanSwap_FromDiamond() public virtual; - function test_CanSwap_MultiHop() public virtual; - function test_FieldValidation() public virtual; - - // Direction constants - uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; - uint8 internal constant DIRECTION_TOKEN1_TO_TOKEN0 = 0; - uint16 internal constant FULL_SHARE = 65535; -} - -``` - -### **Example FooFacet.t.sol** - -```solidity -/// DRAFT file for FooFacet.t.sol -/// DRAFT file for FooFacet.t.sol -/// DRAFT file for FooFacet.t.sol - -contract FooFacetTest is LdaTestBase { - FooFacet internal fooFacet; - - function setUp() public override { - super.setUp(); - fooFacet = new FooFacet(); - addFacet(address(fooFacet), fooFacet.swapFoo.selector); - } - - function test_CanSwap() public override { - bytes memory route = buildSelectorRoute( - address(tokenA), - 1000e18, - fooFacet.swapFoo.selector, - abi.encodePacked(address(pool), uint8(1), address(USER_RECEIVER)) - ); - - vm.prank(USER_SENDER); - uint256 amountOut = coreRouteFacet.processRoute( - address(tokenA), 1000e18, address(tokenB), 950e18, USER_RECEIVER, route - ); - - assertGt(amountOut, 950e18); - } - - function test_CanSwap_FromDiamond() public override { - } - - function test_CanSwap_MultiHop() public override { - } - - function test_FieldValidation() public override { - } -} - -``` - -### **Deployment Scripts** - -- `script/deploy/lda/facets/DeployXFacet.s.sol` -- `scriptMaster.sh` — simple Diamond cut, no registration needed - -### **Migration Path** - -- Backend needs to map every dex to correct facet swap function selector bytes - ---- - -### **7.3 Migration Requirements** - -**Registry Approach (Approach 1):** - -- Smart contract: Deploy facets + register dexTypes -- Backend: No changes needed (keeps existing dexType system) - -**Selector Approach (Approach 2):** - -- Smart contract: Deploy facets (no registration) -- Backend: Update route encoding to use selectors instead of dexTypes - ---- - -## **8. Detailed Approach Analysis** - -### **8.1 Backend Communication Comparison** - -**Current Complexity (OLD/Approach 1):** - -- Backend/Smart contract teams need to understand `poolType/dexType` mappings -- Complex enum/mapping management -- Multiple DEXs share same `poolType/dexType` identifier -- Requires coordination for `poolType/dexType` assignments - -**Simplified Communication (Approach 2):** - -- **Simpler mapping**: Compatible DEXs share selectors, callback-requiring DEXs get unique selectors -- **No enum management**: Direct function selector usage -- **Self-documenting**: `PancakeV3Facet.swapPancakeV3.selector` is clear -- **Reduced coordination**: Deploy facet → get selector → use immediately - -Regarding communication with the backend team, **Approach 2 significantly simplifies coordination**. Currently, we need to communicate which `dexType` each DEX should use, requiring mapping management and potential conflicts. With the selector approach, communication becomes **clearer and more explicit**: compatible DEXs share selectors while callback-requiring DEXs get unique selectors, reducing the need for complex enum management and coordination overhead. - -### **8.2 DEX Grouping by Compatibility** - -**Selector sharing patterns:** - -- **UniV2-compatible DEXs**: All share `UniswapV2StyleFacet.swapUniV2.selector` - - UniswapV2, SushiSwap, PancakeV2, TraderJoe V1, etc. - - Only pool address differs in route data -- **UniV3-compatible DEXs**: Each gets unique selector due to callback differences - - UniV3 → `UniV3Facet.swapUniV3.selector` - - PancakeV3 → `PancakeV3Facet.swapPancakeV3.selector` - - RamsesV2 → `RamsesV2Facet.swapRamsesV2.selector` - - Different callback function names require separate facets -- **Unique protocol DEXs**: Each gets its own selector - - Curve, Balancer, 1inch, etc. - - Different swap interfaces and callback patterns - -## **9. Final Approach Comparison Matrix** - -| **Aspect** | **OLD (Monolithic)** | **Approach 1 (Registry)** | **Approach 2 (Selector)** | **Notes** | -| --- | --- | --- | --- | --- | -| **Backend Changes** | ✅ None (current system) | ✅ None (keeps dexType) | ❌ Requires route encoding update | One-time migration to selector based routing | -| **User Gas Cost** | ~50-200 gas (growing) | ~2,100 gas (constant) | ~20 gas (constant) | | -| **DEX Integration** | ❌ Update CoreRouteFacet | ✅ Register dexType | ✅ Deploy and use | | -| **Backend Changes** | N/A | ✅ None (keep dexType system) | ✅ Minor (selector mapping) | | -| **Scalability** | ❌ Limited by contract size | ✅ 255 DEXs (uint8) | ✅ 4 billion DEXs (bytes4) | | -| **Deployment Complexity** | ❌ High | ❌ Medium (dependencies) | ✅ Minimal (independent) | | -| **Deployment Order** | N/A | ❌ Must follow dependency order | ✅ Any order | | -| **Facet Dependencies** | N/A | ❌ Hard dependencies exist | ✅ Zero dependencies | | -| **Single Point of Failure** | ❌ Monolithic contract | ❌ Registry corruption | ✅ No central registry | | -| **Gas Predictability** | ❌ Increases with DEXs | ✅ Constant | ✅ Constant | | -| **Code Reuse** | N/A | ✅ High (shared swap functions) | ✅ Medium (via libraries) | | -| **Test Setup Complexity** | ❌ Massive test suite | ❌ Higher (registry setup) | ✅ Lower (direct facet addition) | | -| **Test Isolation** | ❌ Monolithic coupling | ❌ Medium (shared registry) | ✅ High (independent facets) | | -| **Maintenance** | ❌ Fragile monolith | ❌ Fragile interdependencies | ✅ Clear separation | | -| **Upgrade Safety** | ❌ Monolithic failure | ❌ Cascade failures possible | ✅ Isolated failures | | -| **Bytecode Size** | ❌ Massive monolith | ✅ Smaller (less duplication) | ❌ Larger (each facet complete) | with selector approach its larger but still great fit for facet | - -> Note: -> -> -> Approach 1 (Registry-based) was my **initial approach**, which is why I've kept it in the documentation — just to let you know it's still a valid and fully working option. It allows us to onboard new DEXs **without requiring any changes on the backend**, since the backend can continue using the existing `dexType` field. That said, we'd need to ask the LDA backend team whether they're capable and willing to switch to using function selectors (required by Approach 2). -> -> Personally, I recommend Approach 2 going forward. It reduces backend coordination, scales better, and is more explicit and maintainable. You simply deploy a new facet, use its selector directly, and avoid all shared-state registry management or dependency tracking. -> - ---- - -# **Part II: Development Standards & Tooling (common for both approaches)** - -## **10. Callback Handling: From `lastCalledPool` → `CallbackManager`** - -OLD: - -```solidity -lastCalledPool = pool; // in swap function -... -require(msg.sender == lastCalledPool); // in callback function - -``` - -**NEW:** - -We now use the **`CallbackManager` library**, which stores the expected sender in diamond-safe storage. - -**Step-by-step usage:** - -1. **Arm** the callback guard in the swap function (before external call) -2. **Verify** or use the `onlyExpectedCallback` modifier at the beginning of the callback -3. **Clear** the state after validation (modifier handles it automatically) - -**`CallbackManager.sol`** (in `/Libraries`): - -```solidity - -// DRAFT file for LibCallbackManager.sol -// DRAFT file for LibCallbackManager.sol -// DRAFT file for LibCallbackManager.sol - -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -error UnexpectedCallbackSender(address actual, address expected); - -library LibCallbackManager { - bytes32 internal constant NAMESPACE = keccak256("com.lifi.lda.callbackmanager"); - - struct Data { - address expected; - } - - function data() internal pure returns (Data storage d) { - bytes32 p = NAMESPACE; - assembly { - d.slot := p - } - } - - /// @notice Arm the guard with expected pool - function arm(address expectedCallbackSender) internal { - data().expected = expectedCallbackSender; - } - - /// @notice Clear the guard (called inside the callback) - function clear() internal { - data().expected = address(0); - } - - /// @notice Check that callback comes from expected address - function verifyCallbackSender() internal view { - address expected = data().expected; - if (msg.sender != expected) { - revert UnexpectedCallbackSender(msg.sender, expected); - } - } - - /// @dev Wraps a callback with verify + clear. To use with `using CallbackManager for *`. - modifier onlyExpectedCallback() { - verifyCallbackSender(); - _; - clear(); - } -} - -``` - -Example usage: - -```solidity -// DRAFT file for FooFacet.sol -// DRAFT file for FooFacet.sol -// DRAFT file for FooFacet.sol - -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; - -contract FooFacet { - using LibCallbackManager for *; - - function swapFoo( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) external returns (uint256 amountOut) { - address pool = decodePoolFromStream(stream); - - // arm callback guard - LibCallbackManager.arm(pool); - - // call to external pool - IFooPool(pool).swap(tokenIn, amountIn, ...); - - // actual result will be handled via callback - } - - /// @notice Callback triggered by FooPool - function fooSwapCallback(bytes calldata data) external LibCallbackManager.onlyExpectedCallback { - // sender verified + cleared automatically - - // Perform balance check, token transfer, emit event, etc. - } -} - -``` - -## **11. Release Checklist** - -Release checklist same based on [New Facet Contract Checklist](https://www.notion.so/New-Facet-Contract-Checklist-157f0ff14ac78095a2b8f999d655622e?pvs=21) - -## **12. Conventions & Cleanup** - -- all `DEX_TYPE` constants in a shared `DexTypes.sol` (Registry approach) -- use `LibAsset` for transfers -- update [`conventions.md`](http://conventions.md/) accordingly - -## **13. Code Generation** - -We use `plop` to scaffold new facet modules with all required boilerplate: - -```bash -bun run plop facet -``` - -You will be prompted with: - -``` -? What kind of facet do you want to generate? -> [1] Main Diamond (e.g. LiFiDiamond) -> [2] LDA Diamond (e.g. LDALiFiDiamond) -? Which approach are you using? -> [1] Registry-based (Approach 1) -> [2] Selector-based (Approach 2) - -``` - -### **Plop Output:** - -- **Path:** `src/Lda/Facets/FooFacet.sol` -- **Test:** `test/solidity/Lda/Facets/FooFacet.t.sol` (extends `LdaTestBase`) -- **Deploy script:** `script/deploy/lda/facets/DeployFooFacet.s.sol` -- **Update script:** `script/deploy/lda/facets/UpdateFooFacet.s.sol` -- **Docs:** `docs/lda/FooFacet.md` - -**Questions:**  - -Can we deprecate bento? \ No newline at end of file diff --git a/src/Interfaces/ICurve.sol b/src/Interfaces/ICurve.sol index a972bffc4..56482aa52 100644 --- a/src/Interfaces/ICurve.sol +++ b/src/Interfaces/ICurve.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; /// @title Interface for Curve /// @author LI.FI (https://li.fi) +/// @notice Minimal Curve pool interface for exchange operations /// @custom:version 1.0.0 interface ICurve { function exchange( diff --git a/src/Interfaces/ICurveLegacy.sol b/src/Interfaces/ICurveLegacy.sol index 7b418b193..2b7f4bb9e 100644 --- a/src/Interfaces/ICurveLegacy.sol +++ b/src/Interfaces/ICurveLegacy.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; /// @title Interface for Curve /// @author LI.FI (https://li.fi) +/// @notice Minimal legacy Curve pool interface for exchange operations /// @custom:version 1.0.0 interface ICurveLegacy { function exchange( diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 07280ea50..ff6eaa372 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -18,7 +18,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ProcessUserERC20, // 2 - processUserERC20 (User's funds) ProcessNative, // 3 - processNative ProcessOnePool, // 4 - processOnePool (Pool's funds) - ApplyPermit // 6 - applyPermit + ApplyPermit // 5 - applyPermit } struct ExpectedEvent { diff --git a/test/solidity/utils/MockNoCallbackPool.sol b/test/solidity/utils/MockNoCallbackPool.sol index 85128a110..f3afe1e46 100644 --- a/test/solidity/utils/MockNoCallbackPool.sol +++ b/test/solidity/utils/MockNoCallbackPool.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; From 94f47252b36ab7c0f69e88eb6d1be37d09269473 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 21 Aug 2025 17:42:45 +0200 Subject: [PATCH 050/220] Update SPDX license in ReentrancyGuard, add new error handling in Errors.sol, and refactor imports in various facets to use the new error. Exclude interface files from solhint checks for improved linting. --- .solhint.json | 1 + src/Errors/GenericErrors.sol | 1 - src/Helpers/ReentrancyGuard.sol | 2 +- src/Interfaces/ICurve.sol | 1 - src/Periphery/Lda/Errors/Errors.sol | 5 +++++ src/Periphery/Lda/Facets/AlgebraFacet.sol | 2 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 2 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 3 ++- test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 2 +- 10 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 src/Periphery/Lda/Errors/Errors.sol diff --git a/.solhint.json b/.solhint.json index c0c4e08e7..b370ae52f 100644 --- a/.solhint.json +++ b/.solhint.json @@ -1,6 +1,7 @@ { "extends": "solhint:recommended", "plugins": ["prettier"], + "excludedFiles": ["**/src/Interfaces/**/*.sol"], "rules": { "avoid-call-value": "off", "avoid-low-level-calls": "off", diff --git a/src/Errors/GenericErrors.sol b/src/Errors/GenericErrors.sol index 22358da68..cfa9127c5 100644 --- a/src/Errors/GenericErrors.sol +++ b/src/Errors/GenericErrors.sol @@ -40,4 +40,3 @@ error UnAuthorized(); error UnsupportedChainId(uint256 chainId); error WithdrawFailed(); error ZeroAmount(); -error SwapCallbackNotExecuted(); diff --git a/src/Helpers/ReentrancyGuard.sol b/src/Helpers/ReentrancyGuard.sol index 32585e0c2..af5f87558 100644 --- a/src/Helpers/ReentrancyGuard.sol +++ b/src/Helpers/ReentrancyGuard.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; /// @title Reentrancy Guard diff --git a/src/Interfaces/ICurve.sol b/src/Interfaces/ICurve.sol index 56482aa52..097036166 100644 --- a/src/Interfaces/ICurve.sol +++ b/src/Interfaces/ICurve.sol @@ -10,7 +10,6 @@ interface ICurve { int128 i, int128 j, uint256 dx, - // solhint-disable-next-line var-name-mixedcase uint256 min_dy ) external payable returns (uint256); } diff --git a/src/Periphery/Lda/Errors/Errors.sol b/src/Periphery/Lda/Errors/Errors.sol new file mode 100644 index 000000000..1aec7728b --- /dev/null +++ b/src/Periphery/Lda/Errors/Errors.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: LGPL-3.0-only +/// @custom:version 1.0.0 +pragma solidity ^0.8.17; + +error SwapCallbackNotExecuted(); diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 9b0a963d6..d261960ef 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -8,7 +8,7 @@ import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title AlgebraFacet diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index b7ba3aa05..c2755ac98 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -7,7 +7,7 @@ import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; -import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; /// @title IzumiV3Facet /// @author LI.FI (https://li.fi) diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 08ed21b58..720c57a8b 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -6,7 +6,8 @@ import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; -import { InvalidCallData, SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title UniV3StyleFacet diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index 20676c14f..e60a2bc72 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; -import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index dca403794..8aff21238 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -8,7 +8,7 @@ import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Errors/GenericErrors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; From 5f6d8d4177874969e81931fc6751f5a4c0ff3e69 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 21 Aug 2025 17:44:09 +0200 Subject: [PATCH 051/220] Update version annotation in LibAsset to 2.1.2 and change transferNativeAsset function visibility from private to internal for improved accessibility --- src/Libraries/LibAsset.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/LibAsset.sol b/src/Libraries/LibAsset.sol index 7b943a09d..ffe844ca1 100644 --- a/src/Libraries/LibAsset.sol +++ b/src/Libraries/LibAsset.sol @@ -9,7 +9,7 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { InvalidReceiver, NullAddrIsNotAValidSpender, InvalidAmount, NullAddrIsNotAnERC20Token } from "../Errors/GenericErrors.sol"; /// @title LibAsset -/// @custom:version 2.1.1 +/// @custom:version 2.1.2 /// @author LI.FI (https://li.fi) /// @notice This library contains helpers for dealing with onchain transfers /// of assets, including accounting for the native asset `assetId` @@ -61,7 +61,7 @@ library LibAsset { function transferNativeAsset( address payable recipient, uint256 amount - ) private { + ) internal { // make sure a meaningful receiver address was provided if (recipient == NULL_ADDRESS) revert InvalidReceiver(); From 09009714bcad5d7b56ab52f330d9455193b95b87 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 21 Aug 2025 17:49:23 +0200 Subject: [PATCH 052/220] Rename `clear` function to `disarm` in LibCallbackManager for improved clarity and update references in AlgebraFacet, IzumiV3Facet, and UniV3StyleFacet accordingly --- src/Libraries/LibCallbackManager.sol | 4 ++-- src/Periphery/Lda/Facets/AlgebraFacet.sol | 2 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 2 +- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Libraries/LibCallbackManager.sol b/src/Libraries/LibCallbackManager.sol index fb98fbc6f..e5d461173 100644 --- a/src/Libraries/LibCallbackManager.sol +++ b/src/Libraries/LibCallbackManager.sol @@ -37,8 +37,8 @@ library LibCallbackManager { callbackStorage().expected = expectedCallbackSender; } - /// @notice Clear the guard (called inside the callback) - function clear() internal { + /// @notice Disarm the guard (called inside the callback) + function disarm() internal { callbackStorage().expected = address(0); } diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index d261960ef..f629742ea 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -100,6 +100,6 @@ contract AlgebraFacet is BaseRouteConstants { ) external { LibCallbackManager.verifyCallbackSender(); LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.clear(); + LibCallbackManager.disarm(); } } diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index c2755ac98..426b62db1 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -129,6 +129,6 @@ contract IzumiV3Facet is BaseRouteConstants { address tokenIn = abi.decode(data, (address)); LibAsset.transferERC20(tokenIn, msg.sender, amountToPay); - LibCallbackManager.clear(); + LibCallbackManager.disarm(); } } diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 720c57a8b..467d88df6 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -34,7 +34,7 @@ contract UniV3StyleFacet is BaseRouteConstants { modifier onlyExpectedPool() { LibCallbackManager.verifyCallbackSender(); _; - LibCallbackManager.clear(); + LibCallbackManager.disarm(); } // ==== External Functions ==== From 42d65fe8bde06d5a8bf4149c7e3d414ac152a686 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 10:51:23 +0200 Subject: [PATCH 053/220] Integrate LibAsset for ERC20 transfers in LibUniV3Logic, replacing direct IERC20 calls for improved modularity and maintainability --- src/Libraries/LibUniV3Logic.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Libraries/LibUniV3Logic.sol b/src/Libraries/LibUniV3Logic.sol index 516745e9e..e89d2aff0 100644 --- a/src/Libraries/LibUniV3Logic.sol +++ b/src/Libraries/LibUniV3Logic.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.17; import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { LibAsset } from "./LibAsset.sol"; /// @title UniV3 Logic Library /// @author LI.FI (https://li.fi) @@ -25,6 +26,6 @@ library LibUniV3Logic { } address tokenIn = abi.decode(data, (address)); - IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount)); + LibAsset.transferERC20(tokenIn, msg.sender, uint256(amount)); } } From 0c18410bc54d9f78d863360182dbe4bfacdc162b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 10:52:37 +0200 Subject: [PATCH 054/220] fixed test_OwnerCanInitializeFacet --- test/solidity/Facets/HopFacet.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solidity/Facets/HopFacet.t.sol b/test/solidity/Facets/HopFacet.t.sol index 0b7fe4042..392ec2495 100644 --- a/test/solidity/Facets/HopFacet.t.sol +++ b/test/solidity/Facets/HopFacet.t.sol @@ -247,7 +247,7 @@ contract HopFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(hopFacet2), functionSelectors); + addFacet(diamond2, address(hopFacet2), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](3); configs[0] = HopFacet.Config(ADDRESS_USDC, USDC_BRIDGE); From 80e51701ed826dfdecdcc6b2dee57edd7c1d2c62 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 11:40:28 +0200 Subject: [PATCH 055/220] Add LibCallbackAuthenticator for callback management. Moved modifier to parent contract --- ...nager.sol => LibCallbackAuthenticator.sol} | 4 +-- src/Periphery/Lda/Facets/AlgebraFacet.sol | 19 ++++++------- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 23 ++++++++-------- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 27 +++++++------------ .../Lda/PoolCallbackAuthenticated.sol | 15 +++++++++++ .../Periphery/Lda/BaseCoreRouteTest.t.sol | 2 +- .../Lda/BaseDexFacetWithCallback.t.sol | 8 +++--- .../Lda/BaseUniV3StyleDexFacet.t.sol | 2 +- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 4 +-- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 2 +- .../Lda/Facets/EnosysDexV3Facet.t.sol | 2 +- .../Lda/Facets/HyperswapV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 4 +-- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 2 +- .../Lda/Facets/SyncSwapV2Facet.t.sol | 2 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 2 +- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 2 +- 18 files changed, 66 insertions(+), 58 deletions(-) rename src/Libraries/{LibCallbackManager.sol => LibCallbackAuthenticator.sol} (94%) create mode 100644 src/Periphery/Lda/PoolCallbackAuthenticated.sol diff --git a/src/Libraries/LibCallbackManager.sol b/src/Libraries/LibCallbackAuthenticator.sol similarity index 94% rename from src/Libraries/LibCallbackManager.sol rename to src/Libraries/LibCallbackAuthenticator.sol index e5d461173..03dc6edf4 100644 --- a/src/Libraries/LibCallbackManager.sol +++ b/src/Libraries/LibCallbackAuthenticator.sol @@ -5,10 +5,10 @@ pragma solidity ^0.8.17; /// @author LI.FI (https://li.fi) /// @notice Provides functionality for managing callback validation in diamond-safe storage /// @custom:version 1.0.0 -library LibCallbackManager { +library LibCallbackAuthenticator { /// Types /// bytes32 internal constant NAMESPACE = - keccak256("com.lifi.lda.callbackmanager"); + keccak256("com.lifi.lda.callbackauthenticator"); /// Storage /// struct CallbackStorage { diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index f629742ea..b1bdd19fe 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -4,18 +4,19 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title AlgebraFacet /// @author LI.FI (https://li.fi) /// @notice Handles Algebra swaps with callback management /// @custom:version 1.0.0 -contract AlgebraFacet is BaseRouteConstants { +contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticated { using LibPackedStream for uint256; using SafeERC20 for IERC20; @@ -58,7 +59,7 @@ contract AlgebraFacet is BaseRouteConstants { ); } - LibCallbackManager.arm(pool); + LibCallbackAuthenticator.arm(pool); if (supportsFeeOnTransfer) { IAlgebraPool(pool).swapSupportingFeeOnInputTokens( @@ -79,14 +80,16 @@ contract AlgebraFacet is BaseRouteConstants { ); } - if (LibCallbackManager.callbackStorage().expected != address(0)) { + if ( + LibCallbackAuthenticator.callbackStorage().expected != address(0) + ) { revert SwapCallbackNotExecuted(); } } /// @notice Called by Algebra pool after executing a swap via IAlgebraPool#swap /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method must be verified to be an AlgebraPool using LibCallbackManager. + /// The caller of this method must be verified to be an AlgebraPool using LibCallbackAuthenticator. /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. @@ -97,9 +100,7 @@ contract AlgebraFacet is BaseRouteConstants { int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) external { - LibCallbackManager.verifyCallbackSender(); + ) external onlyExpectedPool { LibUniV3Logic.handleCallback(amount0Delta, amount1Delta, data); - LibCallbackManager.disarm(); } } diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 426b62db1..51446a049 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -3,19 +3,20 @@ pragma solidity ^0.8.17; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; /// @title IzumiV3Facet /// @author LI.FI (https://li.fi) /// @notice Handles IzumiV3 swaps with callback management /// @custom:version 1.0.0 -contract IzumiV3Facet is BaseRouteConstants { +contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticated { using LibPackedStream for uint256; - using LibCallbackManager for *; + using LibCallbackAuthenticator for *; // ==== Constants ==== /// @dev Minimum point boundary for iZiSwap pool price range @@ -61,7 +62,7 @@ contract IzumiV3Facet is BaseRouteConstants { ); } - LibCallbackManager.arm(pool); + LibCallbackAuthenticator.arm(pool); if (direction) { IiZiSwapPool(pool).swapX2Y( @@ -82,7 +83,9 @@ contract IzumiV3Facet is BaseRouteConstants { // After the swapX2Y or swapY2X call, the callback should clear the registered pool // If it hasn't, it means the callback either didn't happen, was incorrect, or the pool misbehaved // so we revert to protect against misuse or faulty integrations - if (LibCallbackManager.callbackStorage().expected != address(0)) { + if ( + LibCallbackAuthenticator.callbackStorage().expected != address(0) + ) { revert SwapCallbackNotExecuted(); } } @@ -96,7 +99,7 @@ contract IzumiV3Facet is BaseRouteConstants { uint256 amountX, uint256, bytes calldata data - ) external { + ) external onlyExpectedPool { _handleIzumiV3SwapCallback(amountX, data); } @@ -108,7 +111,7 @@ contract IzumiV3Facet is BaseRouteConstants { uint256, uint256 amountY, bytes calldata data - ) external { + ) external onlyExpectedPool { _handleIzumiV3SwapCallback(amountY, data); } @@ -120,15 +123,11 @@ contract IzumiV3Facet is BaseRouteConstants { uint256 amountToPay, bytes calldata data ) private { - LibCallbackManager.verifyCallbackSender(); - if (amountToPay == 0) { revert IzumiV3SwapCallbackNotPositiveAmount(); } address tokenIn = abi.decode(data, (address)); LibAsset.transferERC20(tokenIn, msg.sender, amountToPay); - - LibCallbackManager.disarm(); } } diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 467d88df6..4dadad3b9 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -3,19 +3,20 @@ pragma solidity ^0.8.17; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; +import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title UniV3StyleFacet /// @author LI.FI (https://li.fi) /// @notice Handles Uniswap V3 style swaps with callback verification /// @custom:version 1.0.0 -contract UniV3StyleFacet is BaseRouteConstants { - using LibCallbackManager for *; +contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticated { + using LibCallbackAuthenticator for *; using LibPackedStream for uint256; // ==== Constants ==== @@ -25,18 +26,6 @@ contract UniV3StyleFacet is BaseRouteConstants { uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - // ==== Errors ==== - /// @dev Thrown when callback verification fails or unexpected callback state - error UniV3SwapUnexpected(); - - // ==== Modifiers ==== - /// @dev Ensures callback is from expected pool and cleans up after callback - modifier onlyExpectedPool() { - LibCallbackManager.verifyCallbackSender(); - _; - LibCallbackManager.disarm(); - } - // ==== External Functions ==== /// @notice Executes a swap through a UniV3-style pool /// @dev Handles token transfers and manages callback verification @@ -71,7 +60,7 @@ contract UniV3StyleFacet is BaseRouteConstants { } // Arm callback protection - LibCallbackManager.arm(pool); + LibCallbackAuthenticator.arm(pool); // Execute swap IUniV3StylePool(pool).swap( @@ -83,7 +72,9 @@ contract UniV3StyleFacet is BaseRouteConstants { ); // Verify callback was called (arm should be cleared by callback) - if (LibCallbackManager.callbackStorage().expected != address(0)) { + if ( + LibCallbackAuthenticator.callbackStorage().expected != address(0) + ) { revert SwapCallbackNotExecuted(); } } diff --git a/src/Periphery/Lda/PoolCallbackAuthenticated.sol b/src/Periphery/Lda/PoolCallbackAuthenticated.sol new file mode 100644 index 000000000..81a1c167b --- /dev/null +++ b/src/Periphery/Lda/PoolCallbackAuthenticated.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; + +abstract contract PoolCallbackAuthenticated { + using LibCallbackAuthenticator for *; + + /// @dev Ensures callback is from expected pool and cleans up after callback + modifier onlyExpectedPool() { + LibCallbackAuthenticator.verifyCallbackSender(); + _; + LibCallbackAuthenticator.disarm(); + } +} diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index ff6eaa372..5b7781b0b 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index e60a2bc72..a7862f5d8 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LibCallbackManager } from "lifi/Libraries/LibCallbackManager.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; @@ -25,7 +25,9 @@ abstract contract BaseDexFacetWithCallbackTest is BaseDexFacetTest { { // No swap has armed the guard; expected == address(0) vm.startPrank(USER_SENDER); - vm.expectRevert(LibCallbackManager.UnexpectedCallbackSender.selector); + vm.expectRevert( + LibCallbackAuthenticator.UnexpectedCallbackSender.selector + ); (bool ok, ) = address(ldaDiamond).call( abi.encodeWithSelector( _getCallbackSelector(), diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 97ab54939..3a2664b7c 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { BaseDexFacetWithCallbackTest } from "./BaseDexFacetWithCallback.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 8aff21238..fc9f14a9a 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -6,9 +6,9 @@ import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; +import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 657ced313..93f047722 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { MockPullERC20Facet } from "../../../utils/MockPullERC20Facet.sol"; import { MockNativeFacet } from "../../../utils/MockNativeFacet.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 1b4e2deaa..de6c6a1b5 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 545b443c8..a2c62af2a 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index ac0079340..017ea4120 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; +import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; @@ -253,7 +253,7 @@ contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { vm.store( address(ldaDiamond), - keccak256("com.lifi.lda.callbackmanager"), + keccak256("com.lifi.lda.callbackauthenticator"), bytes32(uint256(uint160(poolInMid))) ); diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index cb298e8c8..e22d89fa4 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 03111495f..c30911451 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 5f74c3efb..0529039b8 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; -import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; contract SyncSwapV2FacetTest is BaseDexFacetTest { diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 0502758c3..71ebe6486 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -6,7 +6,7 @@ import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 1266c7647..2a914c504 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { From 1494fd326b650ae9b554c1c374bb3fb8d434c99d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 12:15:44 +0200 Subject: [PATCH 056/220] Add constructor documentation and improve comments for native asset handling in CoreRouteFacet.sol --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 81a6eef2a..5fec43cde 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -44,6 +44,8 @@ contract CoreRouteFacet is error SwapFailed(); error UnknownSelector(); + /// @notice Constructor + /// @param _owner The address of the contract owner constructor(address _owner) WithdrawablePeriphery(_owner) { if (_owner == address(0)) revert InvalidConfig(); } @@ -94,6 +96,9 @@ contract CoreRouteFacet is address to, bytes calldata route ) private returns (uint256 amountOut) { + // For native assets (ETH), we skip balance tracking since: + // 1. ETH balance checks would be misleading due to gas costs, so calling address(to).balance at the end of the route is not reliable + // 2. Native asset handling is done via _handleNative which consumes all ETH on the contract uint256 balInInitial = LibAsset.isNativeAsset(tokenIn) ? 0 : IERC20(tokenIn).balanceOf(msg.sender); From e3b7b70c6a5b96402f1a56e358909458ca027af5 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 13:00:49 +0200 Subject: [PATCH 057/220] added dev description for _runRoute --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 71 ++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 5fec43cde..b198d1f53 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -140,11 +140,78 @@ contract CoreRouteFacet is } /// @notice Interprets and executes commands from the route byte stream - /// @dev Processes commands in sequence: ERC20, native, permits, and pool interactions + /// @dev A route is a packed byte stream of sequential commands. Each command begins with a 1-byte opcode: + /// - 1 = HandleSelfERC20 (tokens already on this contract) + /// - 2 = HandleUserERC20 (tokens from user) + /// - 3 = HandleNative (ETH held by this contract) + /// - 4 = HandleSinglePool (tokens already in pool) + /// - 5 = ApplyPermit (EIP-2612 permit for tokenIn on behalf of msg.sender) + /// + /// Stream formats per opcode: + /// 1. HandleSelfERC20: + /// [1][token: address][n: uint8] then n legs, each: + /// [share: uint16][len: uint16][data: bytes] + /// total = IERC20(token).balanceOf(address(this)) minus 1 wei (undrain protection) + /// from = address(this), tokenIn = token + /// + /// 2. HandleUserERC20: + /// [2][token: address][n: uint8] then n legs, each: + /// [share: uint16][len: uint16][data: bytes] + /// total = declaredAmountIn + /// from = msg.sender, tokenIn = token + /// + /// 3. HandleNative: + /// [3][n: uint8] then n legs, each: + /// [share: uint16][len: uint16][data: bytes] + /// total = address(this).balance (includes msg.value and any residual ETH) + /// from = address(this), tokenIn = INTERNAL_INPUT_SOURCE + /// + /// 4. HandleSinglePool: + /// [4][token: address][len: uint16][data: bytes] + /// amountIn = 0 (pool sources tokens internally), from = INTERNAL_INPUT_SOURCE + /// + /// 5. ApplyPermit: + /// [5][value: uint256][deadline: uint256][v: uint8][r: bytes32][s: bytes32] + /// Calls permit on tokenIn for msg.sender → address(this). No swap occurs. + /// + /// Leg data encoding: + /// Each leg's data field contains [selector (4 bytes) | payload (bytes)]. + /// The selector determines the DEX facet function to call. The router delegatecalls the facet with: + /// (bytes swapData, address from, address tokenIn, uint256 amountIn) + /// where swapData is the payload from the route, containing DEX-specific data: + /// - Example for UniV3-style: abi.encode(pool, direction, recipient) + /// - Each DEX facet defines its own payload format based on what its pools need + /// + /// Example multihop route (two legs on user ERC20, then single-pool hop): + /// ``` + /// // Leg payloads with facet selectors: + /// leg1 = abi.encodePacked( + /// UniV3StyleFacet.swapUniV3.selector, + /// abi.encode(poolA, DIRECTION_TOKEN0_TO_TOKEN1, poolC) // recipient is the final pool + /// ); + /// leg2 = abi.encodePacked( + /// IzumiV3Facet.swapIzumiV3.selector, + /// abi.encode(poolB, DIRECTION_TOKEN0_TO_TOKEN1, poolC) // recipient is the final pool + /// ); + /// leg3 = abi.encodePacked( + /// SomePoolFacet.swapSinglePool.selector, + /// abi.encode(poolC, finalRecipient, otherPoolParams) // pool that received tokens from leg1&2 + /// ); + /// + /// // Full route: [2][tokenA][2 legs][60% leg1][40% leg2] then [4][tokenB][leg3] + /// route = abi.encodePacked( + /// uint8(2), tokenA, uint8(2), + /// uint16(39321), uint16(leg1.length), leg1, // ~60% of amountIn + /// uint16(26214), uint16(leg2.length), leg2, // ~40% of amountIn + /// uint8(4), tokenB, + /// uint16(leg3.length), leg3 + /// ); + /// ``` /// @param tokenIn The input token address /// @param declaredAmountIn The declared input amount /// @param route The encoded route data - /// @return realAmountIn The actual amount used in the first hop + /// @return realAmountIn The actual amount used in the first hop. For opcode 1: contract balance minus 1, + /// for opcode 3: contract's ETH balance, for opcode 2: equals declaredAmountIn function _runRoute( address tokenIn, uint256 declaredAmountIn, From 7da28f487f112b0e3f41b20771a87594c0b7309b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 13:50:46 +0200 Subject: [PATCH 058/220] Refactor _executeRoute and balance handling in CoreRouteFacet.sol for improved clarity and efficiency. Introduced helper functions _getInitialBalances and _getFinalBalancesAndCheck --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 95 ++++++++++++++++----- 1 file changed, 73 insertions(+), 22 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index b198d1f53..cbdd251cf 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -96,20 +96,81 @@ contract CoreRouteFacet is address to, bytes calldata route ) private returns (uint256 amountOut) { - // For native assets (ETH), we skip balance tracking since: - // 1. ETH balance checks would be misleading due to gas costs, so calling address(to).balance at the end of the route is not reliable - // 2. Native asset handling is done via _handleNative which consumes all ETH on the contract - uint256 balInInitial = LibAsset.isNativeAsset(tokenIn) - ? 0 - : IERC20(tokenIn).balanceOf(msg.sender); + bool isNativeIn = LibAsset.isNativeAsset(tokenIn); + bool isNativeOut = LibAsset.isNativeAsset(tokenOut); - uint256 balOutInitial = LibAsset.isNativeAsset(tokenOut) - ? address(to).balance - : IERC20(tokenOut).balanceOf(to); + // Get initial token balances, with special handling for native assets + (uint256 balInInitial, uint256 balOutInitial) = _getInitialBalances( + tokenIn, + tokenOut, + to, + isNativeIn, + isNativeOut + ); + // Execute the route and get actual input amount used (may differ from amountIn for some opcodes) uint256 realAmountIn = _runRoute(tokenIn, amountIn, route); - uint256 balInFinal = LibAsset.isNativeAsset(tokenIn) + // Verify balances after route execution and calculate output amount + amountOut = _getFinalBalancesAndCheck( + tokenIn, + amountIn, + balInInitial, + tokenOut, + amountOutMin, + balOutInitial, + to, + isNativeIn, + isNativeOut + ); + + emit Route( + msg.sender, + to, + tokenIn, + tokenOut, + realAmountIn, + amountOutMin, + amountOut + ); + } + + /// @notice Gets initial balances for both input and output tokens before route execution + /// @dev For native input assets (ETH), we return 0 since: + /// 1. ETH balance checks would be misleading due to gas costs + /// 2. Native asset handling is done via _handleNative which consumes all ETH on the contract + /// @param tokenIn The input token address + /// @param tokenOut The output token address + /// @param to The recipient address for output tokens + /// @param isNativeIn Whether input token is native ETH + /// @param isNativeOut Whether output token is native ETH + /// @return balInInitial Initial balance of input token + /// @return balOutInitial Initial balance of output token + function _getInitialBalances( + address tokenIn, + address tokenOut, + address to, + bool isNativeIn, + bool isNativeOut + ) private view returns (uint256 balInInitial, uint256 balOutInitial) { + balInInitial = isNativeIn ? 0 : IERC20(tokenIn).balanceOf(msg.sender); + balOutInitial = isNativeOut + ? address(to).balance + : IERC20(tokenOut).balanceOf(to); + } + + function _getFinalBalancesAndCheck( + address tokenIn, + uint256 amountIn, + uint256 balInInitial, + address tokenOut, + uint256 amountOutMin, + uint256 balOutInitial, + address to, + bool isNativeIn, + bool isNativeOut + ) private view returns (uint256 amountOut) { + uint256 balInFinal = isNativeIn ? 0 : IERC20(tokenIn).balanceOf(msg.sender); if (balInFinal + amountIn < balInInitial) { @@ -119,7 +180,7 @@ contract CoreRouteFacet is ); } - uint256 balOutFinal = LibAsset.isNativeAsset(tokenOut) + uint256 balOutFinal = isNativeOut ? address(to).balance : IERC20(tokenOut).balanceOf(to); if (balOutFinal < balOutInitial + amountOutMin) { @@ -127,16 +188,6 @@ contract CoreRouteFacet is } amountOut = balOutFinal - balOutInitial; - - emit Route( - msg.sender, - to, - tokenIn, - tokenOut, - realAmountIn, - amountOutMin, - amountOut - ); } /// @notice Interprets and executes commands from the route byte stream @@ -179,7 +230,7 @@ contract CoreRouteFacet is /// The selector determines the DEX facet function to call. The router delegatecalls the facet with: /// (bytes swapData, address from, address tokenIn, uint256 amountIn) /// where swapData is the payload from the route, containing DEX-specific data: - /// - Example for UniV3-style: abi.encode(pool, direction, recipient) + /// - Example for UniV3-style: abi.encode(pool, direction, recipient) // for Uniswap V3, PancakeV3, etc. /// - Each DEX facet defines its own payload format based on what its pools need /// /// Example multihop route (two legs on user ERC20, then single-pool hop): From 4bf7e9eede7657d69c0e77d1ae06964fe96a05e5 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 13:59:41 +0200 Subject: [PATCH 059/220] Renamed errors for clarity: MinimalInputBalanceViolation to TokenInSpendingExceeded and MinimalOutputBalanceViolation to TokenOutAmountTooLow --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 8 ++++---- .../solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index cbdd251cf..4054e0209 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -38,8 +38,8 @@ contract CoreRouteFacet is ); // ==== Errors ==== - error MinimalOutputBalanceViolation(uint256 amountOut); - error MinimalInputBalanceViolation(uint256 available, uint256 required); + error TokenInSpendingExceeded(uint256 actualSpent, uint256 expectedSpent); + error TokenOutAmountTooLow(uint256 actualOutput); error UnknownCommandCode(); error SwapFailed(); error UnknownSelector(); @@ -174,7 +174,7 @@ contract CoreRouteFacet is ? 0 : IERC20(tokenIn).balanceOf(msg.sender); if (balInFinal + amountIn < balInInitial) { - revert MinimalInputBalanceViolation( + revert TokenInSpendingExceeded( balInFinal + amountIn, balInInitial ); @@ -184,7 +184,7 @@ contract CoreRouteFacet is ? address(to).balance : IERC20(tokenOut).balanceOf(to); if (balOutFinal < balOutInitial + amountOutMin) { - revert MinimalOutputBalanceViolation(balOutFinal - balOutInitial); + revert TokenOutAmountTooLow(balOutFinal - balOutInitial); } amountOut = balOutFinal - balOutInitial; diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 93f047722..f81fa2164 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -226,7 +226,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); } - // MinimalInputBalanceViolation: trigger by charging the user twice via two ProcessUserERC20 steps. + // TokenInSpendingExceeded: trigger by charging the user twice via two ProcessUserERC20 steps. function testRevert_WhenInputBalanceIsInsufficientForTwoSteps() public { // Prepare token and approvals uint256 amountIn = 1e18; @@ -260,7 +260,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.MinimalInputBalanceViolation.selector, + CoreRouteFacet.TokenInSpendingExceeded.selector, amountIn, // available = final(0) + amountIn 2 * amountIn // required = initial (we minted 2*amountIn) ) @@ -316,7 +316,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.MinimalInputBalanceViolation.selector, + CoreRouteFacet.TokenInSpendingExceeded.selector, amountIn, // available = final(0) + amountIn 2 * amountIn // required = initial (we minted 2*amountIn) ) @@ -372,7 +372,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { // Expect MinimalOutputBalanceViolation with deltaOut = 0 vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.MinimalOutputBalanceViolation.selector, + CoreRouteFacet.TokenOutAmountTooLow.selector, uint256(0) ) ); @@ -421,7 +421,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { // Expect MinimalOutputBalanceViolation with deltaOut = 0 (no ETH sent) vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.MinimalOutputBalanceViolation.selector, + CoreRouteFacet.TokenOutAmountTooLow.selector, uint256(0) ) ); From e2c3990d4cd4901f8a50864ab8dd7eb5e440a967 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 14:01:57 +0200 Subject: [PATCH 060/220] Renamed error identifiers in CoreRouteFacet for consistency: TokenInSpendingExceeded to SwapTokenInSpendingExceeded and TokenOutAmountTooLow to SwapTokenOutAmountTooLow --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 11 +++++++---- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 8 ++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 4054e0209..b5ec8ee84 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -38,8 +38,11 @@ contract CoreRouteFacet is ); // ==== Errors ==== - error TokenInSpendingExceeded(uint256 actualSpent, uint256 expectedSpent); - error TokenOutAmountTooLow(uint256 actualOutput); + error SwapTokenInSpendingExceeded( + uint256 actualSpent, + uint256 expectedSpent + ); + error SwapTokenOutAmountTooLow(uint256 actualOutput); error UnknownCommandCode(); error SwapFailed(); error UnknownSelector(); @@ -174,7 +177,7 @@ contract CoreRouteFacet is ? 0 : IERC20(tokenIn).balanceOf(msg.sender); if (balInFinal + amountIn < balInInitial) { - revert TokenInSpendingExceeded( + revert SwapTokenInSpendingExceeded( balInFinal + amountIn, balInInitial ); @@ -184,7 +187,7 @@ contract CoreRouteFacet is ? address(to).balance : IERC20(tokenOut).balanceOf(to); if (balOutFinal < balOutInitial + amountOutMin) { - revert TokenOutAmountTooLow(balOutFinal - balOutInitial); + revert SwapTokenOutAmountTooLow(balOutFinal - balOutInitial); } amountOut = balOutFinal - balOutInitial; diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index f81fa2164..b8c690148 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -260,7 +260,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.TokenInSpendingExceeded.selector, + CoreRouteFacet.SwapTokenInSpendingExceeded.selector, amountIn, // available = final(0) + amountIn 2 * amountIn // required = initial (we minted 2*amountIn) ) @@ -316,7 +316,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.TokenInSpendingExceeded.selector, + CoreRouteFacet.SwapTokenInSpendingExceeded.selector, amountIn, // available = final(0) + amountIn 2 * amountIn // required = initial (we minted 2*amountIn) ) @@ -372,7 +372,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { // Expect MinimalOutputBalanceViolation with deltaOut = 0 vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.TokenOutAmountTooLow.selector, + CoreRouteFacet.SwapTokenOutAmountTooLow.selector, uint256(0) ) ); @@ -421,7 +421,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { // Expect MinimalOutputBalanceViolation with deltaOut = 0 (no ETH sent) vm.expectRevert( abi.encodeWithSelector( - CoreRouteFacet.TokenOutAmountTooLow.selector, + CoreRouteFacet.SwapTokenOutAmountTooLow.selector, uint256(0) ) ); From be266033c97d719de35aee3eeccc28b527f95e34 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 15:37:21 +0200 Subject: [PATCH 061/220] Refactor CoreRouteFacet to improve clarity and consistency in command handling. Renamed functions and updated comments to reflect new command structure for distributing and swapping tokens. Enhanced error handling for output amounts. --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 84 ++++++++++++--------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index b5ec8ee84..5711158bf 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -186,41 +186,40 @@ contract CoreRouteFacet is uint256 balOutFinal = isNativeOut ? address(to).balance : IERC20(tokenOut).balanceOf(to); - if (balOutFinal < balOutInitial + amountOutMin) { - revert SwapTokenOutAmountTooLow(balOutFinal - balOutInitial); - } - amountOut = balOutFinal - balOutInitial; + if (amountOut < amountOutMin) { + revert SwapTokenOutAmountTooLow(amountOut); + } } /// @notice Interprets and executes commands from the route byte stream - /// @dev A route is a packed byte stream of sequential commands. Each command begins with a 1-byte opcode: - /// - 1 = HandleSelfERC20 (tokens already on this contract) - /// - 2 = HandleUserERC20 (tokens from user) - /// - 3 = HandleNative (ETH held by this contract) - /// - 4 = HandleSinglePool (tokens already in pool) + /// @dev A route is a packed byte stream of sequential commands. Each command begins with a 1-byte route command: + /// - 1 = DistributeSelfERC20 (distributes and swaps tokens already on this contract) + /// - 2 = DistributeUserERC20 (distributes and swaps tokens from user) + /// - 3 = DistributeNative (distributes and swaps ETH held by this contract) + /// - 4 = DispatchSinglePoolSwap (dispatches swap using tokens already in pool) /// - 5 = ApplyPermit (EIP-2612 permit for tokenIn on behalf of msg.sender) /// - /// Stream formats per opcode: - /// 1. HandleSelfERC20: + /// Stream formats per route command: + /// 1. DistributeSelfERC20: /// [1][token: address][n: uint8] then n legs, each: /// [share: uint16][len: uint16][data: bytes] - /// total = IERC20(token).balanceOf(address(this)) minus 1 wei (undrain protection) + /// total = IERC20(token).balanceOf(address(this)) minus 1 wei (prevents tiny swaps) /// from = address(this), tokenIn = token /// - /// 2. HandleUserERC20: + /// 2. DistributeUserERC20: /// [2][token: address][n: uint8] then n legs, each: /// [share: uint16][len: uint16][data: bytes] /// total = declaredAmountIn /// from = msg.sender, tokenIn = token /// - /// 3. HandleNative: + /// 3. DistributeNative: /// [3][n: uint8] then n legs, each: /// [share: uint16][len: uint16][data: bytes] /// total = address(this).balance (includes msg.value and any residual ETH) /// from = address(this), tokenIn = INTERNAL_INPUT_SOURCE /// - /// 4. HandleSinglePool: + /// 4. DispatchSinglePoolSwap: /// [4][token: address][len: uint16][data: bytes] /// amountIn = 0 (pool sources tokens internally), from = INTERNAL_INPUT_SOURCE /// @@ -254,10 +253,10 @@ contract CoreRouteFacet is /// /// // Full route: [2][tokenA][2 legs][60% leg1][40% leg2] then [4][tokenB][leg3] /// route = abi.encodePacked( - /// uint8(2), tokenA, uint8(2), + /// uint8(2), tokenA, uint8(2), // DistributeUserERC20 with 2 legs /// uint16(39321), uint16(leg1.length), leg1, // ~60% of amountIn /// uint16(26214), uint16(leg2.length), leg2, // ~40% of amountIn - /// uint8(4), tokenB, + /// uint8(4), tokenB, // DispatchSinglePoolSwap /// uint16(leg3.length), leg3 /// ); /// ``` @@ -275,19 +274,22 @@ contract CoreRouteFacet is uint256 step = 0; uint256 cur = LibPackedStream.createStream(route); + // Iterate until the packed route stream is fully consumed. + // `isNotEmpty()` returns true while there are unread bytes left in the stream. while (cur.isNotEmpty()) { - uint8 opcode = cur.readUint8(); - if (opcode == 1) { - uint256 used = _handleSelfERC20(cur); + // Read the next command byte that specifies how to handle tokens in this step + uint8 routeCommand = cur.readUint8(); + if (routeCommand == 1) { + uint256 used = _distributeSelfERC20(cur); if (step == 0) realAmountIn = used; - } else if (opcode == 2) { - _handleUserERC20(cur, declaredAmountIn); - } else if (opcode == 3) { - uint256 usedNative = _handleNative(cur); + } else if (routeCommand == 2) { + _distributeUserERC20(cur, declaredAmountIn); + } else if (routeCommand == 3) { + uint256 usedNative = _distributeNative(cur); if (step == 0) realAmountIn = usedNative; - } else if (opcode == 4) { - _handleSinglePool(cur); - } else if (opcode == 5) { + } else if (routeCommand == 4) { + _dispatchSinglePoolSwap(cur); + } else if (routeCommand == 5) { _applyPermit(tokenIn, cur); } else { revert UnknownCommandCode(); @@ -321,39 +323,47 @@ contract CoreRouteFacet is ); } - /// @notice Handles native token (ETH) inputs + /// @notice Distributes native ETH held by this contract across legs and dispatches swaps /// @dev Assumes ETH is already present on the contract /// @param cur The current position in the byte stream /// @return total The total amount of ETH to process - function _handleNative(uint256 cur) private returns (uint256 total) { + function _distributeNative(uint256 cur) private returns (uint256 total) { total = address(this).balance; _distributeAndSwap(cur, address(this), INTERNAL_INPUT_SOURCE, total); } - /// @notice Processes ERC20 tokens already on this contract + /// @notice Distributes ERC20 tokens already on this contract /// @dev Includes protection against full balance draining /// @param cur The current position in the byte stream /// @return total The total amount of tokens to process - function _handleSelfERC20(uint256 cur) private returns (uint256 total) { + function _distributeSelfERC20( + uint256 cur + ) private returns (uint256 total) { address token = cur.readAddress(); total = IERC20(token).balanceOf(address(this)); unchecked { - if (total > 0) total -= 1; // slot undrain protection + // Prevent swaps with uselessly small amounts (like 1 wei) that could: + // 1. Cause the entire transaction to fail (most DEXs reject such tiny trades) + // 2. Waste gas even if they succeeded + // By subtracting 1 from any positive balance, we ensure: + // - A balance of 1 becomes a swap amount of 0 (effectively skipping the swap) + // - Larger balances are barely affected + if (total > 0) total -= 1; } _distributeAndSwap(cur, address(this), token, total); } - /// @notice Processes ERC20 tokens from the caller + /// @notice Distributes ERC20 tokens from the caller /// @param cur The current position in the byte stream - /// @param total The total amount to process - function _handleUserERC20(uint256 cur, uint256 total) private { + /// @param total The declared total to distribute from msg.sender + function _distributeUserERC20(uint256 cur, uint256 total) private { address token = cur.readAddress(); _distributeAndSwap(cur, msg.sender, token, total); } - /// @notice Processes a pool interaction where tokens are already in the pool + /// @notice Dispatches a single swap using tokens already in the pool /// @param cur The current position in the byte stream - function _handleSinglePool(uint256 cur) private { + function _dispatchSinglePoolSwap(uint256 cur) private { address token = cur.readAddress(); _dispatchSwap(cur, INTERNAL_INPUT_SOURCE, token, 0); } From 63dad2f30cb4da4f13a687ea2f30bf002b5c29d9 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 17:32:49 +0200 Subject: [PATCH 062/220] Update documentation in SyncSwapV2Facet to clarify support for both V1 and V2 pools. Enhanced comments to specify token deposit requirements for V1 pools. --- src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index 879885e9a..68d5d6daf 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -10,7 +10,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title SyncSwapV2Facet /// @author LI.FI (https://li.fi) /// @notice Handles SyncSwap V2 pool swaps with vault integration -/// @dev Implements direct selector-callable swap function for both V1 and V2 SyncSwap pools +/// @dev Supports both V1 and V2 SyncSwap pools /// @custom:version 1.0.0 contract SyncSwapV2Facet { using LibPackedStream for uint256; @@ -59,6 +59,8 @@ contract SyncSwapV2Facet { // if from is not msg.sender or address(this), it must be INTERNAL_INPUT_SOURCE // which means tokens are already in the vault/pool, no transfer needed + // SyncSwap V1 pools require tokens to be deposited into their vault first + // before they can be used for swapping if (isV1Pool) { ISyncSwapVault(target).deposit(tokenIn, pool); } From 9a0247b4fed785696dae307e1fb71be0ee8b7898 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 17:39:47 +0200 Subject: [PATCH 063/220] add comments --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 66 +++++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 5711158bf..73e78bc76 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -345,9 +345,7 @@ contract CoreRouteFacet is // Prevent swaps with uselessly small amounts (like 1 wei) that could: // 1. Cause the entire transaction to fail (most DEXs reject such tiny trades) // 2. Waste gas even if they succeeded - // By subtracting 1 from any positive balance, we ensure: - // - A balance of 1 becomes a swap amount of 0 (effectively skipping the swap) - // - Larger balances are barely affected + // By subtracting 1 from any positive balance, we ensure a balance of 1 becomes a swap amount of 0 (effectively skipping the swap) if (total > 0) total -= 1; } _distributeAndSwap(cur, address(this), token, total); @@ -392,6 +390,10 @@ contract CoreRouteFacet is /// @notice Dispatches a swap call to the appropriate DEX facet /// @dev Uses direct selector dispatch with optimized calldata construction + /// Assembly is used to: + /// - Build calldata for delegatecall without extra memory copies/abi.encode overhead + /// - Reuse the payload already read from the stream without re-encoding + /// - Keep memory usage predictable and cheap across arbitrary payload sizes /// @param cur The current position in the byte stream /// @param from The source address for tokens /// @param tokenIn The input token address @@ -402,12 +404,14 @@ contract CoreRouteFacet is address tokenIn, uint256 amountIn ) private { + // Read [selector | payload] for the specific DEX facet bytes memory data = cur.readBytesWithLength(); bytes4 selector = _readSelector(data); - // in-place payload alias (no copy) + // In-place payload alias (no copy): point to data+4 and change length to exclude selector bytes memory payload; assembly { + // payload = data + 4; then set payload.length = data.length - 4 payload := add(data, 4) mstore(payload, sub(mload(data), 4)) } @@ -418,44 +422,70 @@ contract CoreRouteFacet is bool success; bytes memory returnData; assembly { + // Load free memory pointer where we’ll build calldata let free := mload(0x40) - // selector + + // Calldata layout we build: + // [0..3] function selector + // [4..] ABI-encoded args: + // head (4 slots): + // slot0: offset_to_payload (0x80 = after 4 static slots) + // slot1: from (address) + // slot2: tokenIn (address) + // slot3: amountIn (uint256) + // payload area: + // slot4: payload.length + // slot5+: payload bytes (padded to 32) + + // Write function selector at free mstore(free, selector) let args := add(free, 4) - // head (4 args): [offset_to_payload, from, tokenIn, amountIn] - mstore(args, 0x80) // offset to payload data (after 4 static slots) + // Head area: [offset_to_payload, from, tokenIn, amountIn] + // offset_to_payload = 0x80 (4 slots * 32 bytes) + mstore(args, 0x80) mstore(add(args, 0x20), from) mstore(add(args, 0x40), tokenIn) mstore(add(args, 0x60), amountIn) - // payload area + // Write payload area (length + bytes) let d := add(args, 0x80) let len := mload(payload) mstore(d, len) - // copy payload bytes - // identity precompile is cheapest for arbitrary-length copy + + // Copy payload bytes into memory using the identity precompile (address 0x04) + // This is cheaper for arbitrary-size copies versus manual 32-byte loops. + // to = d+32; from = payload+32; size = len pop( staticcall(gas(), 0x04, add(payload, 32), len, add(d, 32), len) ) + // Round up payload length to 32 bytes for total calldata size let padded := and(add(len, 31), not(31)) + // total calldata = 4 (selector) + 0x80 (head) + 0x20 (payload length) + padded payload let total := add(4, add(0x80, add(0x20, padded))) + + // Perform delegatecall to the facet with our constructed calldata + // - delegatecall preserves msg.sender and storage context (diamond pattern) success := delegatecall(gas(), facet, free, total, 0, 0) - // update free memory pointer + // Advance the free memory pointer to after our calldata mstore(0x40, add(free, total)) - // capture return data + // Capture return data into a new bytes array: + // returnData = new bytes(returndatasize()) let rsize := returndatasize() returnData := mload(0x40) mstore(returnData, rsize) let rptr := add(returnData, 32) returndatacopy(rptr, 0, rsize) + + // Bump free memory pointer past the returnData buffer (rounded to 32 bytes) mstore(0x40, add(rptr, and(add(rsize, 31), not(31)))) } if (!success) { + // Bubble up revert reason from facet if present LibUtil.revertWith(returnData); } } @@ -463,6 +493,10 @@ contract CoreRouteFacet is // ==== Private Functions - Helpers ==== /// @notice Extracts function selector from calldata + /// @dev Assembly used to load the first 4 bytes (selector) directly from the bytes blob: + /// - bytes are laid out as [len (32 bytes) | data...] + /// - mload(add(blob, 32)) loads the first 32 bytes of data + /// - Solidity ABI selectors occupy the first 4 bytes /// @param blob The calldata bytes /// @return sel The extracted selector function _readSelector( @@ -473,14 +507,20 @@ contract CoreRouteFacet is } } - /// @notice Creates a new bytes array without the selector + /// @notice Creates a new bytes view that aliases blob without the first 4 bytes (selector) + /// @dev Assembly used to: + /// - Point into the original bytes (no allocation/copy) + /// - Rewrite the length to exclude the 4-byte selector + /// This is safe here because we treat the result as a read-only slice. /// @param blob The original calldata bytes /// @return payload The calldata without selector function _payloadFrom( bytes memory blob ) private pure returns (bytes memory payload) { assembly { + // payload points at blob + 4, sharing the same underlying buffer payload := add(blob, 4) + // set payload.length = blob.length - 4 mstore(payload, sub(mload(blob), 4)) } } From 546ab4df6aeba56ca5470554a06d40e51cad7206 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 17:56:10 +0200 Subject: [PATCH 064/220] Enhance comments in CoreRouteFacet for assembly --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 96 +++++++++++++-------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 73e78bc76..9ae496ede 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -404,15 +404,19 @@ contract CoreRouteFacet is address tokenIn, uint256 amountIn ) private { - // Read [selector | payload] for the specific DEX facet + // Read [selector | payload] blob for the specific DEX facet bytes memory data = cur.readBytesWithLength(); + // Extract function selector (first 4 bytes of data) bytes4 selector = _readSelector(data); - // In-place payload alias (no copy): point to data+4 and change length to exclude selector + + // Create an in-place alias for the payload (skip the first 4 bytes). + // This avoids allocating/copying a second bytes array for the payload. bytes memory payload; assembly { - // payload = data + 4; then set payload.length = data.length - 4 + // payload points to data + 4 and shares the same buffer payload := add(data, 4) + // payload.length = data.length - 4 mstore(payload, sub(mload(data), 4)) } @@ -422,70 +426,94 @@ contract CoreRouteFacet is bool success; bytes memory returnData; assembly { - // Load free memory pointer where we’ll build calldata + // Example: Building calldata for swapUniV3(bytes,address,address,uint256) + // with example values: + // - selector: 0x1234abcd + // - from: 0xaaa... + // - tokenIn: 0xbbb... (USDC) + // - amountIn: 1000000 (1 USDC) + // - swapData: abi.encode( + // pool: 0x123..., + // direction: 1, + // recipient: 0x456... + // ) + // + // Memory layout it builds (each line is 32 bytes): + // Position Content + // 0x80: 0x1234abcd00000000... // selector + // 0x84: 0x80 // offset to swapData + // 0xa4: 0xaaa... // from address + // 0xc4: 0xbbb... // tokenIn address + // 0xe4: 0x0f4240 // amountIn (1000000) + // 0x104: 0x60 // swapData length (96) + // 0x124: 0x123... // pool address + // 0x144: 0x01 // direction + // 0x164: 0x456... // recipient + + // Free memory pointer where we’ll build calldata for delegatecall let free := mload(0x40) // Calldata layout we build: // [0..3] function selector // [4..] ABI-encoded args: - // head (4 slots): - // slot0: offset_to_payload (0x80 = after 4 static slots) - // slot1: from (address) - // slot2: tokenIn (address) - // slot3: amountIn (uint256) + // head (4 words): + // word0: offset_to_payload (0x80 = after 4 static words) + // word1: from (address) + // word2: tokenIn (address) + // word3: amountIn (uint256) // payload area: - // slot4: payload.length - // slot5+: payload bytes (padded to 32) + // word4: payload.length + // word5+: payload bytes (padded to 32 bytes) - // Write function selector at free + // Write function selector mstore(free, selector) let args := add(free, 4) - // Head area: [offset_to_payload, from, tokenIn, amountIn] - // offset_to_payload = 0x80 (4 slots * 32 bytes) - mstore(args, 0x80) + // Head: [offset_to_payload, from, tokenIn, amountIn] + mstore(args, 0x80) // offset to payload data mstore(add(args, 0x20), from) mstore(add(args, 0x40), tokenIn) mstore(add(args, 0x60), amountIn) - // Write payload area (length + bytes) + // Payload area (length + bytes) let d := add(args, 0x80) let len := mload(payload) mstore(d, len) - // Copy payload bytes into memory using the identity precompile (address 0x04) - // This is cheaper for arbitrary-size copies versus manual 32-byte loops. + // Copy payload via the identity precompile (address 0x04). + // This is cheaper for arbitrary-length copies vs. manual loops. // to = d+32; from = payload+32; size = len pop( staticcall(gas(), 0x04, add(payload, 32), len, add(d, 32), len) ) - // Round up payload length to 32 bytes for total calldata size + // Round payload length up to a multiple of 32 and compute total calldata size let padded := and(add(len, 31), not(31)) - // total calldata = 4 (selector) + 0x80 (head) + 0x20 (payload length) + padded payload + // total = 4 (selector) + 0x80 (head) + 0x20 (payload length) + padded payload let total := add(4, add(0x80, add(0x20, padded))) - // Perform delegatecall to the facet with our constructed calldata - // - delegatecall preserves msg.sender and storage context (diamond pattern) + // Perform delegatecall into facet. + // delegatecall preserves msg.sender and storage context (diamond pattern). success := delegatecall(gas(), facet, free, total, 0, 0) - // Advance the free memory pointer to after our calldata + // Advance the free memory pointer past our calldata buffer (even on failure). mstore(0x40, add(free, total)) - // Capture return data into a new bytes array: - // returnData = new bytes(returndatasize()) - let rsize := returndatasize() - returnData := mload(0x40) - mstore(returnData, rsize) - let rptr := add(returnData, 32) - returndatacopy(rptr, 0, rsize) - - // Bump free memory pointer past the returnData buffer (rounded to 32 bytes) - mstore(0x40, add(rptr, and(add(rsize, 31), not(31)))) + // Only allocate/copy return data on failure to save gas on success. + switch success + case 0 { + let rsize := returndatasize() + returnData := mload(0x40) + mstore(returnData, rsize) + let rptr := add(returnData, 32) + returndatacopy(rptr, 0, rsize) + // bump free memory pointer past the return data buffer (padded) + mstore(0x40, add(rptr, and(add(rsize, 31), not(31)))) + } } + // Bubble up revert data if delegatecall failed if (!success) { - // Bubble up revert reason from facet if present LibUtil.revertWith(returnData); } } From b50f8ab39e031405b98ba78e9e35a895db2cadfb Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 18:34:51 +0200 Subject: [PATCH 065/220] Refactor UniV2StyleFacet to use a constant for fee denominator, enhancing readability. Remove unnecessary comments in VelodromeV2Facet. Introduce mock facets for testing ERC20 token pulls and native token handling in CoreRouteFacetTest. Delete obsolete mock facet files --- src/Libraries/LibAsset.sol | 2 - src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 8 +++- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 1 - .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 39 +++++++++++++++++-- test/solidity/utils/MockNativeFacet.sol | 21 ---------- test/solidity/utils/MockPullERC20Facet.sol | 28 ------------- test/solidity/utils/TestHelpers.sol | 5 ++- 7 files changed, 45 insertions(+), 59 deletions(-) delete mode 100644 test/solidity/utils/MockNativeFacet.sol delete mode 100644 test/solidity/utils/MockPullERC20Facet.sol diff --git a/src/Libraries/LibAsset.sol b/src/Libraries/LibAsset.sol index ffe844ca1..ae5b49ea8 100644 --- a/src/Libraries/LibAsset.sol +++ b/src/Libraries/LibAsset.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibSwap } from "./LibSwap.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; - -// solhint-disable-next-line max-line-length import { InvalidReceiver, NullAddrIsNotAValidSpender, InvalidAmount, NullAddrIsNotAnERC20Token } from "../Errors/GenericErrors.sol"; /// @title LibAsset diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index 89119a069..16513a3fb 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -15,6 +15,10 @@ import { BaseRouteConstants } from "../BaseRouteConstants.sol"; contract UniV2StyleFacet is BaseRouteConstants { using LibPackedStream for uint256; + // ==== Constants ==== + /// @dev Fee denominator for UniV2-style pools (100% = 1_000_000) + uint256 private constant FEE_DENOMINATOR = 1_000_000; + // ==== Errors ==== /// @dev Thrown when pool reserves are zero, indicating an invalid pool state error WrongPoolReserves(); @@ -62,9 +66,9 @@ contract UniV2StyleFacet is BaseRouteConstants { // Calculate actual input amount from pool balance amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; - uint256 amountInWithFee = amountIn * (1_000_000 - fee); + uint256 amountInWithFee = amountIn * (FEE_DENOMINATOR - fee); uint256 amountOut = (amountInWithFee * reserveOut) / - (reserveIn * 1_000_000 + amountInWithFee); + (reserveIn * FEE_DENOMINATOR + amountInWithFee); (uint256 amount0Out, uint256 amount1Out) = direction ? (uint256(0), amountOut) diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 8055569c6..6be86b0e5 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -46,7 +46,6 @@ contract VelodromeV2Facet is BaseRouteConstants { if (pool == address(0) || recipient == address(0)) revert InvalidCallData(); - // solhint-disable-next-line max-line-length bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. // Will revert if contract (recipient) does not implement IVelodromeV2PoolCallee. diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index b8c690148..1edcecdb4 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -5,10 +5,9 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; -import { MockPullERC20Facet } from "../../../utils/MockPullERC20Facet.sol"; -import { MockNativeFacet } from "../../../utils/MockNativeFacet.sol"; -import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; +import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; contract CoreRouteFacetTest is BaseCoreRouteTest { using SafeTransferLib for address; @@ -437,3 +436,37 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { vm.stopPrank(); } } + +/// @dev Mock facet implementing LDA's standard interface for testing ERC20 token pulls +contract MockPullERC20Facet { + function pull( + bytes memory /*payload*/, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + if (from == msg.sender) { + LibAsset.transferFromERC20( + tokenIn, + msg.sender, + address(this), + amountIn + ); + } + return amountIn; + } +} + +/// @dev Mock facet implementing LDA's standard interface for testing native token handling +contract MockNativeFacet { + function handleNative( + bytes memory payload, + address /*from*/, + address /*tokenIn*/, + uint256 amountIn + ) external payable returns (uint256) { + address recipient = abi.decode(payload, (address)); + LibAsset.transferAsset(address(0), payable(recipient), amountIn); + return amountIn; + } +} diff --git a/test/solidity/utils/MockNativeFacet.sol b/test/solidity/utils/MockNativeFacet.sol deleted file mode 100644 index 948e12f9b..000000000 --- a/test/solidity/utils/MockNativeFacet.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; - -/// @title MockNativeFacet -/// @author LI.FI (https://li.fi) -/// @notice Mock facet that handles native token transfers -/// @custom:version 1.0.0 -contract MockNativeFacet { - function handleNative( - bytes memory payload, - address /*from*/, - address /*tokenIn*/, - uint256 amountIn - ) external payable returns (uint256) { - address recipient = abi.decode(payload, (address)); - LibAsset.transferAsset(address(0), payable(recipient), amountIn); - return amountIn; - } -} diff --git a/test/solidity/utils/MockPullERC20Facet.sol b/test/solidity/utils/MockPullERC20Facet.sol deleted file mode 100644 index 7c8f4b178..000000000 --- a/test/solidity/utils/MockPullERC20Facet.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; - -/// @title MockPullERC20Facet -/// @author LI.FI (https://li.fi) -/// @notice Mock facet that pulls ERC20 tokens from msg.sender -/// @custom:version 1.0.0 -contract MockPullERC20Facet { - // Pulls `amountIn` from msg.sender if `from == msg.sender` - function pull( - bytes memory /*payload*/, - address from, - address tokenIn, - uint256 amountIn - ) external returns (uint256) { - if (from == msg.sender) { - LibAsset.transferFromERC20( - tokenIn, - msg.sender, - address(this), - amountIn - ); - } - return amountIn; - } -} diff --git a/test/solidity/utils/TestHelpers.sol b/test/solidity/utils/TestHelpers.sol index d4febb1ad..1980130f4 100644 --- a/test/solidity/utils/TestHelpers.sol +++ b/test/solidity/utils/TestHelpers.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.17; import { Test } from "forge-std/Test.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { MockUniswapDEX } from "./MockUniswapDEX.sol"; +import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; interface DexManager { function addDex(address _dex) external; @@ -12,7 +13,7 @@ interface DexManager { } //common utilities for forge tests -contract TestHelpers is Test { +contract TestHelpers is Test, TestBaseForksConstants { uint256 internal customBlockNumberForForking; string internal customRpcUrlForForking; @@ -87,7 +88,7 @@ contract TestHelpers is Test { : vm.envString("ETH_NODE_URI_MAINNET"); uint256 blockNumber = customBlockNumberForForking > 0 ? customBlockNumberForForking - : 14847528; + : DEFAULT_BLOCK_NUMBER_MAINNET; vm.createSelectFork(rpcUrl, blockNumber); } From 77f334a5d971726ddc52fed9fb6b4c7955085dcc Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 19:24:45 +0200 Subject: [PATCH 066/220] Refactor naming conventions in test contracts and library for consistency. Updated 'BaseDexFacetTest' to 'BaseDEXFacetTest' and corrected namespace in 'LibCallbackAuthenticator' for improved clarity --- src/Libraries/LibCallbackAuthenticator.sol | 2 +- test/solidity/Periphery/Lda/BaseDexFacet.t.sol | 4 ++-- .../solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol | 4 ++-- test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 6 +++--- test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol | 8 ++------ test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol | 4 ++-- 13 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/Libraries/LibCallbackAuthenticator.sol b/src/Libraries/LibCallbackAuthenticator.sol index 03dc6edf4..ea2673529 100644 --- a/src/Libraries/LibCallbackAuthenticator.sol +++ b/src/Libraries/LibCallbackAuthenticator.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.17; library LibCallbackAuthenticator { /// Types /// bytes32 internal constant NAMESPACE = - keccak256("com.lifi.lda.callbackauthenticator"); + keccak256("com.lifi.lda.callbackAuthenticator"); /// Storage /// struct CallbackStorage { diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index b997768f9..91f15c229 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -7,10 +7,10 @@ import { BaseCoreRouteTest } from "./BaseCoreRouteTest.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; /** - * @title BaseDexFacetTest + * @title BaseDEXFacetTest * @notice Base test contract with common functionality and abstractions for DEX-specific tests */ -abstract contract BaseDexFacetTest is BaseCoreRouteTest { +abstract contract BaseDEXFacetTest is BaseCoreRouteTest { using SafeERC20 for IERC20; // ==== Types ==== diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index a7862f5d8..e1e65c202 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; -import { BaseDexFacetTest } from "./BaseDexFacet.t.sol"; +import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; -abstract contract BaseDexFacetWithCallbackTest is BaseDexFacetTest { +abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { // Each DEX with callback must implement these hooks function _getCallbackSelector() internal virtual returns (bytes4); function _buildCallbackSwapData( diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 3a2664b7c..2e17ded3a 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; -import { BaseDexFacetWithCallbackTest } from "./BaseDexFacetWithCallback.t.sol"; +import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; -abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetWithCallbackTest { +abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { UniV3StyleFacet internal uniV3Facet; // ==== Types ==== diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index fc9f14a9a..040822154 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -11,9 +11,9 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; -import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; +import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; -contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { +contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { AlgebraFacet internal algebraFacet; // ==== Constants ==== diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index de6c6a1b5..0e4ed6f49 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; -contract EnosysDexV3FacetTest is BaseUniV3StyleDexFacetTest { +contract EnosysDEXV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index a2c62af2a..0fff6ced6 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; -contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { +contract HyperswapV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 017ea4120..f60eff82b 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseDexFacetWithCallbackTest } from "../BaseDexFacetWithCallback.t.sol"; +import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; -contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { +contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { IzumiV3Facet internal izumiV3Facet; // ==== Types ==== @@ -253,7 +253,7 @@ contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { vm.store( address(ldaDiamond), - keccak256("com.lifi.lda.callbackauthenticator"), + keccak256("com.lifi.lda.callbackAuthenticator"), bytes32(uint256(uint160(poolInMid))) ); diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index e22d89fa4..354e60cd1 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; -contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { +contract LaminarV3FacetTest is BaseUniV3StyleDEXFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "hyperevm", diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index c30911451..3db0b690c 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; -contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { +contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "viction", @@ -30,7 +30,6 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { vm.startPrank(USER_SENDER); tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); - // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ pool: address(0), // Invalid pool address @@ -39,7 +38,6 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { }) ); - // Use _buildBaseRoute from base class bytes memory route = _buildBaseRoute( SwapTestParams({ tokenIn: address(tokenIn), @@ -76,7 +74,6 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { vm.startPrank(USER_SENDER); tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); - // Use _buildUniV3SwapData from base class bytes memory swapData = _buildUniV3SwapData( UniV3SwapParams({ pool: poolInOut, @@ -85,7 +82,6 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { }) ); - // Use _buildBaseRoute from base class bytes memory route = _buildBaseRoute( SwapTestParams({ tokenIn: address(tokenIn), diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 0529039b8..fce967bac 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -contract SyncSwapV2FacetTest is BaseDexFacetTest { +contract SyncSwapV2FacetTest is BaseDEXFacetTest { SyncSwapV2Facet internal syncSwapV2Facet; address internal constant SYNC_SWAP_VAULT = diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 71ebe6486..268f600bb 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -8,9 +8,9 @@ import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseDexFacetTest } from "../BaseDexFacet.t.sol"; +import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; -contract VelodromeV2FacetTest is BaseDexFacetTest { +contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2Facet internal velodromeV2Facet; // ==== Constants ==== diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 2a914c504..5c11c4a14 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDexFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; -contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { +contract XSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "xdc", blockNumber: 89279495 }); From 126c2166a7aac8fbc4b92b0059e7aea1330758e3 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 19:27:52 +0200 Subject: [PATCH 067/220] use super.setUp() --- test/solidity/Periphery/Lda/BaseDexFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 91f15c229..3af155725 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -109,7 +109,7 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { customBlockNumberForForking = forkConfig.blockNumber; fork(); - BaseCoreRouteTest.setUp(); + super.setUp(); _setupDexEnv(); // populate tokens/pools _addDexFacet(); } diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 1edcecdb4..7ebae5805 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -16,7 +16,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { // ==== Setup Functions ==== function setUp() public override { - BaseCoreRouteTest.setUp(); + super.setUp(); // Register mock pull facet once and store selector MockPullERC20Facet mockPull = new MockPullERC20Facet(); bytes4[] memory sel = new bytes4[](1); From fa247e1f2d589b92ac8d3c9ae9314196d1b5d621 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 22 Aug 2025 19:30:13 +0200 Subject: [PATCH 068/220] Update CoreRouteFacetTest to use USER_RECEIVER --- test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 7ebae5805..4b035fb95 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -86,16 +86,15 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { function test_ProcessNativeCommandSendsEthToRecipient() public { _addMockNativeFacet(); - address recipient = USER_RECEIVER; uint256 amount = 1 ether; // Fund the actual caller (USER_SENDER) vm.deal(USER_SENDER, amount); - // swapData: selector + abi.encode(recipient) + // swapData: selector + abi.encode(USER_RECEIVER) bytes memory swapData = abi.encodePacked( MockNativeFacet.handleNative.selector, - abi.encode(recipient) + abi.encode(USER_RECEIVER) ); // route: [3][num=1][share=FULL_SHARE][len][swapData] @@ -105,7 +104,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { amountIn: amount, minOut: 0, sender: USER_SENDER, // Use USER_SENDER directly - recipient: recipient, + recipient: USER_RECEIVER, commandType: CommandType.ProcessNative }); From 1b19ed17ea6bad43ba9836181ddc3b4e643cef72 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sat, 23 Aug 2025 00:23:37 +0200 Subject: [PATCH 069/220] Refactor swap execution in test contracts to utilize _buildRouteAndExecuteSwap helper function, improving code readability and reducing redundancy across AlgebraFacetTest, IzumiV3FacetTest, RabbitSwapV3FacetTest, SyncSwapV2FacetTest, and VelodromeV2FacetTest --- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 58 ++++++++ .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 88 +++--------- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 47 +------ .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 34 +---- .../Lda/Facets/SyncSwapV2Facet.t.sol | 130 ++---------------- .../Lda/Facets/VelodromeV2Facet.t.sol | 55 +++----- 6 files changed, 115 insertions(+), 297 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 5b7781b0b..592164ca0 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -291,6 +291,64 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } + /// @dev Helper that builds route and executes swap in one call + function _buildRouteAndExecuteSwap( + SwapTestParams memory params, + bytes memory swapData, + ExpectedEvent[] memory expectedEvents, + bool expectRevert, + RouteEventVerification memory verification + ) internal { + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap( + params, + route, + expectedEvents, + expectRevert, + verification + ); + } + + /// @dev Overload with default parameters for simple cases + function _buildRouteAndExecuteSwap( + SwapTestParams memory params, + bytes memory swapData + ) internal { + _buildRouteAndExecuteSwap( + params, + swapData, + new ExpectedEvent[](0), + false, + RouteEventVerification({ expectedExactOut: 0, checkData: false }) + ); + } + + /// @dev Overload for revert cases + function _buildRouteAndExecuteSwap( + SwapTestParams memory params, + bytes memory swapData, + bytes4 expectedRevert + ) internal { + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route, expectedRevert); + } + + /// @dev Overload matching _executeAndVerifySwap's 4-parameter signature + function _buildRouteAndExecuteSwap( + SwapTestParams memory params, + bytes memory swapData, + ExpectedEvent[] memory additionalEvents, + bool isFeeOnTransferToken + ) internal { + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap( + params, + route, + additionalEvents, + isFeeOnTransferToken + ); + } + // helper: load a 32-byte topic from a 32-byte abi.encode(param) function _asTopic(bytes memory enc) internal pure returns (bytes32 topic) { if (enc.length != 32) revert InvalidTopicLength(); diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 040822154..253bfb961 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -135,7 +135,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -145,22 +145,9 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, commandType: CommandType.ProcessUserERC20 }), - swapData - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: RANDOM_APE_ETH_HOLDER_APECHAIN, - recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, - commandType: CommandType.ProcessUserERC20 - }), - route, + swapData, new ExpectedEvent[](0), - true // This is a fee-on-transfer token + true // This is a fee-on-transfer token, ); vm.stopPrank(); @@ -251,20 +238,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { bytes memory swapData = _buildCallbackSwapData(mockPool, USER_SENDER); - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -274,7 +248,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - route, + swapData, SwapCallbackNotExecuted.selector ); @@ -306,7 +280,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -316,20 +290,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - swapData - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route, + swapData, InvalidCallData.selector ); @@ -383,32 +344,17 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { }) ); - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + recipient: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }); - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route, - InvalidCallData.selector - ); + _buildRouteAndExecuteSwap(params, swapData, InvalidCallData.selector); vm.stopPrank(); vm.clearMockedCalls(); diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index f60eff82b..e1528bbf1 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -100,7 +100,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -113,19 +113,6 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { swapData ); - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - route - ); - vm.stopPrank(); } @@ -147,7 +134,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -160,19 +147,6 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { swapData ); - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn() - 1, // -1 for undrain protection - minOut: 0, - sender: address(coreRouteFacet), - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - route - ); - vm.stopPrank(); } @@ -275,20 +249,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { }) ); - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenMid), - tokenOut: address(tokenIn), - amountIn: type(uint216).max, - minOut: 0, - sender: USER_SENDER, - recipient: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenMid), tokenOut: address(tokenIn), @@ -298,7 +259,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { recipient: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), - route, + swapData, InvalidCallData.selector ); diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 3db0b690c..60502b9eb 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -38,7 +38,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -48,20 +48,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - swapData - ); - - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route, + swapData, InvalidCallData.selector ); @@ -82,20 +69,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { }) ); - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -105,7 +79,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - route, + swapData, InvalidCallData.selector ); diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index fce967bac..33aed60c0 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -64,7 +64,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -77,19 +77,6 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { swapData ); - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route - ); - vm.stopPrank(); } @@ -109,7 +96,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -122,19 +109,6 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { swapData ); - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - route - ); - vm.stopPrank(); } @@ -158,7 +132,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -171,19 +145,6 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { swapData ); - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - route - ); - vm.stopPrank(); } @@ -207,7 +168,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory route = _buildBaseRoute( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -220,19 +181,6 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { swapData ); - _executeAndVerifySwap( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenMid), - amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessMyERC20 - }), - route - ); - vm.stopPrank(); } @@ -326,20 +274,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory route = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -349,7 +284,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - route, + swapData, InvalidCallData.selector ); @@ -362,7 +297,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.startPrank(USER_SENDER); - bytes memory swapData = _buildSyncSwapV2SwapData( + bytes memory swapDataWithInvalidPool = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ pool: address(0), to: address(USER_SENDER), @@ -372,20 +307,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory routeWithInvalidPool = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapData - ); - - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -395,7 +317,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - routeWithInvalidPool, + swapDataWithInvalidPool, InvalidCallData.selector ); @@ -409,20 +331,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory routeWithInvalidRecipient = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: _getDefaultAmountForTokenIn(), - minOut: 0, - sender: USER_SENDER, // Send to next pool - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapDataWithInvalidRecipient - ); - - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -432,7 +341,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - routeWithInvalidRecipient, + swapDataWithInvalidRecipient, InvalidCallData.selector ); @@ -453,20 +362,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - bytes memory routeWithInvalidWithdrawMode = _buildBaseRoute( - SwapTestParams({ - tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 1, // Arbitrary amount for this test - minOut: 0, - sender: USER_SENDER, - recipient: USER_SENDER, - commandType: CommandType.ProcessUserERC20 - }), - swapDataWithInvalidWithdrawMode - ); - - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -476,7 +372,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - routeWithInvalidWithdrawMode, + swapDataWithInvalidWithdrawMode, InvalidCallData.selector ); diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 268f600bb..7bd64a5bd 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -452,25 +452,16 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { // --- Test case 1: Zero pool address --- // 1. Create the specific swap data blob - bytes memory swapDataZeroPool = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - address(0), // Invalid pool - uint8(SwapDirection.Token1ToToken0), - USER_SENDER, - uint8(CallbackStatus.Disabled) - ); - - // 2. Create the full route with the length-prefixed swap data - bytes memory routeWithZeroPool = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(tokenIn), - uint8(1), - FULL_SHARE, - uint16(swapDataZeroPool.length), // Length prefix - swapDataZeroPool + bytes memory swapDataZeroPool = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: address(0), // Invalid pool + direction: SwapDirection.Token1ToToken0, + recipient: USER_SENDER, + callbackStatus: CallbackStatus.Disabled + }) ); - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -480,29 +471,21 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - routeWithZeroPool, + swapDataZeroPool, InvalidCallData.selector ); // --- Test case 2: Zero recipient address --- - bytes memory swapDataZeroRecipient = abi.encodePacked( - VelodromeV2Facet.swapVelodromeV2.selector, - validPool, - uint8(SwapDirection.Token1ToToken0), - address(0), // Invalid recipient - uint8(CallbackStatus.Disabled) - ); - - bytes memory routeWithZeroRecipient = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(tokenIn), - uint8(1), - FULL_SHARE, - uint16(swapDataZeroRecipient.length), // Length prefix - swapDataZeroRecipient + bytes memory swapDataZeroRecipient = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: validPool, + direction: SwapDirection.Token1ToToken0, + recipient: address(0), // Invalid recipient + callbackStatus: CallbackStatus.Disabled + }) ); - _executeAndVerifySwap( + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -512,7 +495,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { recipient: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - routeWithZeroRecipient, + swapDataZeroRecipient, InvalidCallData.selector ); @@ -669,7 +652,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, - minOut: 0, + minOut: expectedOutput[1], sender: params.from, recipient: params.to, commandType: commandCode From 2b6fbfa3882a4158e00a11918e0b9eaf89457c71 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 14:21:49 +0200 Subject: [PATCH 070/220] Add LDADiamond contract and refactor addFacet calls in test contracts to use address(diamond) for consistency --- src/Periphery/Lda/LDADiamond.sol | 70 +++++++++++++++++++ templates/facetTest.template.hbs | 2 +- test/solidity/Facets/AccessManagerFacet.t.sol | 12 +++- test/solidity/Facets/AcrossFacet.t.sol | 2 +- test/solidity/Facets/AcrossFacetPacked.t.sol | 6 +- .../solidity/Facets/AcrossFacetPackedV3.t.sol | 6 +- test/solidity/Facets/AcrossFacetV3.t.sol | 2 +- test/solidity/Facets/AllBridgeFacet.t.sol | 2 +- .../solidity/Facets/ArbitrumBridgeFacet.t.sol | 6 +- test/solidity/Facets/CBridge/CBridge.t.sol | 2 +- .../CBridge/CBridgeAndFeeCollection.t.sol | 2 +- .../Facets/CBridge/CBridgeFacetPacked.t.sol | 6 +- .../Facets/CBridge/CBridgeRefund.t.sol | 2 +- .../Facets/CelerCircleBridgeFacet.t.sol | 6 +- test/solidity/Facets/ChainflipFacet.t.sol | 2 +- test/solidity/Facets/DeBridgeDlnFacet.t.sol | 6 +- test/solidity/Facets/DexManagerFacet.t.sol | 4 +- test/solidity/Facets/GasZipFacet.t.sol | 2 +- test/solidity/Facets/GenericSwapFacet.t.sol | 6 +- test/solidity/Facets/GenericSwapFacetV3.t.sol | 12 +++- test/solidity/Facets/GlacisFacet.t.sol | 2 +- test/solidity/Facets/GnosisBridgeFacet.t.sol | 6 +- test/solidity/Facets/HopFacet.t.sol | 6 +- .../HopFacetOptimizedL1.t.sol | 2 +- .../HopFacetOptimizedL2.t.sol | 2 +- .../HopFacetPacked/HopFacetPackedL1.t.sol | 4 +- .../HopFacetPacked/HopFacetPackedL2.t.sol | 4 +- test/solidity/Facets/MayanFacet.t.sol | 6 +- .../OmniBridgeFacet/OmniBridgeFacet.t.sol | 6 +- .../OmniBridgeFacet/OmniBridgeL2Facet.t.sol | 6 +- .../solidity/Facets/OptimismBridgeFacet.t.sol | 6 +- test/solidity/Facets/PioneerFacet.t.sol | 6 +- test/solidity/Facets/PolygonBridgeFacet.t.sol | 6 +- test/solidity/Facets/RelayFacet.t.sol | 2 +- test/solidity/Facets/SquidFacet.t.sol | 2 +- test/solidity/Facets/StargateFacetV2.t.sol | 6 +- test/solidity/Facets/SymbiosisFacet.t.sol | 2 +- test/solidity/Facets/ThorSwapFacet.t.sol | 2 +- .../Gas/CBridgeFacetPackedARB.gas.t.sol | 6 +- .../Gas/CBridgeFacetPackedETH.gas.t.sol | 10 ++- test/solidity/Gas/Hop.t.sol | 2 +- test/solidity/Gas/HopFacetPackedARB.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedETH.gas.t.sol | 2 +- test/solidity/Gas/HopFacetPackedPOL.gas.t.sol | 2 +- test/solidity/Helpers/SwapperV2.t.sol | 2 +- test/solidity/Periphery/GasZipPeriphery.t.sol | 6 +- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 2 +- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 2 +- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 6 +- .../Periphery/Lda/utils/LdaDiamondTest.sol | 8 +-- .../Periphery/LidoWrapper/LidoWrapper.t.sol | 8 ++- test/solidity/utils/BaseDiamondTest.sol | 15 ++-- 52 files changed, 232 insertions(+), 75 deletions(-) create mode 100644 src/Periphery/Lda/LDADiamond.sol diff --git a/src/Periphery/Lda/LDADiamond.sol b/src/Periphery/Lda/LDADiamond.sol new file mode 100644 index 000000000..a9dd4eda0 --- /dev/null +++ b/src/Periphery/Lda/LDADiamond.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibDiamond } from "../../Libraries/LibDiamond.sol"; +import { IDiamondCut } from "../../Interfaces/IDiamondCut.sol"; + +/// @title LDADiamond +/// @author LI.FI (https://li.fi) +/// @notice Base EIP-2535 Diamond Proxy Contract. +/// @custom:version 1.0.0 +contract LDADiamond { + constructor(address _contractOwner, address _diamondCutFacet) payable { + LibDiamond.setContractOwner(_contractOwner); + + // Add the diamondCut external function from the diamondCutFacet + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = IDiamondCut.diamondCut.selector; + cut[0] = LibDiamond.FacetCut({ + facetAddress: _diamondCutFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + LibDiamond.diamondCut(cut, address(0), ""); + } + + // Find facet for function that is called and execute the + // function if a facet is found and return any value. + // solhint-disable-next-line no-complex-fallback + fallback() external payable { + LibDiamond.DiamondStorage storage ds; + bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; + + // get diamond storage + // solhint-disable-next-line no-inline-assembly + assembly { + ds.slot := position + } + + // get facet from function selector + address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; + + if (facet == address(0)) { + revert LibDiamond.FunctionDoesNotExist(); + } + + // Execute external function from facet using delegatecall and return any value. + // solhint-disable-next-line no-inline-assembly + assembly { + // copy function selector and any arguments + calldatacopy(0, 0, calldatasize()) + // execute function call using the facet + let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + // get any return value + returndatacopy(0, 0, returndatasize()) + // return any return value or error back to the caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + // Able to receive ether + // solhint-disable-next-line no-empty-blocks + receive() external payable {} +} diff --git a/templates/facetTest.template.hbs b/templates/facetTest.template.hbs index 8b9771e31..e9d84b022 100644 --- a/templates/facetTest.template.hbs +++ b/templates/facetTest.template.hbs @@ -45,7 +45,7 @@ contract {{titleCase name}}FacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address({{camelCase name}}Facet), functionSelectors); + addFacet(address(diamond), address({{camelCase name}}Facet), functionSelectors); {{camelCase name}}Facet = Test{{titleCase name}}Facet(address(diamond)); {{camelCase name}}Facet.addDex(ADDRESS_UNISWAP); {{camelCase name}}Facet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AccessManagerFacet.t.sol b/test/solidity/Facets/AccessManagerFacet.t.sol index c2308850d..2b6d99c46 100644 --- a/test/solidity/Facets/AccessManagerFacet.t.sol +++ b/test/solidity/Facets/AccessManagerFacet.t.sol @@ -29,11 +29,19 @@ contract AccessManagerFacetTest is TestBase { allowedFunctionSelectors[1] = accessMgr .addressCanExecuteMethod .selector; - addFacet(diamond, address(accessMgr), allowedFunctionSelectors); + addFacet( + address(diamond), + address(accessMgr), + allowedFunctionSelectors + ); bytes4[] memory restrictedFunctionSelectors = new bytes4[](1); restrictedFunctionSelectors[0] = restricted.restrictedMethod.selector; - addFacet(diamond, address(restricted), restrictedFunctionSelectors); + addFacet( + address(diamond), + address(restricted), + restrictedFunctionSelectors + ); accessMgr = AccessManagerFacet(address(diamond)); restricted = RestrictedContract(address(diamond)); diff --git a/test/solidity/Facets/AcrossFacet.t.sol b/test/solidity/Facets/AcrossFacet.t.sol index d0ad2444d..a44332d55 100644 --- a/test/solidity/Facets/AcrossFacet.t.sol +++ b/test/solidity/Facets/AcrossFacet.t.sol @@ -50,7 +50,7 @@ contract AcrossFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(acrossFacet), functionSelectors); + addFacet(address(diamond), address(acrossFacet), functionSelectors); acrossFacet = TestAcrossFacet(address(diamond)); acrossFacet.addDex(ADDRESS_UNISWAP); acrossFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AcrossFacetPacked.t.sol b/test/solidity/Facets/AcrossFacetPacked.t.sol index bb12e9644..e3edb15f6 100644 --- a/test/solidity/Facets/AcrossFacetPacked.t.sol +++ b/test/solidity/Facets/AcrossFacetPacked.t.sol @@ -108,7 +108,11 @@ contract AcrossFacetPackedTest is TestBase { .selector; // add facet to diamond - addFacet(diamond, address(acrossFacetPacked), functionSelectors); + addFacet( + address(diamond), + address(acrossFacetPacked), + functionSelectors + ); acrossFacetPacked = AcrossFacetPacked(payable(address(diamond))); /// Prepare parameters diff --git a/test/solidity/Facets/AcrossFacetPackedV3.t.sol b/test/solidity/Facets/AcrossFacetPackedV3.t.sol index 0ccec4640..7c13b84b5 100644 --- a/test/solidity/Facets/AcrossFacetPackedV3.t.sol +++ b/test/solidity/Facets/AcrossFacetPackedV3.t.sol @@ -115,7 +115,11 @@ contract AcrossFacetPackedV3Test is TestBase { .selector; // add facet to diamond - addFacet(diamond, address(acrossFacetPackedV3), functionSelectors); + addFacet( + address(diamond), + address(acrossFacetPackedV3), + functionSelectors + ); acrossFacetPackedV3 = AcrossFacetPackedV3(payable(address(diamond))); /// Prepare parameters diff --git a/test/solidity/Facets/AcrossFacetV3.t.sol b/test/solidity/Facets/AcrossFacetV3.t.sol index 2d3c4911f..0f3910f57 100644 --- a/test/solidity/Facets/AcrossFacetV3.t.sol +++ b/test/solidity/Facets/AcrossFacetV3.t.sol @@ -56,7 +56,7 @@ contract AcrossFacetV3Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(acrossFacetV3), functionSelectors); + addFacet(address(diamond), address(acrossFacetV3), functionSelectors); acrossFacetV3 = TestAcrossFacetV3(address(diamond)); acrossFacetV3.addDex(ADDRESS_UNISWAP); acrossFacetV3.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/AllBridgeFacet.t.sol b/test/solidity/Facets/AllBridgeFacet.t.sol index b2c42cf5b..17aa5d692 100644 --- a/test/solidity/Facets/AllBridgeFacet.t.sol +++ b/test/solidity/Facets/AllBridgeFacet.t.sol @@ -78,7 +78,7 @@ contract AllBridgeFacetTest is TestBaseFacet, LiFiData { .selector; functionSelectors[4] = allBridgeFacet.getAllBridgeChainId.selector; - addFacet(diamond, address(allBridgeFacet), functionSelectors); + addFacet(address(diamond), address(allBridgeFacet), functionSelectors); allBridgeFacet = TestAllBridgeFacet(address(diamond)); allBridgeFacet.addDex(ADDRESS_UNISWAP); allBridgeFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol index 4d60827c3..ce1557b29 100644 --- a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol +++ b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol @@ -57,7 +57,11 @@ contract ArbitrumBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(arbitrumBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(arbitrumBridgeFacet), + functionSelectors + ); arbitrumBridgeFacet = TestArbitrumBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/CBridge/CBridge.t.sol b/test/solidity/Facets/CBridge/CBridge.t.sol index b7e5bedb9..2ace03982 100644 --- a/test/solidity/Facets/CBridge/CBridge.t.sol +++ b/test/solidity/Facets/CBridge/CBridge.t.sol @@ -89,7 +89,7 @@ contract CBridgeFacetTest is TestBaseFacet { functionSelectors[3] = cBridge.setFunctionApprovalBySignature.selector; functionSelectors[4] = cBridge.triggerRefund.selector; - addFacet(diamond, address(cBridge), functionSelectors); + addFacet(address(diamond), address(cBridge), functionSelectors); cBridge = TestCBridgeFacet(address(diamond)); cBridge.addDex(address(uniswap)); diff --git a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol index c892b350c..8fd7d1ff0 100644 --- a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol @@ -43,7 +43,7 @@ contract CBridgeAndFeeCollectionTest is TestBase { functionSelectors[2] = cBridge.addDex.selector; functionSelectors[3] = cBridge.setFunctionApprovalBySignature.selector; - addFacet(diamond, address(cBridge), functionSelectors); + addFacet(address(diamond), address(cBridge), functionSelectors); cBridge = TestCBridgeFacet(address(diamond)); cBridge.addDex(address(uniswap)); diff --git a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol index 5330e87f4..dca351729 100644 --- a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol @@ -86,7 +86,11 @@ contract CBridgeFacetPackedTest is TestBase { .selector; functionSelectors[8] = cBridgeFacetPacked.triggerRefund.selector; - addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); + addFacet( + address(diamond), + address(cBridgeFacetPacked), + functionSelectors + ); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare parameters diff --git a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol index 96b3faecb..227bb1854 100644 --- a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol @@ -93,7 +93,7 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { selector[0] = withdrawFacet.executeCallAndWithdraw.selector; vm.startPrank(OWNER_ADDRESS); - addFacet(diamond, address(withdrawFacet), selector); + addFacet(address(diamond), address(withdrawFacet), selector); vm.stopPrank(); withdrawFacet = WithdrawFacet(address(diamond)); diff --git a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol index 95c8d67bb..50424f46a 100644 --- a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol +++ b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol @@ -54,7 +54,11 @@ contract CelerCircleBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(celerCircleBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(celerCircleBridgeFacet), + functionSelectors + ); celerCircleBridgeFacet = TestCelerCircleBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/ChainflipFacet.t.sol b/test/solidity/Facets/ChainflipFacet.t.sol index 115b73697..972329d52 100644 --- a/test/solidity/Facets/ChainflipFacet.t.sol +++ b/test/solidity/Facets/ChainflipFacet.t.sol @@ -59,7 +59,7 @@ contract ChainflipFacetTest is TestBaseFacet, LiFiData { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(chainflipFacet), functionSelectors); + addFacet(address(diamond), address(chainflipFacet), functionSelectors); chainflipFacet = TestChainflipFacet(address(diamond)); chainflipFacet.addDex(ADDRESS_UNISWAP); chainflipFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/DeBridgeDlnFacet.t.sol b/test/solidity/Facets/DeBridgeDlnFacet.t.sol index d98da4bd8..a735101f5 100644 --- a/test/solidity/Facets/DeBridgeDlnFacet.t.sol +++ b/test/solidity/Facets/DeBridgeDlnFacet.t.sol @@ -62,7 +62,11 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { functionSelectors[5] = deBridgeDlnFacet.getDeBridgeChainId.selector; functionSelectors[6] = DeBridgeDlnFacet.initDeBridgeDln.selector; - addFacet(diamond, address(deBridgeDlnFacet), functionSelectors); + addFacet( + address(diamond), + address(deBridgeDlnFacet), + functionSelectors + ); deBridgeDlnFacet = TestDeBridgeDlnFacet(address(diamond)); deBridgeDlnFacet.addDex(ADDRESS_UNISWAP); deBridgeDlnFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/DexManagerFacet.t.sol b/test/solidity/Facets/DexManagerFacet.t.sol index 2cbfc7107..405755beb 100644 --- a/test/solidity/Facets/DexManagerFacet.t.sol +++ b/test/solidity/Facets/DexManagerFacet.t.sol @@ -49,7 +49,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { .selector; functionSelectors[7] = DexManagerFacet.isFunctionApproved.selector; - addFacet(diamond, address(dexMgr), functionSelectors); + addFacet(address(diamond), address(dexMgr), functionSelectors); // add AccessManagerFacet to be able to whitelist addresses for execution of protected functions accessMgr = new AccessManagerFacet(); @@ -57,7 +57,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { functionSelectors = new bytes4[](2); functionSelectors[0] = accessMgr.setCanExecute.selector; functionSelectors[1] = accessMgr.addressCanExecuteMethod.selector; - addFacet(diamond, address(accessMgr), functionSelectors); + addFacet(address(diamond), address(accessMgr), functionSelectors); accessMgr = AccessManagerFacet(address(diamond)); dexMgr = DexManagerFacet(address(diamond)); diff --git a/test/solidity/Facets/GasZipFacet.t.sol b/test/solidity/Facets/GasZipFacet.t.sol index af5eff276..10e0739a8 100644 --- a/test/solidity/Facets/GasZipFacet.t.sol +++ b/test/solidity/Facets/GasZipFacet.t.sol @@ -67,7 +67,7 @@ contract GasZipFacetTest is TestBaseFacet { functionSelectors[5] = gasZipFacet .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(gasZipFacet), functionSelectors); + addFacet(address(diamond), address(gasZipFacet), functionSelectors); gasZipFacet = TestGasZipFacet(payable(address(diamond))); diff --git a/test/solidity/Facets/GenericSwapFacet.t.sol b/test/solidity/Facets/GenericSwapFacet.t.sol index bedd98665..f90166377 100644 --- a/test/solidity/Facets/GenericSwapFacet.t.sol +++ b/test/solidity/Facets/GenericSwapFacet.t.sol @@ -40,7 +40,11 @@ contract GenericSwapFacetTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(genericSwapFacet), functionSelectors); + addFacet( + address(diamond), + address(genericSwapFacet), + functionSelectors + ); genericSwapFacet = TestGenericSwapFacet(address(diamond)); genericSwapFacet.addDex(address(uniswap)); diff --git a/test/solidity/Facets/GenericSwapFacetV3.t.sol b/test/solidity/Facets/GenericSwapFacetV3.t.sol index 7c9c4e089..5b2425685 100644 --- a/test/solidity/Facets/GenericSwapFacetV3.t.sol +++ b/test/solidity/Facets/GenericSwapFacetV3.t.sol @@ -73,7 +73,11 @@ contract GenericSwapFacetV3Test is TestBase { functionSelectors[3] = genericSwapFacet .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(genericSwapFacet), functionSelectors); + addFacet( + address(diamond), + address(genericSwapFacet), + functionSelectors + ); // add genericSwapFacet (v3) to diamond bytes4[] memory functionSelectorsV3 = new bytes4[](6); @@ -96,7 +100,11 @@ contract GenericSwapFacetV3Test is TestBase { .swapTokensMultipleV3NativeToERC20 .selector; - addFacet(diamond, address(genericSwapFacetV3), functionSelectorsV3); + addFacet( + address(diamond), + address(genericSwapFacetV3), + functionSelectorsV3 + ); genericSwapFacet = TestGenericSwapFacet(address(diamond)); genericSwapFacetV3 = TestGenericSwapFacetV3(address(diamond)); diff --git a/test/solidity/Facets/GlacisFacet.t.sol b/test/solidity/Facets/GlacisFacet.t.sol index 4426fbd2a..d82aed5b9 100644 --- a/test/solidity/Facets/GlacisFacet.t.sol +++ b/test/solidity/Facets/GlacisFacet.t.sol @@ -59,7 +59,7 @@ abstract contract GlacisFacetTestBase is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(glacisFacet), functionSelectors); + addFacet(address(diamond), address(glacisFacet), functionSelectors); glacisFacet = TestGlacisFacet(address(diamond)); glacisFacet.addDex(ADDRESS_UNISWAP); glacisFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/GnosisBridgeFacet.t.sol b/test/solidity/Facets/GnosisBridgeFacet.t.sol index d72ea4d4c..a594b8042 100644 --- a/test/solidity/Facets/GnosisBridgeFacet.t.sol +++ b/test/solidity/Facets/GnosisBridgeFacet.t.sol @@ -62,7 +62,11 @@ contract GnosisBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(gnosisBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(gnosisBridgeFacet), + functionSelectors + ); gnosisBridgeFacet = TestGnosisBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacet.t.sol b/test/solidity/Facets/HopFacet.t.sol index 392ec2495..fde6b92b5 100644 --- a/test/solidity/Facets/HopFacet.t.sol +++ b/test/solidity/Facets/HopFacet.t.sol @@ -54,7 +54,7 @@ contract HopFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](3); configs[0] = HopFacet.Config(ADDRESS_USDC, USDC_BRIDGE); @@ -247,7 +247,7 @@ contract HopFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond2, address(hopFacet2), functionSelectors); + addFacet(address(diamond2), address(hopFacet2), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](3); configs[0] = HopFacet.Config(ADDRESS_USDC, USDC_BRIDGE); @@ -282,7 +282,7 @@ contract HopFacetTest is TestBaseFacet { functionSelectors[2] = hopFacet2.initHop.selector; functionSelectors[3] = hopFacet2.registerBridge.selector; - addFacet(diamond, address(hopFacet2), functionSelectors); + addFacet(address(diamond), address(hopFacet2), functionSelectors); HopFacet.Config[] memory configs = new HopFacet.Config[](1); configs[0] = HopFacet.Config(addressUSDCPolygon, ammWrapperPolygon); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol index 5d1258289..929805cc2 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol @@ -56,7 +56,7 @@ contract HopFacetOptimizedL1Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); hopFacet = TestHopFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol index 2d5d78582..25ff28ef9 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol @@ -60,7 +60,7 @@ contract HopFacetOptimizedL2Test is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); hopFacet = TestHopFacet(address(diamond)); diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol index 5c2840fe2..ef7e3baab 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol @@ -113,7 +113,7 @@ contract HopFacetPackedL1Test is TestBase { .encode_startBridgeTokensViaHopL1ERC20Packed .selector; - addFacet(diamond, address(hopFacetPacked), functionSelectors); + addFacet(address(diamond), address(hopFacetPacked), functionSelectors); hopFacetPacked = HopFacetPacked(address(diamond)); /// Approval @@ -131,7 +131,7 @@ contract HopFacetPackedL1Test is TestBase { .setApprovalForBridges .selector; addFacet( - diamond, + address(diamond), address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol index d3e30cae7..4b1ded25a 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol @@ -120,7 +120,7 @@ contract HopFacetPackedL2Test is TestBase { .encode_startBridgeTokensViaHopL1ERC20Packed .selector; - addFacet(diamond, address(hopFacetPacked), functionSelectors); + addFacet(address(diamond), address(hopFacetPacked), functionSelectors); hopFacetPacked = HopFacetPacked(address(diamond)); /// Approval @@ -150,7 +150,7 @@ contract HopFacetPackedL2Test is TestBase { .setApprovalForBridges .selector; addFacet( - diamond, + address(diamond), address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Facets/MayanFacet.t.sol b/test/solidity/Facets/MayanFacet.t.sol index d56f8a5b3..2235203e1 100644 --- a/test/solidity/Facets/MayanFacet.t.sol +++ b/test/solidity/Facets/MayanFacet.t.sol @@ -111,7 +111,11 @@ contract MayanFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(mayanBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(mayanBridgeFacet), + functionSelectors + ); mayanBridgeFacet = TestMayanFacet(address(diamond)); mayanBridgeFacet.addDex(ADDRESS_UNISWAP); mayanBridgeFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol index ae3eab85c..eb353e8ce 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol @@ -53,7 +53,11 @@ contract OmniBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(omniBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(omniBridgeFacet), + functionSelectors + ); omniBridgeFacet = TestOmniBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol index 1c1e458b5..8f024af49 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol @@ -62,7 +62,11 @@ contract OmniBridgeL2FacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(omniBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(omniBridgeFacet), + functionSelectors + ); omniBridgeFacet = TestOmniBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/OptimismBridgeFacet.t.sol b/test/solidity/Facets/OptimismBridgeFacet.t.sol index c9e7c1b71..d15755ea3 100644 --- a/test/solidity/Facets/OptimismBridgeFacet.t.sol +++ b/test/solidity/Facets/OptimismBridgeFacet.t.sol @@ -63,7 +63,11 @@ contract OptimismBridgeFacetTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(optimismBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(optimismBridgeFacet), + functionSelectors + ); OptimismBridgeFacet.Config[] memory configs = new OptimismBridgeFacet.Config[](1); diff --git a/test/solidity/Facets/PioneerFacet.t.sol b/test/solidity/Facets/PioneerFacet.t.sol index 17143740b..4ff2e84e5 100644 --- a/test/solidity/Facets/PioneerFacet.t.sol +++ b/test/solidity/Facets/PioneerFacet.t.sol @@ -50,7 +50,11 @@ contract PioneerFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(basePioneerFacet), functionSelectors); + addFacet( + address(diamond), + address(basePioneerFacet), + functionSelectors + ); pioneerFacet = TestPioneerFacet(address(diamond)); pioneerFacet.addDex(ADDRESS_UNISWAP); pioneerFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/PolygonBridgeFacet.t.sol b/test/solidity/Facets/PolygonBridgeFacet.t.sol index dbecd9f57..bd3220d63 100644 --- a/test/solidity/Facets/PolygonBridgeFacet.t.sol +++ b/test/solidity/Facets/PolygonBridgeFacet.t.sol @@ -53,7 +53,11 @@ contract PolygonBridgeFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(polygonBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(polygonBridgeFacet), + functionSelectors + ); polygonBridgeFacet = TestPolygonBridgeFacet(address(diamond)); diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 133f64c2b..6d94e5db1 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -68,7 +68,7 @@ contract RelayFacetTest is TestBaseFacet, LiFiData { functionSelectors[4] = relayFacet.getMappedChainId.selector; functionSelectors[5] = relayFacet.setConsumedId.selector; - addFacet(diamond, address(relayFacet), functionSelectors); + addFacet(address(diamond), address(relayFacet), functionSelectors); relayFacet = TestRelayFacet(address(diamond)); relayFacet.addDex(ADDRESS_UNISWAP); relayFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/SquidFacet.t.sol b/test/solidity/Facets/SquidFacet.t.sol index 76354282f..9b371c28b 100644 --- a/test/solidity/Facets/SquidFacet.t.sol +++ b/test/solidity/Facets/SquidFacet.t.sol @@ -58,7 +58,7 @@ contract SquidFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(squidFacet), functionSelectors); + addFacet(address(diamond), address(squidFacet), functionSelectors); squidFacet = TestSquidFacet(address(diamond)); squidFacet.addDex(ADDRESS_UNISWAP); squidFacet.setFunctionApprovalBySignature( diff --git a/test/solidity/Facets/StargateFacetV2.t.sol b/test/solidity/Facets/StargateFacetV2.t.sol index 299513b7a..1bc9cb4a9 100644 --- a/test/solidity/Facets/StargateFacetV2.t.sol +++ b/test/solidity/Facets/StargateFacetV2.t.sol @@ -75,7 +75,11 @@ contract StargateFacetV2Test is TestBaseFacet { .selector; functionSelectors[4] = stargateFacetV2.tokenMessaging.selector; - addFacet(diamond, address(stargateFacetV2), functionSelectors); + addFacet( + address(diamond), + address(stargateFacetV2), + functionSelectors + ); stargateFacetV2 = TestStargateFacetV2(payable(address(diamond))); // whitelist DEX and feeCollector addresses and function selectors in diamond diff --git a/test/solidity/Facets/SymbiosisFacet.t.sol b/test/solidity/Facets/SymbiosisFacet.t.sol index 7742e213d..2beb9cb19 100644 --- a/test/solidity/Facets/SymbiosisFacet.t.sol +++ b/test/solidity/Facets/SymbiosisFacet.t.sol @@ -49,7 +49,7 @@ contract SymbiosisFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(symbiosisFacet), functionSelectors); + addFacet(address(diamond), address(symbiosisFacet), functionSelectors); symbiosisFacet = TestSymbiosisFacet(address(diamond)); diff --git a/test/solidity/Facets/ThorSwapFacet.t.sol b/test/solidity/Facets/ThorSwapFacet.t.sol index 2d8aff2e3..5174a7d3e 100644 --- a/test/solidity/Facets/ThorSwapFacet.t.sol +++ b/test/solidity/Facets/ThorSwapFacet.t.sol @@ -43,7 +43,7 @@ contract ThorSwapFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(thorSwapFacet), functionSelectors); + addFacet(address(diamond), address(thorSwapFacet), functionSelectors); thorSwapFacet = TestThorSwapFacet(address(diamond)); thorSwapFacet.addDex(ADDRESS_UNISWAP); diff --git a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol index d492404c7..667b60185 100644 --- a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol @@ -57,7 +57,11 @@ contract CBridgeGasARBTest is TestBase { .encode_startBridgeTokensViaCBridgeERC20Packed .selector; - addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); + addFacet( + address(diamond), + address(cBridgeFacetPacked), + functionSelectors + ); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare parameters diff --git a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol index 43810c59f..06e98c078 100644 --- a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol @@ -71,7 +71,11 @@ contract CBridgeGasETHTest is TestBase { .encode_startBridgeTokensViaCBridgeERC20Packed .selector; - addFacet(diamond, address(cBridgeFacetPacked), functionSelectors); + addFacet( + address(diamond), + address(cBridgeFacetPacked), + functionSelectors + ); cBridgeFacetPacked = CBridgeFacetPacked(payable(address(diamond))); /// Perpare CBridgeFacet @@ -82,7 +86,7 @@ contract CBridgeGasETHTest is TestBase { .startBridgeTokensViaCBridge .selector; - addFacet(diamond, address(cBridgeFacet), functionSelectors2); + addFacet(address(diamond), address(cBridgeFacet), functionSelectors2); cBridgeFacet = CBridgeFacet(address(diamond)); /// Perpare parameters @@ -170,7 +174,7 @@ contract CBridgeGasETHTest is TestBase { .setApprovalForBridges .selector; addFacet( - diamond, + address(diamond), address(hopFacetOptimized), functionSelectorsApproval ); diff --git a/test/solidity/Gas/Hop.t.sol b/test/solidity/Gas/Hop.t.sol index 5785539ef..3dcaf0431 100644 --- a/test/solidity/Gas/Hop.t.sol +++ b/test/solidity/Gas/Hop.t.sol @@ -25,7 +25,7 @@ contract HopGasTest is TestBase { functionSelectors[0] = hopFacet.initHop.selector; functionSelectors[1] = hopFacet.startBridgeTokensViaHop.selector; - addFacet(diamond, address(hopFacet), functionSelectors); + addFacet(address(diamond), address(hopFacet), functionSelectors); hopFacet = HopFacet(address(diamond)); HopFacet.Config[] memory config = new HopFacet.Config[](1); diff --git a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol index e590bbc04..8a2ed50bd 100644 --- a/test/solidity/Gas/HopFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedARB.gas.t.sol @@ -98,7 +98,7 @@ pragma solidity ^0.8.17; // .encoder_startBridgeTokensViaHopL2ERC20Packed // .selector; -// addFacet(diamond, address(hopFacetPacked), functionSelectors); +// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol index 38c992f81..e707c644b 100644 --- a/test/solidity/Gas/HopFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedETH.gas.t.sol @@ -70,7 +70,7 @@ pragma solidity ^0.8.17; // .startBridgeTokensViaHopL1ERC20Min // .selector; -// addFacet(diamond, address(hopFacetPacked), functionSelectors); +// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol index daefa092a..8f0e894ee 100644 --- a/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol +++ b/test/solidity/Gas/HopFacetPackedPOL.gas.t.sol @@ -99,7 +99,7 @@ pragma solidity ^0.8.17; // .encoder_startBridgeTokensViaHopL2ERC20Packed // .selector; -// addFacet(diamond, address(hopFacetPacked), functionSelectors); +// addFacet(address(diamond), address(hopFacetPacked), functionSelectors); // hopFacetPacked = HopFacetPacked(address(diamond)); // /// Perpare HopFacetOptimized & Approval diff --git a/test/solidity/Helpers/SwapperV2.t.sol b/test/solidity/Helpers/SwapperV2.t.sol index 2a6a177cc..218ba838b 100644 --- a/test/solidity/Helpers/SwapperV2.t.sol +++ b/test/solidity/Helpers/SwapperV2.t.sol @@ -51,7 +51,7 @@ contract SwapperV2Test is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(swapper), functionSelectors); + addFacet(address(diamond), address(swapper), functionSelectors); swapper = TestSwapperV2(address(diamond)); swapper.addDex(address(amm)); diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 03fe4323e..76ba452b8 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -471,7 +471,11 @@ contract GasZipPeripheryTest is TestBase { .setFunctionApprovalBySignature .selector; - addFacet(diamond, address(_gnosisBridgeFacet), functionSelectors); + addFacet( + address(diamond), + address(_gnosisBridgeFacet), + functionSelectors + ); _gnosisBridgeFacet = TestGnosisBridgeFacet(address(diamond)); diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 592164ca0..9ae9721a7 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -78,7 +78,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { coreRouteFacet = new CoreRouteFacet(USER_DIAMOND_OWNER); bytes4[] memory selectors = new bytes4[](1); selectors[0] = CoreRouteFacet.processRoute.selector; - addFacet(ldaDiamond, address(coreRouteFacet), selectors); + addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); coreRouteFacet = CoreRouteFacet(payable(address(ldaDiamond))); } diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 3af155725..bb7655042 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -121,7 +121,7 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { bytes4[] memory functionSelectors ) = _createFacetAndSelectors(); - addFacet(ldaDiamond, facetAddress, functionSelectors); + addFacet(address(ldaDiamond), facetAddress, functionSelectors); _setFacetInstance(payable(address(ldaDiamond))); } diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 4b035fb95..5f875a766 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -21,7 +21,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { MockPullERC20Facet mockPull = new MockPullERC20Facet(); bytes4[] memory sel = new bytes4[](1); sel[0] = MockPullERC20Facet.pull.selector; - addFacet(ldaDiamond, address(mockPull), sel); + addFacet(address(ldaDiamond), address(mockPull), sel); pullSel = sel[0]; } @@ -30,14 +30,14 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { MockNativeFacet mock = new MockNativeFacet(); bytes4[] memory selectors = new bytes4[](1); selectors[0] = MockNativeFacet.handleNative.selector; - addFacet(ldaDiamond, address(mock), selectors); + addFacet(address(ldaDiamond), address(mock), selectors); } function _addMockPullFacet() internal returns (bytes4 sel) { MockPullERC20Facet mock = new MockPullERC20Facet(); bytes4[] memory selectors = new bytes4[](1); selectors[0] = MockPullERC20Facet.pull.selector; - addFacet(ldaDiamond, address(mock), selectors); + addFacet(address(ldaDiamond), address(mock), selectors); return selectors[0]; } diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 8fcd780f4..84e1c8933 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; +import { LDADiamond } from "lifi/Periphery/Lda/LDADiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; @@ -11,7 +11,7 @@ import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { - LiFiDiamond internal ldaDiamond; + LDADiamond internal ldaDiamond; function setUp() public virtual { ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER, USER_PAUSER); @@ -20,7 +20,7 @@ contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { function createLdaDiamond( address _diamondOwner, address _pauserWallet - ) internal returns (LiFiDiamond) { + ) internal returns (LDADiamond) { vm.startPrank(_diamondOwner); DiamondCutFacet diamondCut = new DiamondCutFacet(); DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); @@ -28,7 +28,7 @@ contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { EmergencyPauseFacet emergencyPause = new EmergencyPauseFacet( _pauserWallet ); - LiFiDiamond diamond = new LiFiDiamond( + LDADiamond diamond = new LDADiamond( _diamondOwner, address(diamondCut) ); diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol index 56cb2e768..031710864 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol @@ -99,7 +99,7 @@ contract LidoWrapperTest is TestBase, LiFiData { functionSelectors[4] = relayFacet.getMappedChainId.selector; functionSelectors[5] = relayFacet.setConsumedId.selector; - addFacet(diamond, address(relayFacet), functionSelectors); + addFacet(address(diamond), address(relayFacet), functionSelectors); // slither-disable-next-line reentrancy-no-eth relayFacet = TestRelayFacet(address(diamond)); @@ -505,7 +505,11 @@ contract LidoWrapperTest is TestBase, LiFiData { .selector; // add facet to diamond and store diamond with facet interface in variable - addFacet(diamond, address(genericSwapFacetV3), functionSelectors); + addFacet( + address(diamond), + address(genericSwapFacetV3), + functionSelectors + ); genericSwapFacetV3 = GenericSwapFacetV3(address(diamond)); } } diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol index 31c59f473..db577e68d 100644 --- a/test/solidity/utils/BaseDiamondTest.sol +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -5,7 +5,6 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { Test } from "forge-std/Test.sol"; abstract contract BaseDiamondTest is Test { @@ -51,7 +50,7 @@ abstract contract BaseDiamondTest is Test { } function addFacet( - LiFiDiamond _diamond, + address _diamond, address _facet, bytes4[] memory _selectors ) public virtual { @@ -59,7 +58,7 @@ abstract contract BaseDiamondTest is Test { } function addFacet( - LiFiDiamond _diamond, + address _diamond, address _facet, bytes4[] memory _selectors, address _init, @@ -69,13 +68,13 @@ abstract contract BaseDiamondTest is Test { } function _addFacet( - LiFiDiamond _diamond, + address _diamond, address _facet, bytes4[] memory _selectors, address _init, bytes memory _initCallData ) internal virtual { - vm.startPrank(OwnershipFacet(address(_diamond)).owner()); + vm.startPrank(OwnershipFacet(_diamond).owner()); cut.push( LibDiamond.FacetCut({ facetAddress: _facet, @@ -84,11 +83,7 @@ abstract contract BaseDiamondTest is Test { }) ); - DiamondCutFacet(address(_diamond)).diamondCut( - cut, - _init, - _initCallData - ); + DiamondCutFacet(_diamond).diamondCut(cut, _init, _initCallData); delete cut; vm.stopPrank(); From 88b61d124ad24bb6405519d8206a245c2b67973d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 17:13:16 +0200 Subject: [PATCH 071/220] improved documentation --- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 114 ++++++++++++-- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 143 +++++++++++------- .../Lda/BaseUniV3StyleDexFacet.t.sol | 50 +++++- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 120 ++++++++++++++- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 33 +++- .../Lda/Facets/EnosysDexV3Facet.t.sol | 7 + .../Lda/Facets/HyperswapV3Facet.t.sol | 8 + .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 34 ++++- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 6 + .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 8 + .../Lda/Facets/SyncSwapV2Facet.t.sol | 22 ++- .../Lda/Facets/VelodromeV2Facet.t.sol | 39 ++++- .../Periphery/Lda/Facets/XSwapV3Facet.t.sol | 8 + .../Periphery/Lda/utils/LdaDiamondTest.sol | 14 +- test/solidity/utils/BaseDiamondTest.sol | 33 +++- 15 files changed, 563 insertions(+), 76 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 9ae9721a7..a20787106 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -6,12 +6,22 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; - -abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { +import { LDADiamondTest } from "./utils/LDADiamondTest.sol"; + +/// @title BaseCoreRouteTest +/// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. +/// @dev Offers: +/// - Flexible route building for single/multi-hop +/// - Event expectations helpers +/// - Overloads of `_executeAndVerifySwap` including revert path +/// Concrete tests compose these helpers to succinctly define swap scenarios. +abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { using SafeERC20 for IERC20; // ==== Types ==== + + /// @notice Command types recognized by CoreRouteFacet route parser. + /// @dev Controls how `processRoute` resolves the source of funds. enum CommandType { None, // 0 - not used ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) @@ -21,6 +31,14 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ApplyPermit // 5 - applyPermit } + /// @notice Generic event expectation shape for verifying external protocol emissions alongside Route. + /// @param checkTopic1 Whether to check topic1 (indexed param #1). + /// @param checkTopic2 Whether to check topic2 (indexed param #2). + /// @param checkTopic3 Whether to check topic3 (indexed param #3). + /// @param checkData Whether to check event data (non-indexed params). + /// @param eventSelector keccak256 hash of the event signature. + /// @param eventParams Params encoded as abi.encode(param) each; indexed must be exactly 32 bytes. + /// @param indexedParamIndices Indices of params that are indexed (map to topics 1..3). struct ExpectedEvent { bool checkTopic1; bool checkTopic2; @@ -31,11 +49,22 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { uint8[] indexedParamIndices; // indices of params that are indexed (→ topics 1..3) } + /// @notice Tuning for verifying the core `Route` event. + /// @param expectedExactOut Set >0 to match exact amountOut, otherwise only structure is validated. + /// @param checkData Whether to validate event data payload. struct RouteEventVerification { uint256 expectedExactOut; // Only for event verification bool checkData; } + /// @notice Parameters passed to `_buildBaseRoute` and `_executeAndVerifySwap`. + /// @param tokenIn Input token address (or NATIVE constant). + /// @param tokenOut Output token address (or NATIVE constant). + /// @param amountIn Input amount. + /// @param minOut Minimum acceptable output amount (slippage). + /// @param sender Logical sender of the funds for this hop. + /// @param recipient Receiver of the swap proceeds. + /// @param commandType Command determining source of funds. struct SwapTestParams { address tokenIn; address tokenOut; @@ -47,12 +76,25 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { } // ==== Constants ==== + + /// @notice Denotes 100% share in route encoding. uint16 internal constant FULL_SHARE = 65535; // ==== Variables ==== + + /// @notice Proxy handle to CoreRouteFacet on the test diamond. CoreRouteFacet internal coreRouteFacet; // ==== Events ==== + + /// @notice Emitted by CoreRouteFacet upon route processing completion. + /// @param from Sender address (user or synthetic if aggregator-funded). + /// @param to Recipient address. + /// @param tokenIn Input token. + /// @param tokenOut Output token. + /// @param amountIn Input amount. + /// @param amountOutMin Min acceptable output amount. + /// @param amountOut Actual output amount. event Route( address indexed from, address to, @@ -64,16 +106,25 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); // ==== Errors ==== - error InvalidTopicLength(); + + /// @notice Thrown if an event expectation includes >3 indexed params. error TooManyIndexedParams(); + /// @notice Thrown when building topics from non-32-byte encoded params. + error InvalidTopicLength(); + /// @notice Thrown when data verification encounters dynamic params (unsupported). error DynamicParamsNotSupported(); // ==== Setup Functions ==== + + /// @notice Deploys and attaches `CoreRouteFacet` to the diamond under test. + /// @dev Invoked from `setUp` of child tests via inheritance chain. function setUp() public virtual override { - LdaDiamondTest.setUp(); + LDADiamondTest.setUp(); _addCoreRouteFacet(); } + /// @notice Internal helper to deploy CoreRouteFacet and add its `processRoute` selector. + /// @dev Sets `coreRouteFacet` to the diamond proxy after cut. function _addCoreRouteFacet() internal { coreRouteFacet = new CoreRouteFacet(USER_DIAMOND_OWNER); bytes4[] memory selectors = new bytes4[](1); @@ -83,6 +134,18 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { } // ==== Helper Functions ==== + + /// @notice Builds a base route-blob for a single hop given `SwapTestParams` and `swapData`. + /// @param params Swap parameters including command type. + /// @param swapData DEX-specific data (usually starts with facet.swapX.selector). + /// @return Encoded hop bytes to be concatenated for multi-hop or passed directly for single-hop. + /// @dev Format depends on command: + /// - ProcessNative: [cmd(1)][numPools(1)=1][share(2)=FULL][len(2)][data] + /// - ProcessOnePool: [cmd(1)][tokenIn(20)][len(2)][data] + /// - Others (User/MyERC20): [cmd(1)][tokenIn(20)][numPools(1)=1][share(2)=FULL][len(2)][data] + /// @custom:example Single-hop user ERC20 + /// bytes memory data = abi.encodePacked(facet.swapUniV2.selector, pool, uint8(1), recipient); + /// bytes memory route = _buildBaseRoute(params, data); function _buildBaseRoute( SwapTestParams memory params, bytes memory swapData @@ -117,6 +180,13 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { } } + /// @notice Executes a built route and verifies balances and events. + /// @param params Swap params; if ProcessMyERC20, measures in/out at the diamond. + /// @param route Pre-built route bytes (single or multi-hop). + /// @param additionalEvents Additional external events to expect. + /// @param isFeeOnTransferToken Whether tokenIn is fee-on-transfer (tolerates off-by-1 spent). + /// @param routeEventVerification Route event check configuration (exact out optional). + /// @dev Approves tokenIn if not aggregator-funded. Emits and verifies Route event. function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -223,6 +293,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { assertGt(outAfter - outBefore, 0, "Should receive tokens"); } + /// @notice Convenience overload for `_executeAndVerifySwap` without exact-out check. function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -238,6 +309,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } + /// @notice Convenience overload for `_executeAndVerifySwap` with only params and route. function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route @@ -251,6 +323,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } + /// @notice Convenience overload for `_executeAndVerifySwap` with fee-on-transfer toggle. function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -265,7 +338,13 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } - // Keep the revert case separate + /// @notice Executes route expecting a specific revert error selector. + /// @param params Swap params; for aggregator funds, the helper deliberately uses amountIn-1 to trigger errors. + /// @param route Pre-built route bytes. + /// @param expectedRevert Error selector expected from `processRoute`. + /// @dev Example: + /// vm.expectRevert(Errors.SwapCallbackNotExecuted.selector); + /// _executeAndVerifySwap(params, route, Errors.SwapCallbackNotExecuted.selector); function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -291,7 +370,13 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } - /// @dev Helper that builds route and executes swap in one call + /// @notice Helper that builds route and executes swap in one call, with extended verification options. + /// @param params SwapTestParams for building and executing. + /// @param swapData DEX-specific swap data to pack. + /// @param expectedEvents Additional events to expect. + /// @param expectRevert Treats token as fee-on-transfer to adjust spent checking if true. + /// @param verification Route event verification configuration. + /// @dev Primarily used by complex tests to keep scenario assembly terse. function _buildRouteAndExecuteSwap( SwapTestParams memory params, bytes memory swapData, @@ -309,7 +394,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } - /// @dev Overload with default parameters for simple cases + /// @notice Overload: builds route and runs default execution checks. function _buildRouteAndExecuteSwap( SwapTestParams memory params, bytes memory swapData @@ -323,7 +408,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } - /// @dev Overload for revert cases + /// @notice Overload: builds route and expects a revert. function _buildRouteAndExecuteSwap( SwapTestParams memory params, bytes memory swapData, @@ -333,7 +418,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { _executeAndVerifySwap(params, route, expectedRevert); } - /// @dev Overload matching _executeAndVerifySwap's 4-parameter signature + /// @notice Overload: builds route and runs with fee-on-transfer toggle and extra events. function _buildRouteAndExecuteSwap( SwapTestParams memory params, bytes memory swapData, @@ -349,7 +434,9 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { ); } - // helper: load a 32-byte topic from a 32-byte abi.encode(param) + /// @notice Helper to load a topic value from a 32-byte abi.encode(param). + /// @param enc A 32-byte abi-encoded static param. + /// @return topic The bytes32 topic. function _asTopic(bytes memory enc) internal pure returns (bytes32 topic) { if (enc.length != 32) revert InvalidTopicLength(); assembly { @@ -360,6 +447,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { /** * @notice Sets up event expectations for a list of events * @param events Array of events to expect + * @dev Each `ExpectedEvent` can independently toggle checking indexed topics and data. */ function _expectEvents(ExpectedEvent[] memory events) internal { for (uint256 i = 0; i < events.length; i++) { @@ -370,6 +458,10 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { /** * @notice Sets up expectation for a single event * @param evt The event to expect with its check parameters and data + * @dev Builds the right number of topics based on `indexedParamIndices`, and an ABI-packed data + * payload of non-indexed params (static only). + * @custom:error TooManyIndexedParams if more than 3 indexed params are specified. + * @custom:error DynamicParamsNotSupported if any non-indexed param is not 32 bytes. */ function _expectEvent(ExpectedEvent memory evt) internal { vm.expectEmit( diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index bb7655042..712e05ff1 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -8,73 +8,115 @@ import { stdJson } from "forge-std/StdJson.sol"; /** * @title BaseDEXFacetTest - * @notice Base test contract with common functionality and abstractions for DEX-specific tests + * @notice Base test contract with common functionality and abstractions for DEX-specific tests. + * @dev Child tests implement the virtual hooks to: + * - choose fork/network + * - set pool/token addresses + * - deploy and register their DEX facet + callback (if applicable) + * + * Usage: + * - Inherit and implement `_setupForkConfig`, `_setupDexEnv`, + * and facet creation hooks. + * - Call core helpers like `_buildMultiHopRoute` and `_addDexFacet`. */ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { using SafeERC20 for IERC20; // ==== Types ==== + + /// @notice Encapsulates a fork target used by setUp(). + /// @param networkName A key into config/networks.json and an ENV suffix for ETH_NODE_URI_. + /// @param blockNumber Block height to fork at. struct ForkConfig { string networkName; uint256 blockNumber; } + /// @notice Describes swap direction for two-token pools. + /// @dev TokenXToTokenY values map to pool-specific zeroForOne flags. enum SwapDirection { Token1ToToken0, // 0 Token0ToToken1 // 1 } - struct RouteParams { - CommandType commandType; - address tokenIn; - uint8 numPools; // defaults to 1 - uint16 share; // defaults to FULL_SHARE - bytes swapData; - } - // ==== Events ==== - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); // ==== Errors ==== + + /// @notice Thrown when an expected pool reserve shape is not met in setup. error WrongPoolReserves(); + /// @notice Thrown when a required on-chain pool does not exist. error PoolDoesNotExist(); + /// @notice Thrown when hopParams and hopData arrays differ in length. error ParamsDataLengthMismatch(); + /// @notice Thrown when no hops were provided to `_buildMultiHopRoute`. error NoHopsProvided(); + /// @notice Thrown when setUp fork config is invalid. + /// @param reason A human-readable reason. error InvalidForkConfig(string reason); + /// @notice Thrown when networkName is not found in config/networks.json. + /// @param name The missing network name. error UnknownNetwork(string name); + // Custom errors for abstract test functions + /// @notice Thrown when test_CanSwap is not implemented by child contract. + error TestCanSwapNotImplemented(); + /// @notice Thrown when test_CanSwap_FromDexAggregator is not implemented by child contract. + error TestCanSwapFromDexAggregatorNotImplemented(); + /// @notice Thrown when test_CanSwap_MultiHop is not implemented by child contract. + error TestCanSwapMultiHopNotImplemented(); + /// @notice Thrown when testRevert_CallbackFromUnexpectedSender is not implemented by child contract. + error TestRevertCallbackFromUnexpectedSenderNotImplemented(); + /// @notice Thrown when testRevert_SwapWithoutCallback is not implemented by child contract. + error TestRevertSwapWithoutCallbackNotImplemented(); + // ==== Storage Variables ==== + + /// @notice Active fork settings for this test. ForkConfig internal forkConfig; + /// @notice Primary input token for single-hop tests. IERC20 internal tokenIn; + /// @notice Optional middle token for multi-hop tests. IERC20 internal tokenMid; // optional for multi-hop + /// @notice Primary output token for single-hop tests. IERC20 internal tokenOut; + /// @notice Pool for tokenIn->tokenOut single-hop routes or UniV2-style. address internal poolInOut; // for single hop or UniV2-style + /// @notice Pool for hop 1 in multi-hop tests. address internal poolInMid; // for hop 1 + /// @notice Pool for hop 2 in multi-hop tests. address internal poolMidOut; // for hop 2 - // Optional flag/hook for aggregator slot-undrain behavior - bool internal aggregatorUndrainMinusOne; - // ==== Virtual Functions ==== + + /// @notice Child must deploy its facet and return its function selectors. + /// @return facetAddress Address of the facet implementation. + /// @return functionSelectors Selectors to add to the diamond. function _createFacetAndSelectors() internal virtual returns (address, bytes4[] memory); + /// @notice Child must set its facet instance to the diamond proxy. + /// @param facetAddress Address of the diamond proxy that now exposes the facet. function _setFacetInstance(address payable facetAddress) internal virtual; + /// @notice Child must configure tokens/pools for the selected fork. function _setupDexEnv() internal virtual; + /// @notice Child must set `forkConfig` with target network + block. function _setupForkConfig() internal virtual; // ==== Setup Functions ==== + + /// @notice Forks the configured network, validates ENV, and attaches the DEX facet to LDA. + /// @dev Validates: + /// - ENV var ETH_NODE_URI_ + /// - key existence in config/networks.json + /// - non-zero block number + /// Then forks and sets up the DEX env and adds the DEX facet to the LDA diamond. function setUp() public virtual override { _setupForkConfig(); @@ -115,6 +157,10 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { } // ==== Internal Functions ==== + + /// @notice Deploys and adds the child DEX facet to the LDA diamond. + /// @dev Uses `_createFacetAndSelectors` hook to deploy and collect selectors, + /// performs diamondCut, and then updates the facet instance to the diamond proxy via `_setFacetInstance`. function _addDexFacet() internal virtual { ( address facetAddress, @@ -127,7 +173,10 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { } // ==== Helper Functions ==== - // helper to uppercase ASCII + + /// @notice Converts lowercase ASCII to uppercase; leaves non-letters unmodified. + /// @param s Input string. + /// @return Uppercased string. function _convertToUpperCase( string memory s ) internal pure returns (string memory) { @@ -141,7 +190,9 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { return string(b); } - // optional: ensure key exists in config/networks.json + /// @notice Ensures a network key exists in `config/networks.json`. + /// @param name Network name to validate. + /// @dev Reads `..name` and reverts if missing. function _ensureNetworkExists(string memory name) internal { // will revert if the key path is missing string memory json = vm.readFile("config/networks.json"); @@ -153,6 +204,13 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { } } + /// @notice Concatenates multiple base routes into a single multi-hop route for `processRoute`. + /// @param hopParams Array of hop parameters (tokenIn/out, amountIn, sender/recipient, command type). + /// @param hopData Array of corresponding DEX-specific swap data for each hop. + /// @return Concatenated route bytes suitable for `CoreRouteFacet.processRoute`. + /// @dev Reverts if arrays mismatch or empty. Example: + /// - Hop0: user ERC20 A -> aggregator, UniV2.pool0 + /// - Hop1: aggregator ERC20 B -> user, Algebra.pool1 (supports fee-on-transfer) function _buildMultiHopRoute( SwapTestParams[] memory hopParams, bytes[] memory hopData @@ -171,6 +229,8 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { return route; } + /// @notice Default amount for `tokenIn` used by derived tests. + /// @return Default amount, override to adapt per pool/tokenIn/decimals. function _getDefaultAmountForTokenIn() internal pure @@ -181,57 +241,36 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { } // ==== Abstract Test Cases ==== - /** - * @notice Abstract test for basic token swapping functionality - * Each DEX implementation should override this - */ + + /// @notice Abstract test stub: must be implemented by concrete DEX tests to validate basic swapping. function test_CanSwap() public virtual { // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap: Not implemented"); + revert TestCanSwapNotImplemented(); } - /** - * @notice Abstract test for swapping tokens from the DEX aggregator - * Each DEX implementation should override this - */ + /// @notice Abstract test stub: must be implemented by concrete DEX tests to validate aggregator-funded swap. function test_CanSwap_FromDexAggregator() public virtual { // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_FromDexAggregator: Not implemented"); + revert TestCanSwapFromDexAggregatorNotImplemented(); } - /** - * @notice Abstract test for multi-hop swapping - * Each DEX implementation should override this - */ + /// @notice Abstract test stub: must be implemented by concrete DEX tests to validate multi-hop routing. function test_CanSwap_MultiHop() public virtual { // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_MultiHop: Not implemented"); + revert TestCanSwapMultiHopNotImplemented(); } - /** - * @notice Abstract test for verifying callback protection against unauthorized calls - * @dev DEX implementations with callbacks must override this - * DEXs without callbacks should leave this empty - */ + /// @notice Abstract test stub: for DEXes with callbacks, ensure callback cannot be called by unexpected senders. function testRevert_CallbackFromUnexpectedSender() public virtual { // Each DEX implementation with callbacks must override this // DEXs without callbacks should leave this empty - // solhint-disable-next-line gas-custom-errors - revert("testRevert_CallbackFromUnexpectedSender: Not implemented"); + revert TestRevertCallbackFromUnexpectedSenderNotImplemented(); } - /** - * @notice Abstract test for verifying swaps fail if callback is not executed - * @dev DEX implementations with callbacks must override this - * DEXs without callbacks should leave this empty - */ + /// @notice Abstract test stub: for DEXes with callbacks, ensure swap reverts if callback is not executed. function testRevert_SwapWithoutCallback() public virtual { // Each DEX implementation with callbacks must override this // DEXs without callbacks should leave this empty - // solhint-disable-next-line gas-custom-errors - revert("testRevert_SwapWithoutCallback: Not implemented"); + revert TestRevertSwapWithoutCallbackNotImplemented(); } } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 2e17ded3a..6b9971fd6 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -5,25 +5,45 @@ import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; +/// @title BaseUniV3StyleDEXFacetTest +/// @notice Shared UniV3-style testing helpers built atop BaseDEXFacetWithCallbackTest. +/// @dev Handles selector wiring, pool direction inference (token0/token1), and auto-execution flows. abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { + /// @notice UniV3-style facet proxy handle (points to diamond after setup). UniV3StyleFacet internal uniV3Facet; // ==== Types ==== + + /// @notice Parameters for a single UniV3-style swap step. + /// @param pool Target pool address. + /// @param direction Direction of the swap (token0->token1 or vice versa). + /// @param recipient Recipient of the proceeds. struct UniV3SwapParams { address pool; SwapDirection direction; address recipient; } + /// @notice Parameters for convenience auto-execution. + /// @param commandType Whether to use aggregator funds or user funds. + /// @param amountIn Input amount to test with. struct UniV3AutoSwapParams { CommandType commandType; uint256 amountIn; } // ==== Errors ==== + + /// @notice Thrown when a pool does not include the provided token. + /// @param token Token address not found in pool. + /// @param pool Pool address. error TokenNotInPool(address token, address pool); // ==== Setup Functions ==== + + /// @notice Deploys UniV3Style facet and registers `swapUniV3` + DEX-specific callback selector. + /// @return facetAddress Address of the deployed facet implementation. + /// @return functionSelectors Selectors for swap and callback. function _createFacetAndSelectors() internal override @@ -36,6 +56,8 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { return (address(uniV3Facet), functionSelectors); } + /// @notice Sets `uniV3Facet` to the diamond proxy (post-cut). + /// @param facetAddress Diamond proxy address. function _setFacetInstance( address payable facetAddress ) internal override { @@ -43,6 +65,12 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { } // ==== Helper Functions ==== + + /// @notice Builds packed swap data for UniV3-style swap dispatch. + /// @param params Struct including pool, direction and recipient. + /// @return Packed payload starting with `swapUniV3` selector. + /// @custom:example + /// bytes memory data = _buildUniV3SwapData(UniV3SwapParams(pool, dir, user)); function _buildUniV3SwapData( UniV3SwapParams memory params ) internal view returns (bytes memory) { @@ -55,6 +83,11 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { ); } + /// @notice Executes a UniV3-style swap for arbitrary pool and direction. + /// @param params Swap test params (sender/recipient/funding mode). + /// @param pool Pool to swap on. + /// @param direction Swap direction to use. + /// @dev Funds sender or diamond accordingly, builds route and executes with default assertions. function _executeUniV3StyleSwap( SwapTestParams memory params, address pool, @@ -84,7 +117,10 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } - // Infer swap direction from pool's token0/token1 and TOKEN_IN + /// @notice Infers direction (token0->token1 or token1->token0) given a pool and `tokenIn`. + /// @param pool The target UniV3-style pool. + /// @param tokenIn The input token address. + /// @return Inferred direction. function _getDirection( address pool, address tokenIn @@ -96,6 +132,9 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { revert TokenNotInPool(tokenIn, pool); } + /// @notice Convenience flow: infers direction from `poolInOut` and executes with given funding mode. + /// @param params commandType + amountIn + /// @dev Funds sender or diamond, builds route for `poolInOut`, and executes. function _executeUniV3StyleSwapAuto( UniV3AutoSwapParams memory params ) internal { @@ -152,6 +191,11 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { } // ==== Overrides ==== + + /// @notice Builds callback-arming swap data for `BaseDEXFacetWithCallbackTest` harness. + /// @param pool Pool to invoke against in callback tests. + /// @param recipient Receiver for proceeds in these tests. + /// @return Packed swap payload. function _buildCallbackSwapData( address pool, address recipient @@ -167,6 +211,8 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { } // ==== Test Cases ==== + + /// @notice Intentionally skipped: UniV3 multi-hop unsupported due to amountSpecified=0 limitation on second hop. function test_CanSwap_MultiHop() public virtual override { // SKIPPED: UniV3 forke dex multi-hop unsupported due to AS (amount specified) requirement. // UniV3 forke dex does not support a "one-pool" second hop today, @@ -176,6 +222,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { // in a single processRoute invocation. } + /// @notice User-funded single-hop swap on UniV3-style pool inferred from `poolInOut`. function test_CanSwap() public virtual override { _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ @@ -185,6 +232,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { ); } + /// @notice Aggregator-funded single-hop swap on UniV3-style. function test_CanSwap_FromDexAggregator() public virtual override { _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 253bfb961..9fc02e1e5 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -13,18 +13,39 @@ import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; +/// @title AlgebraFacetTest +/// @notice Forked tests for Algebra pools integrated via LDA CoreRoute. +/// @dev Covers: +/// - user-funded and aggregator-funded swaps +/// - fee-on-transfer compatibility +/// - multi-hop routes combining user and aggregator steps +/// - callback protection and revert scenarios contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { + /// @notice Facet proxy handle exposed on the diamond. AlgebraFacet internal algebraFacet; // ==== Constants ==== + + /// @notice Algebra factory on ApeChain used during fork tests. address private constant ALGEBRA_FACTORY_APECHAIN = 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; + /// @notice Algebra QuoterV2 on ApeChain used to get expected outputs. address private constant ALGEBRA_QUOTER_V2_APECHAIN = 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; + /// @notice Random APE_ETH holder used to fund aggregator or users in fork tests. address private constant RANDOM_APE_ETH_HOLDER_APECHAIN = address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); // ==== Types ==== + + /// @notice Parameters for the high-level `_testSwap` helper. + /// @param from Logical sender (user or aggregator/diamond). + /// @param to Recipient of proceeds. + /// @param tokenIn Input token. + /// @param amountIn Input amount. + /// @param tokenOut Output token. + /// @param direction Direction (Token0->Token1 or reverse). + /// @param supportsFeeOnTransfer Toggle fee-on-transfer compatibility in swap data. struct AlgebraSwapTestParams { address from; address to; @@ -35,6 +56,15 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { bool supportsFeeOnTransfer; } + /// @notice Local multi-hop test state used to assemble two Algebra pools and tokens. + /// @param tokenA First token. + /// @param tokenB Middle token (can be fee-on-transfer). + /// @param tokenC Final token. + /// @param pool1 Pool for A<->B. + /// @param pool2 Pool for B<->C. + /// @param amountIn User's first-hop input amount. + /// @param amountToTransfer Funding amount for user. + /// @param isFeeOnTransfer Whether tokenB charges fees. struct MultiHopTestState { IERC20 tokenA; IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken @@ -46,6 +76,12 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { bool isFeeOnTransfer; } + /// @notice Parameters to pack swap data for AlgebraFacet. + /// @param commandCode Command determining source of funds. + /// @param tokenIn Input token address. + /// @param recipient Recipient address. + /// @param pool Algebra pool. + /// @param supportsFeeOnTransfer Toggle fee-on-transfer handling. struct AlgebraRouteParams { CommandType commandCode; // 1 for contract funds, 2 for user funds address tokenIn; // Input token address @@ -55,6 +91,8 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { } // ==== Setup Functions ==== + + /// @notice Selects `apechain` fork and block height used in tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "apechain", @@ -62,6 +100,9 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { }); } + /// @notice Deploys AlgebraFacet and registers `swapAlgebra` and `algebraSwapCallback`. + /// @return facetAddress Implementation address. + /// @return functionSelectors Selectors list (swap + callback). function _createFacetAndSelectors() internal override @@ -74,18 +115,23 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { return (address(algebraFacet), functionSelectors); } + /// @notice Binds `algebraFacet` to the diamond proxy after diamondCut. + /// @param facetAddress Diamond proxy address. function _setFacetInstance( address payable facetAddress ) internal override { algebraFacet = AlgebraFacet(facetAddress); } + /// @notice Sets fork-based token/pool addresses used by tests. + /// @dev tokenIn/tokenOut chosen for ApeChain, poolInOut resolved accordingly. function _setupDexEnv() internal override { tokenIn = IERC20(0xcF800F4948D16F23333508191B1B1591daF70438); // APE_ETH_TOKEN tokenOut = IERC20(0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949); // WETH_TOKEN poolInOut = 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; // ALGEBRA_POOL_APECHAIN } + /// @notice Default input amount adapted to 6 decimals for APE_ETH on ApeChain. function _getDefaultAmountForTokenIn() internal pure @@ -96,6 +142,9 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { } // ==== Test Cases ==== + + /// @notice Aggregator-funded swap on Algebra using funds transferred from a whale. + /// @dev Transfers APE_ETH to `coreRouteFacet` (aggregator) and executes swap to `USER_SENDER`. function test_CanSwap_FromDexAggregator() public override { // Fund LDA from whale address vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); @@ -121,6 +170,8 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice User-funded swap with fee-on-transfer compatibility enabled. + /// @dev Uses `supportsFeeOnTransfer=true` to relax spending assertion in helper. function test_CanSwap_FeeOnTransferToken() public { vm.startPrank(RANDOM_APE_ETH_HOLDER_APECHAIN); @@ -153,6 +204,8 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice User-funded single-hop swap on Algebra (forward direction). + /// @dev Transfers funds from whale to user and executes swap to same user; enables fee on transfer support. function test_CanSwap() public override { vm.startPrank(RANDOM_APE_ETH_HOLDER_APECHAIN); @@ -179,6 +232,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice Reverse-direction swap roundtrip test to ensure pool works both ways. function test_CanSwap_Reverse() public { test_CanSwap(); @@ -201,6 +255,10 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice Multi-hop user->aggregator swap with fee-on-transfer on middle token. + /// @dev Constructs two pools locally, adds liquidity, and executes: + /// - Hop1: user A->B to aggregator + /// - Hop2: aggregator B->C to user function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { MultiHopTestState memory state; state.isFeeOnTransfer = true; @@ -212,6 +270,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { _executeAndVerifyMultiHopSwap(state); } + /// @notice Multi-hop user->aggregator swap with regular mid token (no fee). function test_CanSwap_MultiHop() public override { MultiHopTestState memory state; state.isFeeOnTransfer = false; @@ -223,6 +282,8 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { _executeAndVerifyMultiHopSwap(state); } + /// @notice Ensures swap reverts if Algebra callback is never invoked by the pool. + /// @dev Deploys a no-callback mock pool and verifies `SwapCallbackNotExecuted`. function testRevert_SwapWithoutCallback() public override { // Pool that does not call back for Algebra address mockPool = _deployNoCallbackPool(); // your Algebra-specific or shared mock @@ -255,6 +316,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice Validates revert when pool is zero address in swap data. function testRevert_AlgebraSwap_ZeroAddressPool() public { // Transfer tokens from whale to user vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); @@ -300,11 +362,15 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { // ==== Overrides ==== + /// @notice Returns Algebra-specific callback selector for the base callback tests. function _getCallbackSelector() internal view override returns (bytes4) { return algebraFacet.algebraSwapCallback.selector; } - // Hook: build Algebra swap data [pool, direction(uint8), recipient, supportsFeeOnTransfer(uint8)] + /// @notice Hook: build Algebra swap data [pool, direction(uint8), recipient, supportsFeeOnTransfer(uint8)] + /// @param pool Pool to be used for callback arming tests. + /// @param recipient Recipient address. + /// @return swapData Packed bytes starting with `swapAlgebra` selector. function _buildCallbackSwapData( address pool, address recipient @@ -319,6 +385,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { ); } + /// @notice Validates revert when recipient is zero address in swap data. function testRevert_AlgebraSwap_ZeroAddressRecipient() public { // Transfer tokens from whale to user vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); @@ -361,6 +428,10 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { } // ==== Helper Functions ==== + + /// @notice Creates 2 local Algebra pools and seeds liquidity for multi-hop tests. + /// @param state Preallocated struct; `isFeeOnTransfer` toggles mid token behavior. + /// @return Updated state with tokens/pools and funding applied. function _setupTokensAndPools( MultiHopTestState memory state ) private returns (MultiHopTestState memory) { @@ -445,6 +516,8 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { return state; } + /// @notice Executes a two-hop route A->B (user) then B->C (aggregator). + /// @param state Prepared state from `_setupTokensAndPools`. function _executeAndVerifyMultiHopSwap( MultiHopTestState memory state ) private { @@ -516,6 +589,10 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice Creates an Algebra pool via on-chain factory (fork path). + /// @param tokenA First token. + /// @param tokenB Second token. + /// @return pool New pool address. function _createAlgebraPool( address tokenA, address tokenB @@ -528,6 +605,11 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { return pool; } + /// @notice Adds liquidity to the given Algebra pool using available token balances. + /// @param pool Pool to seed. + /// @param token0 token0 address. + /// @param token1 token1 address. + /// @dev Uses a helper that safely tolerates fee-on-transfer behavior. function _addLiquidityToPool( address pool, address token0, @@ -584,6 +666,9 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { ); } + /// @notice Builds Algebra swap data payload for a hop. + /// @param params Route parameters used to determine direction and toggle fee on transfer token support. + /// @return Packed bytes for `swapAlgebra`. function _buildAlgebraSwapData( AlgebraRouteParams memory params ) private view returns (bytes memory) { @@ -604,6 +689,9 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { ); } + /// @notice High-level helper that performs a fork-based Algebra swap and verifies balances. + /// @param params See `AlgebraSwapTestParams`. + /// @dev Uses QuoterV2 to get expectedOut and sets minOut = expected - 1 for tolerance. function _testSwap(AlgebraSwapTestParams memory params) internal { // Find or create a pool address pool = _getPool(params.tokenIn, params.tokenOut); @@ -666,6 +754,10 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { ); } + /// @notice Resolves pool by pair via router/factory and reverts if missing. + /// @param tokenA First token. + /// @param tokenB Second token. + /// @return pool Pool address. function _getPool( address tokenA, address tokenB @@ -678,6 +770,11 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { return pool; } + /// @notice Quotes expected output using Algebra QuoterV2 for exactInputSingle. + /// @param tokenIn Input token. + /// @param tokenOut Output token. + /// @param amountIn Input amount. + /// @return amountOut Quote for amount out. function _getQuoteExactInput( address tokenIn, address tokenOut, @@ -689,16 +786,33 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { } } +/// @title AlgebraLiquidityAdderHelper +/// @notice Adds liquidity to an Algebra pool using balances available on this helper. +/// @dev Implements `algebraMintCallback` to transfer owed amounts to the pool. contract AlgebraLiquidityAdderHelper { + /// @notice token0 used by the target pool. address public immutable TOKEN_0; + /// @notice token1 used by the target pool. address public immutable TOKEN_1; + /// @notice Sets immutable token references. + /// @param _token0 token0 address. + /// @param _token1 token1 address. constructor(address _token0, address _token1) { TOKEN_0 = _token0; TOKEN_1 = _token1; } // ==== External Functions ==== + + /// @notice Adds liquidity by calling Algebra pool `mint` and returning actual spend. + /// @param pool Pool address. + /// @param bottomTick Lower tick. + /// @param topTick Upper tick. + /// @param amount Desired liquidity amount. + /// @return amount0 Actual token0 spent. + /// @return amount1 Actual token1 spent. + /// @return liquidityActual Actual liquidity minted. function addLiquidity( address pool, int24 bottomTick, @@ -733,6 +847,10 @@ contract AlgebraLiquidityAdderHelper { return (amount0, amount1, liquidityActual); } + /// @notice Callback invoked by Algebra pool to collect owed tokens during mint. + /// @param amount0Owed Owed token0. + /// @param amount1Owed Owed token1. + /// @dev Transfers up to current balance for each owed amount and ensures non-zero effect. function algebraMintCallback( uint256 amount0Owed, uint256 amount1Owed, diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 5f875a766..87bd60d7e 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -9,12 +9,19 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; +/// @title CoreRouteFacetTest +/// @notice Tests the CoreRouteFacet's command parser, permit handling, and minimal invariants. +/// @dev Adds small mock “facets” that emulate pull/native handlers for exercising route execution. contract CoreRouteFacetTest is BaseCoreRouteTest { using SafeTransferLib for address; + /// @notice Cached selector for the mock pull facet to simplify route building in tests. bytes4 internal pullSel; // ==== Setup Functions ==== + + /// @notice Registers a mock pull facet once and stores its selector for reuse. + /// @dev Also calls parent `setUp` to add CoreRouteFacet to the diamond. function setUp() public override { super.setUp(); // Register mock pull facet once and store selector @@ -26,6 +33,8 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { } // ==== Helper Functions ==== + + /// @notice Adds a mock native handler facet to the diamond for ProcessNative tests. function _addMockNativeFacet() internal { MockNativeFacet mock = new MockNativeFacet(); bytes4[] memory selectors = new bytes4[](1); @@ -33,6 +42,8 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { addFacet(address(ldaDiamond), address(mock), selectors); } + /// @notice Adds a mock pull facet to the diamond and returns its selector. + /// @return sel Selector of the mock pull function. function _addMockPullFacet() internal returns (bytes4 sel) { MockPullERC20Facet mock = new MockPullERC20Facet(); bytes4[] memory selectors = new bytes4[](1); @@ -41,6 +52,16 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { return selectors[0]; } + /// @notice Signs an EIP-2612 permit for a mock token and returns the ECDSA tuple. + /// @param token Permit-enabled mock token. + /// @param ownerPk Private key of the owner (forge-anvil test key). + /// @param owner Owner address. + /// @param spender Spender to approve (diamond in our tests). + /// @param value Allowance value. + /// @param deadline Permit deadline. + /// @return v ECDSA v + /// @return r ECDSA r + /// @return s ECDSA s function _signPermit( ERC20PermitMock token, uint256 ownerPk, @@ -69,6 +90,8 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { } // ==== Test Cases ==== + + /// @notice Sanity-checks deployment wiring and ownership. function test_ContractIsSetUpCorrectly() public { // Test that owner is set correctly assertEq( @@ -78,11 +101,14 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); } + /// @notice Constructor must revert on zero owner; verifies InvalidConfig. function testRevert_WhenConstructedWithZeroAddress() public { vm.expectRevert(InvalidConfig.selector); new CoreRouteFacet(address(0)); } + /// @notice Verifies ProcessNative command passes ETH to recipient and emits Route with exact out. + /// @dev Builds a route with a mock native handler and funds USER_SENDER with 1 ETH. function test_ProcessNativeCommandSendsEthToRecipient() public { _addMockNativeFacet(); @@ -123,6 +149,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); } + /// @notice Applies an EIP-2612 permit via ApplyPermit command and verifies allowance on diamond. function test_ApplyPermitCommandSetsAllowanceOnDiamond() public { uint256 ownerPk = 0xA11CE; address owner = vm.addr(ownerPk); @@ -173,6 +200,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); } + /// @notice Unknown command codes should revert; verifies UnknownCommandCode error. function testRevert_WhenCommandCodeIsUnknown() public { bytes memory route = abi.encodePacked(uint8(9)); @@ -188,6 +216,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); } + /// @notice Unknown selectors in a step should revert; verifies UnknownSelector. function testRevert_WhenSelectorIsUnknown() public { ERC20PermitMock token = new ERC20PermitMock( "Mock2", @@ -224,7 +253,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); } - // TokenInSpendingExceeded: trigger by charging the user twice via two ProcessUserERC20 steps. + /// @notice TokenInSpendingExceeded: trigger by charging the user twice via two ProcessUserERC20 steps. function testRevert_WhenInputBalanceIsInsufficientForTwoSteps() public { // Prepare token and approvals uint256 amountIn = 1e18; @@ -274,7 +303,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { vm.stopPrank(); } - // Same as above but with tokenOut set to an ERC20, to ensure path-independent behavior. + /// @notice Same as above but with tokenOut set to an ERC20, to ensure path-independent behavior. function testRevert_WhenInputBalanceIsInsufficientForTwoStepsWithERC20Out() public { diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 0e4ed6f49..1ff0754fd 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -5,8 +5,13 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +/// @title EnosysDEXV3FacetTest +/// @notice Forked UniV3-style tests for Enosys DEX V3 pools via LDA route. +/// @dev Configures Flare network and a concrete pool pair; inherits execution helpers from the base. contract EnosysDEXV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== + + /// @notice Selects Flare fork and block height used for deterministic tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "flare", @@ -14,10 +19,12 @@ contract EnosysDEXV3FacetTest is BaseUniV3StyleDEXFacetTest { }); } + /// @notice Returns Enosys-specific UniV3 swap callback selector. function _getCallbackSelector() internal pure override returns (bytes4) { return UniV3StyleFacet.enosysdexV3SwapCallback.selector; } + /// @notice Sets tokenIn/out and pool for Enosys V3 USDT0 pair on Flare. function _setupDexEnv() internal override { tokenIn = IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); // HLN tokenOut = IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); // USDT0 diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 0fff6ced6..fc495c2c0 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -6,8 +6,13 @@ import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +/// @title HyperswapV3FacetTest +/// @notice Fork-based UniV3-style tests for HyperswapV3 integration. +/// @dev Selects Hyperevm fork, sets pool/token addresses, and delegates logic to base UniV3 test helpers. contract HyperswapV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== + + /// @notice Selects `hyperevm` network and block for fork tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "hyperevm", @@ -15,10 +20,12 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDEXFacetTest { }); } + /// @notice Returns the Hyperswap-specific callback selector expected to be invoked by pools. function _getCallbackSelector() internal pure override returns (bytes4) { return UniV3StyleFacet.hyperswapV3SwapCallback.selector; } + /// @notice Resolves tokenIn/out and pool address for Hyperswap V3 USDT0/WHYPE pair. function _setupDexEnv() internal override { tokenIn = IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); // USDT0 tokenOut = IERC20(0x5555555555555555555555555555555555555555); // WHYPE @@ -27,6 +34,7 @@ contract HyperswapV3FacetTest is BaseUniV3StyleDEXFacetTest { ).getPool(address(tokenIn), address(tokenOut), 3000); } + /// @notice Default input amount adapted to 6 decimals for USDT0 on Hyperevm. function _getDefaultAmountForTokenIn() internal pure diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index e1528bbf1..c5f34f4bd 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -7,10 +7,19 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; +/// @title IzumiV3FacetTest +/// @notice Forked + local tests for Izumi V3 pools routed through LDA. +/// @dev Validates swap paths, aggregator/user flows, multi-hop, callback auth, and revert cases. contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { + /// @notice Facet proxy handle bound to diamond after facet cut. IzumiV3Facet internal izumiV3Facet; // ==== Types ==== + + /// @notice Parameters for a single Izumi V3 swap step. + /// @param pool Target pool. + /// @param direction Direction of the swap. + /// @param recipient Address receiving the proceeds. struct IzumiV3SwapParams { address pool; SwapDirection direction; @@ -18,9 +27,13 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { } // ==== Errors ==== + + /// @notice Emitted when callback amounts are non-positive (guard against bad pool behavior). error IzumiV3SwapCallbackNotPositiveAmount(); // ==== Setup Functions ==== + + /// @notice Selects Base fork and block height used by the tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "base", @@ -28,6 +41,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { }); } + /// @notice Deploys facet and returns swap + callback selectors for diamond cut. function _createFacetAndSelectors() internal override @@ -41,12 +55,14 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { return (address(izumiV3Facet), functionSelectors); } + /// @notice Sets `izumiV3Facet` to the diamond proxy. function _setFacetInstance( address payable facetAddress ) internal override { izumiV3Facet = IzumiV3Facet(facetAddress); } + /// @notice Defines a USDC/WETH/USDB_C path and pools on Base for tests. function _setupDexEnv() internal override { tokenIn = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); // USDC tokenMid = IERC20(0x4200000000000000000000000000000000000006); // WETH @@ -55,6 +71,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { poolMidOut = 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; // WETH/USDB_C } + /// @notice Default amount for USDC (6 decimals) used in tests. function _getDefaultAmountForTokenIn() internal pure @@ -65,14 +82,20 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { } // ==== Callback Test Hooks ==== + + /// @notice Chooses which callback to use under the base callback tests. function _getCallbackSelector() internal view override returns (bytes4) { return izumiV3Facet.swapX2YCallback.selector; } + /// @notice Supplies a no-callback pool for negative tests. function _deployNoCallbackPool() internal override returns (address) { return address(new MockNoCallbackPool()); } + /// @notice Encodes swap payload for callback arming tests. + /// @param pool Pool to use. + /// @param recipient Recipient of proceeds. function _buildCallbackSwapData( address pool, address recipient @@ -87,6 +110,8 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { } // ==== Test Cases ==== + + /// @notice User-funded swap USDC->WETH on poolInMid, sending to USER_RECEIVER. function test_CanSwap() public override { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -116,6 +141,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice Aggregator-funded swap USDC->WETH on poolInMid to USER_SENDER. function test_CanSwap_FromDexAggregator() public override { // Test USDC -> WETH deal( @@ -150,6 +176,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } + /// @notice Multi-hop user→aggregator USDC->WETH->USDB_C flow. function test_CanSwap_MultiHop() public override { // Fund the sender with tokens uint256 amountIn = _getDefaultAmountForTokenIn(); @@ -221,7 +248,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } - // ==== Revert Cases ==== + /// @notice Negative test: callback should revert when amounts are not positive. function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -236,6 +263,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { izumiV3Facet.swapY2XCallback(0, 0, abi.encode(tokenIn)); } + /// @notice Negative test: too-large amount encodings must revert with InvalidCallData. function testRevert_FailsIfAmountInIsTooLarge() public { deal(address(tokenMid), USER_SENDER, type(uint256).max); @@ -267,6 +295,10 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { } // ==== Helper Functions ==== + + /// @notice Encodes Izumi V3 swap payloads for route steps. + /// @param params Pool/direction/recipient. + /// @return Packed calldata for `swapIzumiV3`. function _buildIzumiV3SwapData( IzumiV3SwapParams memory params ) internal view returns (bytes memory) { diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 354e60cd1..eddd22115 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -5,7 +5,11 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +/// @title LaminarV3FacetTest +/// @notice Hyperevm UniV3-style tests for Laminar pools via LDA. +/// @dev Minimal setup; inherits all execution helpers from the base. contract LaminarV3FacetTest is BaseUniV3StyleDEXFacetTest { + /// @notice Selects Hyperevm fork and block used by tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "hyperevm", @@ -13,10 +17,12 @@ contract LaminarV3FacetTest is BaseUniV3StyleDEXFacetTest { }); } + /// @notice Returns Laminar V3 callback selector used during swaps. function _getCallbackSelector() internal pure override returns (bytes4) { return UniV3StyleFacet.laminarV3SwapCallback.selector; } + /// @notice Sets tokenIn/out and pool for Laminar V3 on Hyperevm. function _setupDexEnv() internal override { tokenIn = IERC20(0x5555555555555555555555555555555555555555); // WHYPE tokenOut = IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); // LHYPE diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 60502b9eb..e34b2f561 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -6,7 +6,11 @@ import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +/// @title RabbitSwapV3FacetTest +/// @notice Viction UniV3-style tests for RabbitSwap V3 pools. +/// @dev Covers invalid pool/recipient edge cases plus standard setup. contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { + /// @notice Selects Viction fork and block height used in tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "viction", @@ -14,16 +18,19 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { }); } + /// @notice Returns RabbitSwap V3 callback selector used during swaps. function _getCallbackSelector() internal pure override returns (bytes4) { return UniV3StyleFacet.rabbitSwapV3SwapCallback.selector; } + /// @notice Sets tokenIn/out and pool address for RabbitSwap V3 on Viction. function _setupDexEnv() internal override { tokenIn = IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); // SOROS tokenOut = IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); // C98 poolInOut = 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; // pool } + /// @notice Negative: zero pool must be rejected by facet call data validation. function testRevert_RabbitSwapInvalidPool() public { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -55,6 +62,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { vm.stopPrank(); } + /// @notice Negative: zero recipient must be rejected by facet call data validation. function testRevert_RabbitSwapInvalidRecipient() public { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 33aed60c0..d0edd8489 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -6,15 +6,22 @@ import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +/// @title SyncSwapV2FacetTest +/// @notice Linea SyncSwap V2 tests via LDA route; includes both v1 and v2 pool wiring. +/// @dev Verifies single-hop, aggregator flow, multi-hop (with ProcessOnePool), and revert paths. contract SyncSwapV2FacetTest is BaseDEXFacetTest { + /// @notice Facet proxy for swaps bound to the diamond after setup. SyncSwapV2Facet internal syncSwapV2Facet; + /// @notice SyncSwap vault address used by V1 pools. address internal constant SYNC_SWAP_VAULT = address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); + /// @notice A Linea v2 pool used by specific tests. address internal constant USDC_WETH_POOL_V2 = address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); + /// @notice Selects Linea fork and block height used by tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "linea", @@ -22,6 +29,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }); } + /// @notice Deploys SyncSwapV2Facet and returns its swap selector for diamond cut. function _createFacetAndSelectors() internal override @@ -33,12 +41,14 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { return (address(syncSwapV2Facet), functionSelectors); } + /// @notice Sets the facet instance to the diamond proxy after facet cut. function _setFacetInstance( address payable facetAddress ) internal override { syncSwapV2Facet = SyncSwapV2Facet(facetAddress); } + /// @notice Defines tokens and pools used by tests (WETH/USDC/USDT). function _setupDexEnv() internal override { tokenIn = IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); // WETH tokenMid = IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); // USDC @@ -47,7 +57,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { poolMidOut = 0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2; // USDC-USDT V1 } - /// @notice Single‐pool swap: USER sends WETH → receives USDC + /// @notice Single‐pool swap: USER sends WETH → receives USDC. function test_CanSwap() public override { // Transfer 1 000 WETH from whale to USER_SENDER deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -80,6 +90,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice User-funded swap on a V2 pool variant. function test_CanSwap_PoolV2() public { // Transfer 1 000 WETH from whale to USER_SENDER deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -112,6 +123,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Aggregator-funded swap on a V1 pool; uses amountIn-1 for undrain protection. function test_CanSwap_FromDexAggregator() public override { // Fund the aggregator with 1 000 WETH deal( @@ -148,6 +160,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Aggregator-funded swap on a V2 pool; same undrain behavior. function test_CanSwap_FromDexAggregator_PoolV2() public { // Fund the aggregator with 1 000 WETH deal( @@ -184,6 +197,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Multi-hop WETH->USDC (v1) then USDC->USDT (v1) where hop2 consumes hop1 outputs. function test_CanSwap_MultiHop() public override { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -258,6 +272,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice V1 pools require non-zero vault; zero must revert. function testRevert_V1PoolMissingVaultAddress() public { // Transfer 1 000 WETH from whale to USER_SENDER deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -291,6 +306,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Invalid pool/recipient combinations should revert. function testRevert_InvalidPoolOrRecipient() public { // Transfer 1 000 WETH from whale to USER_SENDER deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -348,6 +364,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Only withdrawMode 0/1/2 are supported; invalid modes must revert. function testRevert_InvalidWithdrawMode() public { vm.startPrank(USER_SENDER); @@ -391,6 +408,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { // SyncSwapV2 does not use callbacks - test intentionally empty } + /// @notice SyncSwap V2 swap parameter shape used for `swapSyncSwapV2`. struct SyncSwapV2SwapParams { address pool; address to; @@ -399,6 +417,8 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { address vault; } + /// @notice Builds SyncSwapV2 swap payload for route steps. + /// @param params pool/to/withdrawMode/isV1Pool/vault tuple. function _buildSyncSwapV2SwapData( SyncSwapV2SwapParams memory params ) internal view returns (bytes memory) { diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 7bd64a5bd..c17973b7e 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -10,24 +10,33 @@ import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; +/// @title VelodromeV2FacetTest +/// @notice Optimism Velodrome V2 tests covering stable/volatile pools, aggregator/user flows, multi-hop, and precise reserve accounting. +/// @dev Includes a flashloan callback path to assert event expectations and reserve deltas. contract VelodromeV2FacetTest is BaseDEXFacetTest { + /// @notice Facet proxy bound to the diamond after setup. VelodromeV2Facet internal velodromeV2Facet; // ==== Constants ==== + /// @notice Router used to compute amounts and resolve pools. IVelodromeV2Router internal constant VELODROME_V2_ROUTER = IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router + /// @notice Factory registry used by the router for pool lookup. address internal constant VELODROME_V2_FACTORY_REGISTRY = 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; + /// @notice Mock receiver for exercising the pool's flashloan callback hook. MockVelodromeV2FlashLoanCallbackReceiver internal mockFlashloanCallbackReceiver; // ==== Types ==== + /// @notice Enables/disables the flashloan callback during swap. enum CallbackStatus { Disabled, // 0 Enabled // 1 } + /// @notice Encapsulates a single Velodrome V2 swap request under test. struct VelodromeV2SwapTestParams { address from; address to; @@ -39,6 +48,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { CallbackStatus callbackStatus; } + /// @notice Parameters and precomputed amounts used by multi-hop tests. struct MultiHopTestParams { address tokenIn; address tokenMid; @@ -51,6 +61,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { uint256 pool2Fee; } + /// @notice Snapshot of reserve states across two pools for before/after assertions. struct ReserveState { uint256 reserve0Pool1; uint256 reserve1Pool1; @@ -58,6 +69,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { uint256 reserve1Pool2; } + /// @notice Swap data payload packed for VelodromeV2Facet. struct VelodromeV2SwapData { address pool; SwapDirection direction; @@ -66,6 +78,8 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { } // ==== Setup Functions ==== + + /// @notice Picks Optimism fork and block height. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "optimism", @@ -73,6 +87,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { }); } + /// @notice Deploys facet and returns its swap selector. function _createFacetAndSelectors() internal override @@ -84,12 +99,14 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { return (address(velodromeV2Facet), functionSelectors); } + /// @notice Sets the facet instance to the diamond proxy. function _setFacetInstance( address payable facetAddress ) internal override { velodromeV2Facet = VelodromeV2Facet(facetAddress); } + /// @notice Assigns tokens used in tests; pool addresses are resolved per-test from the router. function _setupDexEnv() internal override { tokenIn = IERC20(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); // USDC tokenMid = IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); // STG @@ -97,6 +114,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { // pools vary by test; and they are fetched inside tests } + /// @notice Default amount for 6-decimal tokens on Optimism. function _getDefaultAmountForTokenIn() internal pure @@ -606,8 +624,9 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { // ==== Helper Functions ==== /** - * @dev Helper function to test a VelodromeV2 swap. - * Uses a struct to group parameters and reduce stack depth. + * @notice Helper to execute a VelodromeV2 swap with optional callback expectation and strict event checking. + * @param params The swap request including direction and whether callback is enabled. + * @dev Computes expected outputs via router, builds payload, and asserts Route + optional HookCalled event. */ function _testSwap(VelodromeV2SwapTestParams memory params) internal { // get expected output amounts from the router. @@ -706,7 +725,13 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { ); } - // Helper function to set up routes and get amounts + /// @notice Builds routes and computes amounts for two-hop paths using the router. + /// @param tokenIn First hop input. + /// @param tokenMid Intermediate token between hops. + /// @param tokenOut Final output token. + /// @param isStableFirst Whether hop1 uses a stable pool. + /// @param isStableSecond Whether hop2 uses a stable pool. + /// @return params MultiHopTestParams including pool addresses, amounts and pool fees. function _setupRoutes( address tokenIn, address tokenMid, @@ -772,6 +797,9 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { return params; } + /// @notice Encodes swap payload for VelodromeV2Facet.swapVelodromeV2. + /// @param params pool/direction/recipient/callback status. + /// @return Packed bytes payload. function _buildVelodromeV2SwapData( VelodromeV2SwapData memory params ) private pure returns (bytes memory) { @@ -785,6 +813,9 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { ); } + /// @notice Verifies exact reserve deltas on both pools against computed amounts and fees. + /// @param params Multi-hop parameters returned by `_setupRoutes`. + /// @param initialReserves Reserves captured before the swap. function _verifyReserves( MultiHopTestParams memory params, ReserveState memory initialReserves @@ -868,6 +899,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { // ==== Events ==== + /// @notice Emitted by the mock to validate callback plumbing during tests. event HookCalled( address sender, uint256 amount0, @@ -875,6 +907,7 @@ contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { bytes data ); + /// @notice Simple hook that emits `HookCalled` with passthrough data. function hook( address sender, uint256 amount0, diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index 5c11c4a14..de9e6299e 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -5,22 +5,30 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +/// @title XSwapV3FacetTest +/// @notice XDC chain UniV3-style tests for XSwap V3 integration via LDA. +/// @dev Minimal setup; inherits execution logic from base UniV3-style test harness. contract XSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== + + /// @notice Selects XDC fork and block height used by the tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ networkName: "xdc", blockNumber: 89279495 }); } + /// @notice Returns the XSwap V3 callback selector used during swaps. function _getCallbackSelector() internal pure override returns (bytes4) { return UniV3StyleFacet.xswapCallback.selector; } + /// @notice Sets tokenIn/out and the pool for XSwap V3 on XDC. function _setupDexEnv() internal override { tokenIn = IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); // USDC.e tokenOut = IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); // WXDC poolInOut = 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; // pool } + /// @notice Default input amount for USDC.e (6 decimals). function _getDefaultAmountForTokenIn() internal pure diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 84e1c8933..50007a89a 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -10,13 +10,23 @@ import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; -contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { +/// @title LDADiamondTest +/// @notice Spins up a minimal LDA Diamond with loupe, ownership, and emergency pause facets for periphery tests. +/// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. +contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { + /// @notice The diamond proxy under test. LDADiamond internal ldaDiamond; + /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. + /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. function setUp() public virtual { ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER, USER_PAUSER); } + /// @notice Creates an LDA diamond and wires up Loupe, Ownership and EmergencyPause facets. + /// @param _diamondOwner Owner address for the diamond. + /// @param _pauserWallet Pauser address for the emergency pause facet. + /// @return diamond The newly created diamond instance. function createLdaDiamond( address _diamondOwner, address _pauserWallet @@ -41,7 +51,7 @@ contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { // Add PeripheryRegistry TODO?!?!? - // Add EmergencyPause + // Add EmergencyPause (removeFacet, pause/unpause) bytes4[] memory functionSelectors = new bytes4[](3); functionSelectors[0] = emergencyPause.removeFacet.selector; functionSelectors[1] = emergencyPause.pauseDiamond.selector; diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol index db577e68d..0228827c4 100644 --- a/test/solidity/utils/BaseDiamondTest.sol +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -7,10 +7,16 @@ import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { Test } from "forge-std/Test.sol"; +/// @title BaseDiamondTest +/// @notice Minimal helper to compose a test Diamond and add facets/selectors for test scenarios. +/// @dev Provides overloads to add facets with or without init calldata. +/// This contract is used by higher-level LDA test scaffolding to assemble the test Diamond. abstract contract BaseDiamondTest is Test { LibDiamond.FacetCut[] internal cut; - // Common function to add Diamond Loupe selectors + /// @notice Adds standard Diamond Loupe selectors to the `cut` buffer. + /// @param _diamondLoupe Address of a deployed `DiamondLoupeFacet`. + /// @dev Call this before invoking diamondCut with the buffered `cut`. function _addDiamondLoupeSelectors(address _diamondLoupe) internal { bytes4[] memory functionSelectors = new bytes4[](5); functionSelectors[0] = DiamondLoupeFacet @@ -30,7 +36,9 @@ abstract contract BaseDiamondTest is Test { ); } - // Common function to add Ownership selectors + /// @notice Adds standard Ownership selectors to the `cut` buffer. + /// @param _ownership Address of a deployed `OwnershipFacet`. + /// @dev Call this before invoking diamondCut with the buffered `cut`. function _addOwnershipSelectors(address _ownership) internal { bytes4[] memory functionSelectors = new bytes4[](4); functionSelectors[0] = OwnershipFacet.transferOwnership.selector; @@ -49,6 +57,11 @@ abstract contract BaseDiamondTest is Test { ); } + /// @notice Adds a facet and function selectors to the target diamond. + /// @param _diamond Address of the diamond proxy. + /// @param _facet Address of the facet implementation to add. + /// @param _selectors Function selectors to expose in the diamond. + /// @dev Convenience overload with no initializer; see the 5-arg overload for init flows. function addFacet( address _diamond, address _facet, @@ -57,6 +70,13 @@ abstract contract BaseDiamondTest is Test { _addFacet(_diamond, _facet, _selectors, address(0), ""); } + /// @notice Adds a facet and function selectors to the target diamond, optionally executing an initializer. + /// @param _diamond Address of the diamond proxy. + /// @param _facet Address of the facet implementation to add. + /// @param _selectors Function selectors to expose in the diamond. + /// @param _init Address of an initializer (can be facet or another contract). + /// @param _initCallData ABI-encoded calldata for the initializer. + /// @dev Owner is impersonated via vm.startPrank for the duration of the diamondCut. function addFacet( address _diamond, address _facet, @@ -67,6 +87,15 @@ abstract contract BaseDiamondTest is Test { _addFacet(_diamond, _facet, _selectors, _init, _initCallData); } + /// @notice Performs diamondCut with an appended `FacetCut`. + /// @param _diamond Address of the diamond proxy. + /// @param _facet Address of the facet implementation to add. + /// @param _selectors Function selectors to expose in the diamond. + /// @param _init Address of an initializer (address(0) for none). + /// @param _initCallData ABI-encoded calldata for the initializer (empty if none). + /// @dev Example: + /// - Append loupe + ownership cuts first. + /// - Then call `_addFacet(diamond, address(myFacet), selectors, address(0), "")`. function _addFacet( address _diamond, address _facet, From cc2def9c5197e84e4e3cfd357f4d24ce2d672cf4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 17:16:07 +0200 Subject: [PATCH 072/220] Update conventions.md to enforce single-line comment format for function documentation --- conventions.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/conventions.md b/conventions.md index ce75a07cd..3ed3ec5f6 100644 --- a/conventions.md +++ b/conventions.md @@ -248,6 +248,15 @@ We use Foundry as our primary development and testing framework. Foundry provide /// @return Description of return value /// @dev Additional details about implementation (optional) ``` + - Incorrect format (do not use): + ```solidity + /** @notice Brief description of function purpose + * @param parameterName Description of parameter + * @return Description of return value + * @dev Additional details about implementation (optional) + */ + ``` + - Always use `///` single-line format instead of `/** */` block format for better readability and gas efficiency 4. **Complex Logic Documentation** - Add inline comments for complex algorithms From 9fd51c93294d90d829638b04bc597cd0c4e16ff5 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 17:21:13 +0200 Subject: [PATCH 073/220] enhance documentation in BaseDEXFacetWithCallbackTest --- .../Lda/BaseDexFacetWithCallback.t.sol | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index e1e65c202..3a517405b 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -6,18 +6,33 @@ import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; +/// @title BaseDEXFacetWithCallbackTest +/// @notice Base harness for testing DEX facets that rely on swap callbacks. +/// @dev Provides callback selector/data hooks and two negative tests: +/// - unexpected callback sender +/// - swap path where pool never calls back (should revert) abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { - // Each DEX with callback must implement these hooks + /// @notice Returns the callback selector used by the DEX under test. + /// @return selector Function selector for the DEX's swap callback. function _getCallbackSelector() internal virtual returns (bytes4); + + /// @notice Builds swap data that arms callback verification for the DEX under test. + /// @param pool Pool expected to invoke the callback. + /// @param recipient Receiver of swap proceeds. + /// @return swapData Encoded payload that triggers the DEX callback path. function _buildCallbackSwapData( address pool, address recipient ) internal virtual returns (bytes memory); + /// @notice Provides a mock pool that never performs the callback (negative path). + /// @return pool Address of a pool that will not call the callback. function _deployNoCallbackPool() internal virtual returns (address) { return address(new MockNoCallbackPool()); } + /// @notice Reverts when the callback is invoked by an unexpected sender. + /// @dev No swap is performed beforehand, so the authenticator should hold address(0) and reject. function testRevert_CallbackFromUnexpectedSender() public virtual @@ -40,6 +55,8 @@ abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Reverts when the swap path never executes the callback. + /// @dev Uses a mock pool that does not call back; the aggregator remains armed and must revert. function testRevert_SwapWithoutCallback() public virtual override { // Pool that does not call back (facet-specific implementation) address mockPool = _deployNoCallbackPool(); From 03436c442f239b2dcce72d546a03795f5f536c1c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 17:29:19 +0200 Subject: [PATCH 074/220] Enhance documentation in PoolCallbackAuthenticated and Errors contracts, adding titles, authors, and notices --- src/Periphery/Lda/Errors/Errors.sol | 3 +++ src/Periphery/Lda/PoolCallbackAuthenticated.sol | 4 ++++ test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 2 -- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Periphery/Lda/Errors/Errors.sol b/src/Periphery/Lda/Errors/Errors.sol index 1aec7728b..058f279fc 100644 --- a/src/Periphery/Lda/Errors/Errors.sol +++ b/src/Periphery/Lda/Errors/Errors.sol @@ -1,4 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only +/// @title LDA Error Definitions +/// @author LI.FI (https://li.fi) +/// @notice Custom errors for LDA contracts /// @custom:version 1.0.0 pragma solidity ^0.8.17; diff --git a/src/Periphery/Lda/PoolCallbackAuthenticated.sol b/src/Periphery/Lda/PoolCallbackAuthenticated.sol index 81a1c167b..707f8f2ad 100644 --- a/src/Periphery/Lda/PoolCallbackAuthenticated.sol +++ b/src/Periphery/Lda/PoolCallbackAuthenticated.sol @@ -3,6 +3,10 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; +/// @title PoolCallbackAuthenticated +/// @author LI.FI (https://li.fi) +/// @notice Abstract contract providing pool callback authentication functionality +/// @custom:version 1.0.0 abstract contract PoolCallbackAuthenticated { using LibCallbackAuthenticator for *; diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 6b9971fd6..983ab0270 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -69,8 +69,6 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { /// @notice Builds packed swap data for UniV3-style swap dispatch. /// @param params Struct including pool, direction and recipient. /// @return Packed payload starting with `swapUniV3` selector. - /// @custom:example - /// bytes memory data = _buildUniV3SwapData(UniV3SwapParams(pool, dir, user)); function _buildUniV3SwapData( UniV3SwapParams memory params ) internal view returns (bytes memory) { From 02b95bc23c3c909ee6911d094b271fa573d436f8 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 17:44:26 +0200 Subject: [PATCH 075/220] Add deployment scripts --- script/deploy/facets/LDA/DeployAlgebraFacet.s.sol | 13 +++++++++++++ .../deploy/facets/LDA/DeployCoreRouteFacet.s.sol | 13 +++++++++++++ script/deploy/facets/LDA/DeployCurveFacet.s.sol | 13 +++++++++++++ script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol | 13 +++++++++++++ .../deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol | 13 +++++++++++++ .../deploy/facets/LDA/DeployUniV2StyleFacet.s.sol | 13 +++++++++++++ .../deploy/facets/LDA/DeployUniV3StyleFacet.s.sol | 13 +++++++++++++ .../facets/LDA/DeployVelodromeV2Facet.s.sol | 15 +++++++++++++++ 8 files changed, 106 insertions(+) create mode 100644 script/deploy/facets/LDA/DeployAlgebraFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployCurveFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol create mode 100644 script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol create mode 100644 script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol diff --git a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol new file mode 100644 index 000000000..ff51b4a91 --- /dev/null +++ b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("AlgebraFacet") {} + + function run() public returns (AlgebraFacet deployed) { + deployed = AlgebraFacet(deploy(type(AlgebraFacet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol new file mode 100644 index 000000000..f28a906fd --- /dev/null +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("CoreRouteFacet") {} + + function run() public returns (CoreRouteFacet deployed) { + deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeployCurveFacet.s.sol b/script/deploy/facets/LDA/DeployCurveFacet.s.sol new file mode 100644 index 000000000..1e5dead12 --- /dev/null +++ b/script/deploy/facets/LDA/DeployCurveFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("CurveFacet") {} + + function run() public returns (CurveFacet deployed) { + deployed = CurveFacet(deploy(type(CurveFacet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol new file mode 100644 index 000000000..55055a52d --- /dev/null +++ b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("IzumiV3Facet") {} + + function run() public returns (IzumiV3Facet deployed) { + deployed = IzumiV3Facet(deploy(type(IzumiV3Facet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol new file mode 100644 index 000000000..328ea8162 --- /dev/null +++ b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("SyncSwapV2Facet") {} + + function run() public returns (SyncSwapV2Facet deployed) { + deployed = SyncSwapV2Facet(deploy(type(SyncSwapV2Facet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol new file mode 100644 index 000000000..6347240bc --- /dev/null +++ b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("UniV2StyleFacet") {} + + function run() public returns (UniV2StyleFacet deployed) { + deployed = UniV2StyleFacet(deploy(type(UniV2StyleFacet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol new file mode 100644 index 000000000..b69085833 --- /dev/null +++ b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("UniV3StyleFacet") {} + + function run() public returns (UniV3StyleFacet deployed) { + deployed = UniV3StyleFacet(deploy(type(UniV3StyleFacet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol new file mode 100644 index 000000000..af8c3817f --- /dev/null +++ b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("VelodromeV2Facet") {} + + function run() public returns (VelodromeV2Facet deployed) { + deployed = VelodromeV2Facet( + deploy(type(VelodromeV2Facet).creationCode) + ); + } +} From 8567c5e084ef5f69cd7d4cb7089bf7296f46ef37 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 17:54:21 +0200 Subject: [PATCH 076/220] added scripts --- script/deploy/facets/LDA/DeployAlgebraFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployCurveFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol | 2 +- script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol | 2 +- script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol | 2 +- src/Periphery/Lda/LDADiamond.sol | 2 +- test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol | 4 ++-- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol index ff51b4a91..40fedd9f1 100644 --- a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol +++ b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; +import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("AlgebraFacet") {} diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index f28a906fd..352d08355 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CoreRouteFacet") {} diff --git a/script/deploy/facets/LDA/DeployCurveFacet.s.sol b/script/deploy/facets/LDA/DeployCurveFacet.s.sol index 1e5dead12..18b3c43ce 100644 --- a/script/deploy/facets/LDA/DeployCurveFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCurveFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; +import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CurveFacet") {} diff --git a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol index 55055a52d..6a295772b 100644 --- a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol +++ b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; +import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("IzumiV3Facet") {} diff --git a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol index 328ea8162..6d10e3c4b 100644 --- a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("SyncSwapV2Facet") {} diff --git a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol index 6347240bc..b08a859c7 100644 --- a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("UniV2StyleFacet") {} diff --git a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol index b69085833..fc825770f 100644 --- a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("UniV3StyleFacet") {} diff --git a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol index af8c3817f..1c1df461e 100644 --- a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("VelodromeV2Facet") {} diff --git a/src/Periphery/Lda/LDADiamond.sol b/src/Periphery/Lda/LDADiamond.sol index a9dd4eda0..f3d68fe7f 100644 --- a/src/Periphery/Lda/LDADiamond.sol +++ b/src/Periphery/Lda/LDADiamond.sol @@ -6,7 +6,7 @@ import { IDiamondCut } from "../../Interfaces/IDiamondCut.sol"; /// @title LDADiamond /// @author LI.FI (https://li.fi) -/// @notice Base EIP-2535 Diamond Proxy Contract. +/// @notice Base EIP-2535 Diamond Proxy Contract for LDA (LiFi DEX Aggregator). /// @custom:version 1.0.0 contract LDADiamond { constructor(address _contractOwner, address _diamondCutFacet) payable { diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 50007a89a..9cb52d14e 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.17; -import { LDADiamond } from "lifi/Periphery/Lda/LDADiamond.sol"; +import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; @@ -11,7 +11,7 @@ import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; /// @title LDADiamondTest -/// @notice Spins up a minimal LDA Diamond with loupe, ownership, and emergency pause facets for periphery tests. +/// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. /// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice The diamond proxy under test. From 3fdd94a94d69f71cedb7541fd3faf344d10d4470 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 20:18:21 +0200 Subject: [PATCH 077/220] Update conventions.md --- conventions.md | 3 ++- test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol | 2 +- test/solidity/Periphery/Lda/BaseDexFacet.t.sol | 2 +- test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol | 2 +- test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol | 2 +- 16 files changed, 17 insertions(+), 16 deletions(-) diff --git a/conventions.md b/conventions.md index 3ed3ec5f6..b304fb184 100644 --- a/conventions.md +++ b/conventions.md @@ -153,7 +153,8 @@ We use Foundry as our primary development and testing framework. Foundry provide - **Error Handling:** - - All custom errors must be defined in `src/Errors/GenericErrors.sol` + - Custom errors should be defined in `src/Errors/GenericErrors.sol` with the following exceptions: + - LDA-specific errors should be defined in `src/Periphery/LDA/Errors/Errors.sol` - Error names should be descriptive and follow PascalCase - Errors should not include error messages (gas optimization) - Use custom error types rather than generic `revert()` statements diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index a20787106..5cc5d531f 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 712e05ff1..e35878a05 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index 3a517405b..349b7e626 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 983ab0270..cea568a79 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 9fc02e1e5..19cba5542 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 87bd60d7e..6730bef42 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 1ff0754fd..6f713b4ec 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index fc495c2c0..a5d1847ac 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index c5f34f4bd..65f630588 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index eddd22115..08c577352 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index e34b2f561..f7d9e2d3f 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index d0edd8489..8d2e0b277 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index c17973b7e..88c956a67 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index de9e6299e..c48a749bc 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 9cb52d14e..83bb8b6e8 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; From 529ee1bce7fff596f35f30b1e46e6c4c30cdcaf0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 20:22:28 +0200 Subject: [PATCH 078/220] Add error handling for invalid indexed parameter positions in BaseCoreRouteTest --- test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 5cc5d531f..8c5fdf796 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -113,6 +113,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { error InvalidTopicLength(); /// @notice Thrown when data verification encounters dynamic params (unsupported). error DynamicParamsNotSupported(); + error InvalidIndexedParamPosition(uint8 position, uint256 totalParams); // ==== Setup Functions ==== @@ -501,6 +502,12 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { bool[8] memory isIndexed; // up to 8 params; expand if needed for (uint256 k = 0; k < topicsCount; k++) { uint8 pos = idx[k]; + if (pos >= evt.eventParams.length) { + revert InvalidIndexedParamPosition( + pos, + evt.eventParams.length + ); + } if (pos < isIndexed.length) isIndexed[pos] = true; } From 096f0387615c6e5f1b73ce7502525a563f39736e Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 21:02:07 +0200 Subject: [PATCH 079/220] Refactor parameter naming in swap functions across multiple facets to use 'destinationAddress' instead of 'recipient', enhancing clarity --- src/Periphery/Lda/Facets/AlgebraFacet.sol | 10 +-- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 47 ++++++------- src/Periphery/Lda/Facets/CurveFacet.sol | 14 ++-- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 10 +-- src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 12 ++-- src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 8 +-- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 8 +-- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 20 +++--- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 26 +++---- .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 2 +- .../Lda/BaseDexFacetWithCallback.t.sol | 8 +-- .../Lda/BaseUniV3StyleDexFacet.t.sol | 24 +++---- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 60 ++++++++-------- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 24 +++---- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 36 +++++----- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 14 ++-- .../Lda/Facets/SyncSwapV2Facet.t.sol | 47 ++++++------- .../Lda/Facets/VelodromeV2Facet.t.sol | 69 ++++++++++--------- 18 files changed, 225 insertions(+), 214 deletions(-) diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index b1bdd19fe..8b96cb73e 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -30,7 +30,7 @@ contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticated { // ==== External Functions ==== /// @notice Executes a swap through an Algebra pool /// @dev Handles both regular swaps and fee-on-transfer token swaps - /// @param swapData Encoded swap parameters [pool, direction, recipient, supportsFeeOnTransfer] + /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, supportsFeeOnTransfer] /// @param from Token source address - if equals msg.sender, /// tokens will be pulled from the caller; otherwise assumes tokens are already at this contract /// @param tokenIn Input token address @@ -45,10 +45,10 @@ contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticated { address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); bool supportsFeeOnTransfer = stream.readUint8() > 0; - if (pool == address(0) || recipient == address(0)) + if (pool == address(0) || destinationAddress == address(0)) revert InvalidCallData(); if (from == msg.sender) { @@ -64,7 +64,7 @@ contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticated { if (supportsFeeOnTransfer) { IAlgebraPool(pool).swapSupportingFeeOnInputTokens( address(this), - recipient, + destinationAddress, direction, int256(amountIn), direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, @@ -72,7 +72,7 @@ contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticated { ); } else { IAlgebraPool(pool).swap( - recipient, + destinationAddress, direction, int256(amountIn), direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 9ae496ede..5d553becf 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -9,7 +9,7 @@ import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol"; -import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; +import { InvalidConfig, InvalidReceiver } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title CoreRouteFacet @@ -29,7 +29,7 @@ contract CoreRouteFacet is // ==== Events ==== event Route( address indexed from, - address to, + address receiverAddress, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, @@ -59,7 +59,7 @@ contract CoreRouteFacet is /// @param amountIn The amount of input tokens /// @param tokenOut The expected output token address (address(0) for native) /// @param amountOutMin The minimum acceptable output amount - /// @param to The recipient address for the output tokens + /// @param receiverAddress The receiver address for the output tokens /// @param route The encoded route data containing function selectors and parameters /// @return amountOut The actual amount of output tokens received function processRoute( @@ -67,16 +67,17 @@ contract CoreRouteFacet is uint256 amountIn, address tokenOut, uint256 amountOutMin, - address to, + address receiverAddress, bytes calldata route ) external payable nonReentrant returns (uint256 amountOut) { + if (receiverAddress == address(0)) revert InvalidReceiver(); return _executeRoute( tokenIn, amountIn, tokenOut, amountOutMin, - to, + receiverAddress, route ); } @@ -88,7 +89,7 @@ contract CoreRouteFacet is /// @param amountIn The amount of input tokens /// @param tokenOut The expected output token address (address(0) for native) /// @param amountOutMin The minimum acceptable output amount - /// @param to The recipient address for the output tokens + /// @param receiverAddress The receiver address for the output tokens /// @param route The encoded route data containing function selectors and parameters /// @return amountOut The actual amount of output tokens received function _executeRoute( @@ -96,7 +97,7 @@ contract CoreRouteFacet is uint256 amountIn, address tokenOut, uint256 amountOutMin, - address to, + address receiverAddress, bytes calldata route ) private returns (uint256 amountOut) { bool isNativeIn = LibAsset.isNativeAsset(tokenIn); @@ -106,7 +107,7 @@ contract CoreRouteFacet is (uint256 balInInitial, uint256 balOutInitial) = _getInitialBalances( tokenIn, tokenOut, - to, + receiverAddress, isNativeIn, isNativeOut ); @@ -122,14 +123,14 @@ contract CoreRouteFacet is tokenOut, amountOutMin, balOutInitial, - to, + receiverAddress, isNativeIn, isNativeOut ); emit Route( msg.sender, - to, + receiverAddress, tokenIn, tokenOut, realAmountIn, @@ -144,7 +145,7 @@ contract CoreRouteFacet is /// 2. Native asset handling is done via _handleNative which consumes all ETH on the contract /// @param tokenIn The input token address /// @param tokenOut The output token address - /// @param to The recipient address for output tokens + /// @param receiverAddress The receiver address for output tokens /// @param isNativeIn Whether input token is native ETH /// @param isNativeOut Whether output token is native ETH /// @return balInInitial Initial balance of input token @@ -152,14 +153,14 @@ contract CoreRouteFacet is function _getInitialBalances( address tokenIn, address tokenOut, - address to, + address receiverAddress, bool isNativeIn, bool isNativeOut ) private view returns (uint256 balInInitial, uint256 balOutInitial) { balInInitial = isNativeIn ? 0 : IERC20(tokenIn).balanceOf(msg.sender); balOutInitial = isNativeOut - ? address(to).balance - : IERC20(tokenOut).balanceOf(to); + ? address(receiverAddress).balance + : IERC20(tokenOut).balanceOf(receiverAddress); } function _getFinalBalancesAndCheck( @@ -169,7 +170,7 @@ contract CoreRouteFacet is address tokenOut, uint256 amountOutMin, uint256 balOutInitial, - address to, + address receiverAddress, bool isNativeIn, bool isNativeOut ) private view returns (uint256 amountOut) { @@ -184,8 +185,8 @@ contract CoreRouteFacet is } uint256 balOutFinal = isNativeOut - ? address(to).balance - : IERC20(tokenOut).balanceOf(to); + ? address(receiverAddress).balance + : IERC20(tokenOut).balanceOf(receiverAddress); amountOut = balOutFinal - balOutInitial; if (amountOut < amountOutMin) { revert SwapTokenOutAmountTooLow(amountOut); @@ -232,7 +233,7 @@ contract CoreRouteFacet is /// The selector determines the DEX facet function to call. The router delegatecalls the facet with: /// (bytes swapData, address from, address tokenIn, uint256 amountIn) /// where swapData is the payload from the route, containing DEX-specific data: - /// - Example for UniV3-style: abi.encode(pool, direction, recipient) // for Uniswap V3, PancakeV3, etc. + /// - Example for UniV3-style: abi.encode(pool, direction, destinationAddress) // for Uniswap V3, PancakeV3, etc. /// - Each DEX facet defines its own payload format based on what its pools need /// /// Example multihop route (two legs on user ERC20, then single-pool hop): @@ -240,15 +241,15 @@ contract CoreRouteFacet is /// // Leg payloads with facet selectors: /// leg1 = abi.encodePacked( /// UniV3StyleFacet.swapUniV3.selector, - /// abi.encode(poolA, DIRECTION_TOKEN0_TO_TOKEN1, poolC) // recipient is the final pool + /// abi.encode(poolA, DIRECTION_TOKEN0_TO_TOKEN1, poolC) // destinationAddress is the final pool /// ); /// leg2 = abi.encodePacked( /// IzumiV3Facet.swapIzumiV3.selector, - /// abi.encode(poolB, DIRECTION_TOKEN0_TO_TOKEN1, poolC) // recipient is the final pool + /// abi.encode(poolB, DIRECTION_TOKEN0_TO_TOKEN1, poolC) // destinationAddress is the final pool /// ); /// leg3 = abi.encodePacked( /// SomePoolFacet.swapSinglePool.selector, - /// abi.encode(poolC, finalRecipient, otherPoolParams) // pool that received tokens from leg1&2 + /// abi.encode(poolC, finalReceiver, otherPoolParams) // pool that received tokens from leg1&2 /// ); /// /// // Full route: [2][tokenA][2 legs][60% leg1][40% leg2] then [4][tokenB][leg3] @@ -435,7 +436,7 @@ contract CoreRouteFacet is // - swapData: abi.encode( // pool: 0x123..., // direction: 1, - // recipient: 0x456... + // destinationAddress: 0x456... // ) // // Memory layout it builds (each line is 32 bytes): @@ -448,7 +449,7 @@ contract CoreRouteFacet is // 0x104: 0x60 // swapData length (96) // 0x124: 0x123... // pool address // 0x144: 0x01 // direction - // 0x164: 0x456... // recipient + // 0x164: 0x456... // destinationAddress // Free memory pointer where we’ll build calldata for delegatecall let free := mload(0x40) diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index 4ff8b368e..ccaf9065d 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -20,7 +20,7 @@ contract CurveFacet { // ==== External Functions ==== /// @notice Executes a swap through a Curve pool /// @dev Handles both modern pools that return amounts and legacy pools that require balance tracking - /// @param swapData Encoded swap parameters [pool, poolType, fromIndex, toIndex, recipient, tokenOut] + /// @param swapData Encoded swap parameters [pool, poolType, fromIndex, toIndex, destinationAddress, tokenOut] /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller; /// otherwise assumes tokens are already at this contract /// @param tokenIn Input token address @@ -37,10 +37,10 @@ contract CurveFacet { uint8 poolType = stream.readUint8(); int128 fromIndex = int8(stream.readUint8()); int128 toIndex = int8(stream.readUint8()); - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); address tokenOut = stream.readAddress(); - if (pool == address(0) || recipient == address(0)) + if (pool == address(0) || destinationAddress == address(0)) revert InvalidCallData(); uint256 amountOut; @@ -80,8 +80,12 @@ contract CurveFacet { } } - if (recipient != address(this)) { - LibAsset.transferAsset(tokenOut, payable(recipient), amountOut); + if (destinationAddress != address(this)) { + LibAsset.transferAsset( + tokenOut, + payable(destinationAddress), + amountOut + ); } } } diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 51446a049..a74694b46 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -31,7 +31,7 @@ contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticated { // ==== External Functions ==== /// @notice Executes a swap through an iZiSwap V3 pool /// @dev Handles both X to Y and Y to X swaps with callback verification - /// @param swapData Encoded swap parameters [pool, direction, recipient] + /// @param swapData Encoded swap parameters [pool, direction, destinationAddress] /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller /// @param tokenIn Input token address /// @param amountIn Amount of input tokens @@ -45,11 +45,11 @@ contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticated { address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; // 0 = Y2X, 1 = X2Y - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); if ( pool == address(0) || - recipient == address(0) || + destinationAddress == address(0) || amountIn > type(uint128).max ) revert InvalidCallData(); @@ -66,14 +66,14 @@ contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticated { if (direction) { IiZiSwapPool(pool).swapX2Y( - recipient, + destinationAddress, uint128(amountIn), IZUMI_LEFT_MOST_PT + 1, abi.encode(tokenIn) ); } else { IiZiSwapPool(pool).swapY2X( - recipient, + destinationAddress, uint128(amountIn), IZUMI_RIGHT_MOST_PT - 1, abi.encode(tokenIn) diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index 68d5d6daf..b5300586f 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -17,7 +17,7 @@ contract SyncSwapV2Facet { /// @notice Executes a swap through a SyncSwap V2 pool /// @dev Handles both V1 (vault-based) and V2 (direct) pool swaps - /// @param swapData Encoded swap parameters [pool, recipient, withdrawMode, isV1Pool, vault] + /// @param swapData Encoded swap parameters [pool, destinationAddress, withdrawMode, isV1Pool, vault] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE /// @param tokenIn Input token address @@ -31,9 +31,9 @@ contract SyncSwapV2Facet { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); - if (pool == address(0) || recipient == address(0)) + if (pool == address(0) || destinationAddress == address(0)) revert InvalidCallData(); // withdrawMode meaning for SyncSwap via vault: @@ -65,7 +65,11 @@ contract SyncSwapV2Facet { ISyncSwapVault(target).deposit(tokenIn, pool); } - bytes memory data = abi.encode(tokenIn, recipient, withdrawMode); + bytes memory data = abi.encode( + tokenIn, + destinationAddress, + withdrawMode + ); ISyncSwapPool(pool).swap(data, from, address(0), new bytes(0)); } diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index 16513a3fb..0b0a208d9 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -26,7 +26,7 @@ contract UniV2StyleFacet is BaseRouteConstants { // ==== External Functions ==== /// @notice Executes a UniswapV2-style swap /// @dev Handles token transfers and calculates output amounts based on pool reserves - /// @param swapData Encoded swap parameters [pool, direction, recipient, fee] + /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, fee] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE /// @param tokenIn Input token address @@ -41,10 +41,10 @@ contract UniV2StyleFacet is BaseRouteConstants { address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); uint24 fee = stream.readUint24(); // pool fee in 1/1_000_000 - if (pool == address(0) || recipient == address(0)) { + if (pool == address(0) || destinationAddress == address(0)) { revert InvalidCallData(); } @@ -77,7 +77,7 @@ contract UniV2StyleFacet is BaseRouteConstants { IUniV2StylePool(pool).swap( amount0Out, amount1Out, - recipient, + destinationAddress, new bytes(0) ); } diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 4dadad3b9..8dd9a52e5 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -29,7 +29,7 @@ contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticated { // ==== External Functions ==== /// @notice Executes a swap through a UniV3-style pool /// @dev Handles token transfers and manages callback verification - /// @param swapData Encoded swap parameters [pool, direction, recipient] + /// @param swapData Encoded swap parameters [pool, direction, destinationAddress] /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller /// @param tokenIn Input token address /// @param amountIn Amount of input tokens @@ -43,9 +43,9 @@ contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticated { address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); - if (pool == address(0) || recipient == address(0)) { + if (pool == address(0) || destinationAddress == address(0)) { revert InvalidCallData(); } @@ -64,7 +64,7 @@ contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticated { // Execute swap IUniV3StylePool(pool).swap( - recipient, + destinationAddress, direction, int256(amountIn), direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 6be86b0e5..273b16e9e 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -26,7 +26,7 @@ contract VelodromeV2Facet is BaseRouteConstants { // ==== External Functions ==== /// @notice Performs a swap through VelodromeV2 pools /// @dev Handles token transfers and optional callbacks, with comprehensive safety checks - /// @param swapData Encoded swap parameters [pool, direction, recipient, callback] + /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, callback] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE /// @param tokenIn Input token address @@ -41,13 +41,13 @@ contract VelodromeV2Facet is BaseRouteConstants { address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); - if (pool == address(0) || recipient == address(0)) + if (pool == address(0) || destinationAddress == address(0)) revert InvalidCallData(); bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. - // Will revert if contract (recipient) does not implement IVelodromeV2PoolCallee. + // Will revert if contract (destinationAddress) does not implement IVelodromeV2PoolCallee. if (from == INTERNAL_INPUT_SOURCE) { (uint256 reserve0, uint256 reserve1, ) = IVelodromeV2Pool(pool) @@ -86,21 +86,21 @@ contract VelodromeV2Facet is BaseRouteConstants { // - Token transfer safety: SafeERC20 is used to ensure token transfers revert on failure // - Expected output verification: The contract calls getAmountOut (including fees) before executing the swap // - Flashloan trigger: A flashloan flag is used to determine if the callback should be triggered - // - Post-swap verification: In processRouteInternal, it verifies that the recipient receives at least minAmountOut + // - Post-swap verification: In processRouteInternal, it verifies that the destinationAddress receives at least minAmountOut // and that the sender's final balance is not less than the initial balance // - Immutable interaction: Velodrome V2 pools and the router are not upgradable, // so we can rely on the behavior of getAmountOut and swap // ATTENTION FOR CALLBACKS / HOOKS: - // - recipient contracts should validate that msg.sender is the Velodrome pool contract who is calling the hook - // - recipient contracts must not manipulate their own tokenOut balance + // - destinationAddress contracts should validate that msg.sender is the Velodrome pool contract who is calling the hook + // - destinationAddress contracts must not manipulate their own tokenOut balance // (as this may bypass/invalidate the built-in slippage protection) - // - @developers: never trust balance-based slippage protection for callback recipients - // - @integrators: do not use slippage guarantees when recipient is a contract with side-effects + // - @developers: never trust balance-based slippage protection for callback of destinationAddress + // - @integrators: do not use slippage guarantees when destinationAddress is a contract with side-effects IVelodromeV2Pool(pool).swap( amount0Out, amount1Out, - recipient, + destinationAddress, callback ? abi.encode(tokenIn) : bytes("") ); } diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 8c5fdf796..16c145e29 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -63,7 +63,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @param amountIn Input amount. /// @param minOut Minimum acceptable output amount (slippage). /// @param sender Logical sender of the funds for this hop. - /// @param recipient Receiver of the swap proceeds. + /// @param destinationAddress Destination address of the swap proceeds. /// @param commandType Command determining source of funds. struct SwapTestParams { address tokenIn; @@ -71,7 +71,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { uint256 amountIn; uint256 minOut; address sender; - address recipient; + address destinationAddress; CommandType commandType; } @@ -89,7 +89,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @notice Emitted by CoreRouteFacet upon route processing completion. /// @param from Sender address (user or synthetic if aggregator-funded). - /// @param to Recipient address. + /// @param receiverAddress Receiver address. /// @param tokenIn Input token. /// @param tokenOut Output token. /// @param amountIn Input amount. @@ -97,7 +97,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @param amountOut Actual output amount. event Route( address indexed from, - address to, + address receiverAddress, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, @@ -145,7 +145,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// - ProcessOnePool: [cmd(1)][tokenIn(20)][len(2)][data] /// - Others (User/MyERC20): [cmd(1)][tokenIn(20)][numPools(1)=1][share(2)=FULL][len(2)][data] /// @custom:example Single-hop user ERC20 - /// bytes memory data = abi.encodePacked(facet.swapUniV2.selector, pool, uint8(1), recipient); + /// bytes memory data = abi.encodePacked(facet.swapUniV2.selector, pool, uint8(1), destinationAddress); /// bytes memory route = _buildBaseRoute(params, data); function _buildBaseRoute( SwapTestParams memory params, @@ -207,8 +207,8 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { uint256 inBefore; uint256 outBefore = LibAsset.isNativeAsset(params.tokenOut) - ? params.recipient.balance - : IERC20(params.tokenOut).balanceOf(params.recipient); + ? params.destinationAddress.balance + : IERC20(params.tokenOut).balanceOf(params.destinationAddress); // For aggregator funds, check the diamond's balance if (params.commandType == CommandType.ProcessMyERC20) { @@ -230,7 +230,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { vm.expectEmit(true, true, true, routeEventVerification.checkData); emit Route( fromAddress, - params.recipient, + params.destinationAddress, params.tokenIn, params.tokenOut, params.amountIn, @@ -245,7 +245,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { params.amountIn, params.tokenOut, params.minOut, - params.recipient, + params.destinationAddress, route ); } else { @@ -254,15 +254,15 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { params.amountIn, params.tokenOut, params.minOut, - params.recipient, + params.destinationAddress, route ); } uint256 inAfter; uint256 outAfter = LibAsset.isNativeAsset(params.tokenOut) - ? params.recipient.balance - : IERC20(params.tokenOut).balanceOf(params.recipient); + ? params.destinationAddress.balance + : IERC20(params.tokenOut).balanceOf(params.destinationAddress); // Check balance change on the correct address if (params.commandType == CommandType.ProcessMyERC20) { @@ -366,7 +366,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { : params.amountIn - 1, params.tokenOut, 0, // minOut = 0 for tests - params.recipient, + params.destinationAddress, route ); } diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index e35878a05..c1316bce9 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -205,7 +205,7 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { } /// @notice Concatenates multiple base routes into a single multi-hop route for `processRoute`. - /// @param hopParams Array of hop parameters (tokenIn/out, amountIn, sender/recipient, command type). + /// @param hopParams Array of hop parameters (tokenIn/out, amountIn, sender/destinationAddress, command type). /// @param hopData Array of corresponding DEX-specific swap data for each hop. /// @return Concatenated route bytes suitable for `CoreRouteFacet.processRoute`. /// @dev Reverts if arrays mismatch or empty. Example: diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index 349b7e626..81c0ce892 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -18,11 +18,11 @@ abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { /// @notice Builds swap data that arms callback verification for the DEX under test. /// @param pool Pool expected to invoke the callback. - /// @param recipient Receiver of swap proceeds. + /// @param destinationAddress Destionation address of swap proceeds. /// @return swapData Encoded payload that triggers the DEX callback path. function _buildCallbackSwapData( address pool, - address recipient + address destinationAddress ) internal virtual returns (bytes memory); /// @notice Provides a mock pool that never performs the callback (negative path). @@ -77,7 +77,7 @@ abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -91,7 +91,7 @@ abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), route, diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index cea568a79..65b76fe61 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -17,11 +17,11 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { /// @notice Parameters for a single UniV3-style swap step. /// @param pool Target pool address. /// @param direction Direction of the swap (token0->token1 or vice versa). - /// @param recipient Recipient of the proceeds. + /// @param destinationAddress Destination address of the proceeds. struct UniV3SwapParams { address pool; SwapDirection direction; - address recipient; + address destinationAddress; } /// @notice Parameters for convenience auto-execution. @@ -67,7 +67,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { // ==== Helper Functions ==== /// @notice Builds packed swap data for UniV3-style swap dispatch. - /// @param params Struct including pool, direction and recipient. + /// @param params Struct including pool, direction and destinationAddress. /// @return Packed payload starting with `swapUniV3` selector. function _buildUniV3SwapData( UniV3SwapParams memory params @@ -77,12 +77,12 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { uniV3Facet.swapUniV3.selector, params.pool, uint8(params.direction), - params.recipient + params.destinationAddress ); } /// @notice Executes a UniV3-style swap for arbitrary pool and direction. - /// @param params Swap test params (sender/recipient/funding mode). + /// @param params Swap test params (sender/destinationAddress/funding mode). /// @param pool Pool to swap on. /// @param direction Swap direction to use. /// @dev Funds sender or diamond accordingly, builds route and executes with default assertions. @@ -105,7 +105,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { UniV3SwapParams({ pool: pool, direction: direction, - recipient: params.recipient + destinationAddress: params.destinationAddress }) ); @@ -154,7 +154,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { UniV3SwapParams({ pool: poolInOut, direction: direction, - recipient: USER_SENDER + destinationAddress: USER_SENDER }) ); @@ -166,7 +166,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { amountIn: amountIn, minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: params.commandType }), swapData @@ -179,7 +179,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { amountIn: amountIn, minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: params.commandType }), route @@ -192,18 +192,18 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { /// @notice Builds callback-arming swap data for `BaseDEXFacetWithCallbackTest` harness. /// @param pool Pool to invoke against in callback tests. - /// @param recipient Receiver for proceeds in these tests. + /// @param destinationAddress Destination address for proceeds in these tests. /// @return Packed swap payload. function _buildCallbackSwapData( address pool, - address recipient + address destinationAddress ) internal view override returns (bytes memory) { return _buildUniV3SwapData( UniV3SwapParams({ pool: pool, direction: SwapDirection.Token0ToToken1, - recipient: recipient + destinationAddress: destinationAddress }) ); } diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 19cba5542..b018ff2aa 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -40,7 +40,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { /// @notice Parameters for the high-level `_testSwap` helper. /// @param from Logical sender (user or aggregator/diamond). - /// @param to Recipient of proceeds. + /// @param destinationAddress Destination address of swap. /// @param tokenIn Input token. /// @param amountIn Input amount. /// @param tokenOut Output token. @@ -48,7 +48,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { /// @param supportsFeeOnTransfer Toggle fee-on-transfer compatibility in swap data. struct AlgebraSwapTestParams { address from; - address to; + address destinationAddress; address tokenIn; uint256 amountIn; address tokenOut; @@ -79,13 +79,13 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { /// @notice Parameters to pack swap data for AlgebraFacet. /// @param commandCode Command determining source of funds. /// @param tokenIn Input token address. - /// @param recipient Recipient address. + /// @param destinationAddress Destination address. /// @param pool Algebra pool. /// @param supportsFeeOnTransfer Toggle fee-on-transfer handling. struct AlgebraRouteParams { CommandType commandCode; // 1 for contract funds, 2 for user funds address tokenIn; // Input token address - address recipient; // Address receiving the output tokens + address destinationAddress; // Address receiving the output tokens address pool; // Algebra pool address bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens } @@ -158,7 +158,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { _testSwap( AlgebraSwapTestParams({ from: address(coreRouteFacet), - to: address(USER_SENDER), + destinationAddress: address(USER_SENDER), tokenIn: address(tokenIn), amountIn: _getDefaultAmountForTokenIn() - 1, tokenOut: address(tokenOut), @@ -180,7 +180,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, tokenIn: address(tokenIn), - recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, + destinationAddress: RANDOM_APE_ETH_HOLDER_APECHAIN, pool: poolInOut, supportsFeeOnTransfer: true }) @@ -193,7 +193,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: RANDOM_APE_ETH_HOLDER_APECHAIN, - recipient: RANDOM_APE_ETH_HOLDER_APECHAIN, + destinationAddress: RANDOM_APE_ETH_HOLDER_APECHAIN, commandType: CommandType.ProcessUserERC20 }), swapData, @@ -220,7 +220,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { _testSwap( AlgebraSwapTestParams({ from: USER_SENDER, - to: USER_SENDER, + destinationAddress: USER_SENDER, tokenIn: address(tokenIn), amountIn: _getDefaultAmountForTokenIn(), tokenOut: address(tokenOut), @@ -243,7 +243,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { _testSwap( AlgebraSwapTestParams({ from: USER_SENDER, - to: USER_SENDER, + destinationAddress: USER_SENDER, tokenIn: address(tokenOut), amountIn: amountIn, tokenOut: address(tokenIn), @@ -306,7 +306,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData, @@ -336,7 +336,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, tokenIn: address(tokenIn), - recipient: USER_SENDER, + destinationAddress: USER_SENDER, pool: address(0), // Zero address pool supportsFeeOnTransfer: true }) @@ -349,7 +349,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData, @@ -367,26 +367,26 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { return algebraFacet.algebraSwapCallback.selector; } - /// @notice Hook: build Algebra swap data [pool, direction(uint8), recipient, supportsFeeOnTransfer(uint8)] + /// @notice Hook: build Algebra swap data [pool, direction(uint8), destinationAddress, supportsFeeOnTransfer(uint8)] /// @param pool Pool to be used for callback arming tests. - /// @param recipient Recipient address. + /// @param destinationAddress Destination address. /// @return swapData Packed bytes starting with `swapAlgebra` selector. function _buildCallbackSwapData( address pool, - address recipient + address destinationAddress ) internal pure override returns (bytes memory) { return abi.encodePacked( AlgebraFacet.swapAlgebra.selector, pool, uint8(1), // Token0->Token1; only the callback arming/clearing is under test - recipient, + destinationAddress, uint8(0) // no fee-on-transfer ); } - /// @notice Validates revert when recipient is zero address in swap data. - function testRevert_AlgebraSwap_ZeroAddressRecipient() public { + /// @notice Validates revert when destinationAddress is zero address in swap data. + function testRevert_AlgebraSwap_ZeroAddressDestinationAddress() public { // Transfer tokens from whale to user vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); IERC20(tokenIn).transfer(USER_SENDER, 1 * 1e18); @@ -400,12 +400,12 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { abi.encode(tokenIn) ); - // Build route with address(0) as recipient + // Build route with address(0) as destination address bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, tokenIn: address(tokenIn), - recipient: address(0), // Zero address recipient + destinationAddress: address(0), // Zero address destination address pool: poolInOut, supportsFeeOnTransfer: true }) @@ -417,7 +417,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }); @@ -534,7 +534,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: state.amountIn, minOut: 0, sender: USER_SENDER, - recipient: address(ldaDiamond), // Send to aggregator for next hop + destinationAddress: address(ldaDiamond), // Send to aggregator for next hop commandType: CommandType.ProcessUserERC20 }); @@ -543,7 +543,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { AlgebraRouteParams({ commandCode: CommandType.ProcessUserERC20, tokenIn: address(state.tokenA), - recipient: address(ldaDiamond), + destinationAddress: address(ldaDiamond), pool: state.pool1, supportsFeeOnTransfer: false }) @@ -556,7 +556,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: 0, // Not used for ProcessMyERC20 minOut: 0, sender: address(ldaDiamond), - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessMyERC20 }); @@ -565,7 +565,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { AlgebraRouteParams({ commandCode: CommandType.ProcessMyERC20, tokenIn: address(state.tokenB), - recipient: USER_SENDER, + destinationAddress: USER_SENDER, pool: state.pool2, supportsFeeOnTransfer: state.isFeeOnTransfer }) @@ -580,7 +580,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: state.amountIn, minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), route @@ -684,7 +684,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { AlgebraFacet.swapAlgebra.selector, params.pool, uint8(direction), - params.recipient, + params.destinationAddress, params.supportsFeeOnTransfer ? uint8(1) : uint8(0) ); } @@ -716,7 +716,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { AlgebraRouteParams({ commandCode: commandCode, tokenIn: params.tokenIn, - recipient: params.to, + destinationAddress: params.destinationAddress, pool: pool, supportsFeeOnTransfer: params.supportsFeeOnTransfer }) @@ -730,7 +730,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: params.amountIn, minOut: minOutput, sender: params.from, - recipient: params.to, + destinationAddress: params.destinationAddress, commandType: commandCode }), swapData @@ -743,7 +743,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { amountIn: params.amountIn, minOut: minOutput, sender: params.from, - recipient: params.to, + destinationAddress: params.destinationAddress, commandType: params.from == address(ldaDiamond) ? CommandType.ProcessMyERC20 : CommandType.ProcessUserERC20 diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 6730bef42..51c371765 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -107,9 +107,9 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { new CoreRouteFacet(address(0)); } - /// @notice Verifies ProcessNative command passes ETH to recipient and emits Route with exact out. + /// @notice Verifies ProcessNative command passes ETH to receiver and emits Route with exact out. /// @dev Builds a route with a mock native handler and funds USER_SENDER with 1 ETH. - function test_ProcessNativeCommandSendsEthToRecipient() public { + function test_ProcessNativeCommandSendsEthToReceiver() public { _addMockNativeFacet(); uint256 amount = 1 ether; @@ -130,7 +130,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { amountIn: amount, minOut: 0, sender: USER_SENDER, // Use USER_SENDER directly - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessNative }); @@ -235,7 +235,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { amountIn: 0, minOut: 0, sender: USER_SENDER, - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -277,7 +277,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { amountIn: amountIn, minOut: 0, sender: USER_SENDER, - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -333,7 +333,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { amountIn: amountIn, minOut: 0, sender: USER_SENDER, - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -375,7 +375,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { "OUT", USER_RECEIVER, 0 - ); // recipient starts at 0 + ); // destination address starts at 0 bytes memory swapData = abi.encodePacked(sel); @@ -387,11 +387,11 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { amountIn: amountIn, minOut: 1, sender: USER_SENDER, - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), swapData - ); // single step; no tokenOut will be sent to recipient + ); // single step; no tokenOut will be sent to receiver vm.startPrank(USER_SENDER); IERC20(address(tokenIn)).approve(address(ldaDiamond), amountIn); @@ -436,7 +436,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { amountIn: amountIn, minOut: 1, sender: USER_SENDER, - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -493,8 +493,8 @@ contract MockNativeFacet { address /*tokenIn*/, uint256 amountIn ) external payable returns (uint256) { - address recipient = abi.decode(payload, (address)); - LibAsset.transferAsset(address(0), payable(recipient), amountIn); + address receiverAddress = abi.decode(payload, (address)); + LibAsset.transferAsset(address(0), payable(receiverAddress), amountIn); return amountIn; } } diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 65f630588..5cc429490 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -19,11 +19,11 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { /// @notice Parameters for a single Izumi V3 swap step. /// @param pool Target pool. /// @param direction Direction of the swap. - /// @param recipient Address receiving the proceeds. + /// @param destinationAddress Address receiving the proceeds. struct IzumiV3SwapParams { address pool; SwapDirection direction; - address recipient; + address destinationAddress; } // ==== Errors ==== @@ -95,17 +95,17 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { /// @notice Encodes swap payload for callback arming tests. /// @param pool Pool to use. - /// @param recipient Recipient of proceeds. + /// @param destinationAddress Destination address of proceeds. function _buildCallbackSwapData( address pool, - address recipient + address destinationAddress ) internal pure override returns (bytes memory) { return abi.encodePacked( IzumiV3Facet.swapIzumiV3.selector, pool, uint8(1), // direction TOKEN0_TO_TOKEN1 - recipient + destinationAddress ); } @@ -121,7 +121,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { IzumiV3SwapParams({ pool: poolInMid, direction: SwapDirection.Token1ToToken0, - recipient: USER_RECEIVER + destinationAddress: USER_RECEIVER }) ); @@ -132,7 +132,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -156,7 +156,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { IzumiV3SwapParams({ pool: poolInMid, direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER + destinationAddress: USER_SENDER }) ); @@ -167,7 +167,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { amountIn: _getDefaultAmountForTokenIn() - 1, // -1 for undrain protection minOut: 0, sender: address(coreRouteFacet), - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessMyERC20 }), swapData @@ -187,7 +187,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { IzumiV3SwapParams({ pool: poolInMid, direction: SwapDirection.Token1ToToken0, - recipient: address(coreRouteFacet) + destinationAddress: address(coreRouteFacet) }) ); @@ -196,7 +196,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { IzumiV3SwapParams({ pool: poolMidOut, direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER + destinationAddress: USER_SENDER }) ); @@ -211,7 +211,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { amountIn: amountIn, minOut: 0, sender: USER_SENDER, - recipient: address(coreRouteFacet), + destinationAddress: address(coreRouteFacet), commandType: CommandType.ProcessUserERC20 }); swapData[0] = firstSwapData; @@ -223,7 +223,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { amountIn: 0, // Will be determined by first swap minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessMyERC20 }); swapData[1] = secondSwapData; @@ -239,7 +239,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { amountIn: amountIn, minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), route @@ -273,7 +273,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { IzumiV3SwapParams({ pool: poolInMid, direction: SwapDirection.Token0ToToken1, - recipient: USER_RECEIVER + destinationAddress: USER_RECEIVER }) ); @@ -284,7 +284,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { amountIn: type(uint216).max, minOut: 0, sender: USER_SENDER, - recipient: USER_RECEIVER, + destinationAddress: USER_RECEIVER, commandType: CommandType.ProcessUserERC20 }), swapData, @@ -297,7 +297,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { // ==== Helper Functions ==== /// @notice Encodes Izumi V3 swap payloads for route steps. - /// @param params Pool/direction/recipient. + /// @param params Pool/direction/destinationAddress. /// @return Packed calldata for `swapIzumiV3`. function _buildIzumiV3SwapData( IzumiV3SwapParams memory params @@ -307,7 +307,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { izumiV3Facet.swapIzumiV3.selector, params.pool, uint8(params.direction), - params.recipient + params.destinationAddress ); } } diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index f7d9e2d3f..94b859743 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -8,7 +8,7 @@ import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title RabbitSwapV3FacetTest /// @notice Viction UniV3-style tests for RabbitSwap V3 pools. -/// @dev Covers invalid pool/recipient edge cases plus standard setup. +/// @dev Covers invalid pool/destinationAddress edge cases plus standard setup. contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { /// @notice Selects Viction fork and block height used in tests. function _setupForkConfig() internal override { @@ -41,7 +41,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { UniV3SwapParams({ pool: address(0), // Invalid pool address direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER + destinationAddress: USER_SENDER }) ); @@ -52,7 +52,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData, @@ -62,8 +62,8 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { vm.stopPrank(); } - /// @notice Negative: zero recipient must be rejected by facet call data validation. - function testRevert_RabbitSwapInvalidRecipient() public { + /// @notice Negative: zero destinationAddress must be rejected by facet call data validation. + function testRevert_RabbitSwapInvalidDestinationAddress() public { deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); vm.startPrank(USER_SENDER); @@ -73,7 +73,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { UniV3SwapParams({ pool: poolInOut, direction: SwapDirection.Token1ToToken0, - recipient: address(0) // Invalid recipient address + destinationAddress: address(0) // Invalid destination address }) ); @@ -84,7 +84,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData, diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 8d2e0b277..89ea210d4 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -81,7 +81,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -114,7 +114,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData @@ -151,7 +151,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessMyERC20 }), swapData @@ -188,7 +188,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessMyERC20 }), swapData @@ -236,7 +236,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: SYNC_SWAP_VAULT, + destinationAddress: SYNC_SWAP_VAULT, commandType: CommandType.ProcessUserERC20 }); swapData[0] = firstSwapData; @@ -248,7 +248,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: 0, // Not used in ProcessOnePool minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessOnePool }); swapData[1] = secondSwapData; @@ -263,7 +263,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), route @@ -296,7 +296,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapData, @@ -306,8 +306,8 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } - /// @notice Invalid pool/recipient combinations should revert. - function testRevert_InvalidPoolOrRecipient() public { + /// @notice Invalid pool/destinationAddress combinations should revert. + function testRevert_InvalidPoolOrDestinationAddress() public { // Transfer 1 000 WETH from whale to USER_SENDER deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -330,22 +330,23 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, // Send to next pool - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapDataWithInvalidPool, InvalidCallData.selector ); - bytes memory swapDataWithInvalidRecipient = _buildSyncSwapV2SwapData( - SyncSwapV2SwapParams({ - pool: poolInOut, - to: address(0), - withdrawMode: 2, - isV1Pool: 1, - vault: SYNC_SWAP_VAULT - }) - ); + bytes + memory swapDataWithInvalidDestinationAddress = _buildSyncSwapV2SwapData( + SyncSwapV2SwapParams({ + pool: poolInOut, + to: address(0), + withdrawMode: 2, + isV1Pool: 1, + vault: SYNC_SWAP_VAULT + }) + ); _buildRouteAndExecuteSwap( SwapTestParams({ @@ -354,10 +355,10 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - swapDataWithInvalidRecipient, + swapDataWithInvalidDestinationAddress, InvalidCallData.selector ); @@ -386,7 +387,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { amountIn: 1, minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapDataWithInvalidWithdrawMode, diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 88c956a67..1e31a052a 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -73,7 +73,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { struct VelodromeV2SwapData { address pool; SwapDirection direction; - address recipient; + address destinationAddress; CallbackStatus callbackStatus; } @@ -309,7 +309,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: params.pool2, // Send to next pool + destinationAddress: params.pool2, // Send to next pool commandType: CommandType.ProcessUserERC20 }); @@ -318,7 +318,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: params.pool1, direction: SwapDirection.Token0ToToken1, - recipient: params.pool2, + destinationAddress: params.pool2, callbackStatus: CallbackStatus.Disabled }) ); @@ -330,7 +330,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: params.amounts1[1], // Use output from first hop sender: params.pool2, minOut: 0, - recipient: USER_SENDER, // Send to next pool + destinationAddress: USER_SENDER, // Send to next pool commandType: CommandType.ProcessOnePool }); @@ -339,7 +339,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: params.pool2, direction: SwapDirection.Token0ToToken1, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, callbackStatus: CallbackStatus.Disabled }) ); @@ -354,7 +354,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), route @@ -406,7 +406,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: params.pool2, // Send to next pool + destinationAddress: params.pool2, // Send to next pool commandType: CommandType.ProcessUserERC20 }); @@ -414,7 +414,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: params.pool1, direction: SwapDirection.Token0ToToken1, - recipient: params.pool2, + destinationAddress: params.pool2, callbackStatus: CallbackStatus.Disabled }) ); @@ -426,7 +426,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: params.amounts1[1], // Use output from first hop sender: params.pool2, minOut: 0, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessOnePool }); @@ -434,7 +434,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: params.pool2, direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, callbackStatus: CallbackStatus.Disabled }) ); @@ -448,7 +448,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), route @@ -457,7 +457,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } - function testRevert_InvalidPoolOrRecipient() public { + function testRevert_InvalidPoolOrDestinationAddress() public { vm.startPrank(USER_SENDER); // Get a valid pool address first for comparison @@ -474,7 +474,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: address(0), // Invalid pool direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, callbackStatus: CallbackStatus.Disabled }) ); @@ -486,22 +486,23 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), swapDataZeroPool, InvalidCallData.selector ); - // --- Test case 2: Zero recipient address --- - bytes memory swapDataZeroRecipient = _buildVelodromeV2SwapData( - VelodromeV2SwapData({ - pool: validPool, - direction: SwapDirection.Token1ToToken0, - recipient: address(0), // Invalid recipient - callbackStatus: CallbackStatus.Disabled - }) - ); + // --- Test case 2: Zero destination address --- + bytes + memory swapDataZeroDestinationAddress = _buildVelodromeV2SwapData( + VelodromeV2SwapData({ + pool: validPool, + direction: SwapDirection.Token1ToToken0, + destinationAddress: address(0), // Invalid destination address + callbackStatus: CallbackStatus.Disabled + }) + ); _buildRouteAndExecuteSwap( SwapTestParams({ @@ -510,10 +511,10 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessUserERC20 }), - swapDataZeroRecipient, + swapDataZeroDestinationAddress, InvalidCallData.selector ); @@ -543,7 +544,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, - recipient: params.pool2, // Send to next pool + destinationAddress: params.pool2, // Send to next pool commandType: CommandType.ProcessUserERC20 }); @@ -551,7 +552,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: params.pool1, direction: SwapDirection.Token0ToToken1, - recipient: params.pool2, + destinationAddress: params.pool2, callbackStatus: CallbackStatus.Disabled }) ); @@ -563,7 +564,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: 0, // Not used in ProcessOnePool minOut: 0, sender: params.pool2, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, commandType: CommandType.ProcessOnePool }); @@ -571,7 +572,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: params.pool2, direction: SwapDirection.Token1ToToken0, - recipient: USER_SENDER, + destinationAddress: USER_SENDER, callbackStatus: CallbackStatus.Disabled }) ); @@ -661,7 +662,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2SwapData({ pool: pool, direction: params.direction, - recipient: params.to, + destinationAddress: params.to, callbackStatus: params.callbackStatus }) ); @@ -673,7 +674,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: params.amountIn, minOut: expectedOutput[1], sender: params.from, - recipient: params.to, + destinationAddress: params.to, commandType: commandCode }), swapData @@ -710,7 +711,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { amountIn: params.amountIn, minOut: expectedOutput[1], sender: params.from, - recipient: params.to, + destinationAddress: params.to, commandType: params.from == address(ldaDiamond) ? CommandType.ProcessMyERC20 : CommandType.ProcessUserERC20 @@ -798,7 +799,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { } /// @notice Encodes swap payload for VelodromeV2Facet.swapVelodromeV2. - /// @param params pool/direction/recipient/callback status. + /// @param params pool/direction/destinationAddress/callback status. /// @return Packed bytes payload. function _buildVelodromeV2SwapData( VelodromeV2SwapData memory params @@ -808,7 +809,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VelodromeV2Facet.swapVelodromeV2.selector, params.pool, uint8(params.direction), - params.recipient, + params.destinationAddress, params.callbackStatus ); } From 6ddbe21cddca9b6a410984cf33107a096c2df433 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 22:33:02 +0200 Subject: [PATCH 080/220] fixed LibPackedStream --- src/Libraries/LibPackedStream.sol | 93 +++++++++++++++++-------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/src/Libraries/LibPackedStream.sol b/src/Libraries/LibPackedStream.sol index 7fd7b7acd..6e7a70c57 100644 --- a/src/Libraries/LibPackedStream.sol +++ b/src/Libraries/LibPackedStream.sol @@ -3,10 +3,15 @@ pragma solidity ^0.8.17; /// @title LibPackedStream /// @author LI.FI (https://li.fi) -/// @notice Minimal byte-stream reader for compact calldata formats +/// @notice A library for reading compact byte streams with minimal overhead +/// @dev Provides functions to read various integer types and addresses from a byte stream. +/// All integer reads are big-endian. The stream pointer advances after each read. /// @custom:version 1.0.0 library LibPackedStream { - /// @dev Returns the start and finish pointers for a bytes array. + /// @notice Returns the start and finish pointers for a bytes array + /// @param data The bytes array to get bounds for + /// @return start The pointer to the start of the actual bytes data (after length prefix) + /// @return finish The pointer to the end of the bytes data function _bounds( bytes memory data ) private pure returns (uint256 start, uint256 finish) { @@ -16,9 +21,10 @@ library LibPackedStream { } } - /** @notice Creates stream from data - * @param data data - */ + /// @notice Creates a new stream from a bytes array + /// @dev Allocates memory for stream pointers and initializes them + /// @param data The source bytes to create a stream from + /// @return stream A pointer to the stream struct (contains current position and end) function createStream( bytes memory data ) internal pure returns (uint256 stream) { @@ -31,9 +37,9 @@ library LibPackedStream { } } - /** @notice Checks if stream is not empty - * @param stream stream - */ + /// @notice Checks if there are unread bytes in the stream + /// @param stream The stream to check + /// @return True if current position is before end of stream function isNotEmpty(uint256 stream) internal pure returns (bool) { uint256 pos; uint256 finish; @@ -44,9 +50,10 @@ library LibPackedStream { return pos < finish; } - /** @notice Reads uint8 from the stream - * @param stream stream - */ + /// @notice Reads a uint8 from the current stream position + /// @dev Reads 1 byte and advances stream by 1 + /// @param stream The stream to read from + /// @return res The uint8 value read function readUint8(uint256 stream) internal pure returns (uint8 res) { assembly { let pos := mload(stream) @@ -55,9 +62,10 @@ library LibPackedStream { } } - /** @notice Reads uint16 from the stream - * @param stream stream - */ + /// @notice Reads a uint16 from the current stream position + /// @dev Reads 2 bytes big-endian and advances stream by 2 + /// @param stream The stream to read from + /// @return res The uint16 value read function readUint16(uint256 stream) internal pure returns (uint16 res) { assembly { let pos := mload(stream) @@ -66,21 +74,22 @@ library LibPackedStream { } } - /** @notice Reads uint24 from the stream - * @param stream stream - */ + /// @notice Reads a uint24 from the current stream position + /// @dev Reads 3 bytes big-endian and advances stream by 3 + /// @param stream The stream to read from + /// @return res The uint24 value read function readUint24(uint256 stream) internal pure returns (uint24 res) { assembly { let pos := mload(stream) - pos := add(pos, 3) - res := mload(pos) - mstore(stream, pos) + res := shr(232, mload(pos)) + mstore(stream, add(pos, 3)) } } - /** @notice Reads uint256 from the stream - * @param stream stream - */ + /// @notice Reads a uint256 from the current stream position + /// @dev Reads 32 bytes and advances stream by 32 + /// @param stream The stream to read from + /// @return res The uint256 value read function readUint256(uint256 stream) internal pure returns (uint256 res) { assembly { let pos := mload(stream) @@ -89,9 +98,10 @@ library LibPackedStream { } } - /** @notice Reads bytes32 from the stream - * @param stream stream - */ + /// @notice Reads a bytes32 from the current stream position + /// @dev Reads 32 bytes and advances stream by 32 + /// @param stream The stream to read from + /// @return res The bytes32 value read function readBytes32(uint256 stream) internal pure returns (bytes32 res) { assembly { let pos := mload(stream) @@ -100,9 +110,10 @@ library LibPackedStream { } } - /** @notice Reads address from the stream - * @param stream stream - */ + /// @notice Reads an address from the current stream position + /// @dev Reads 20 bytes and advances stream by 20 + /// @param stream The stream to read from + /// @return res The address value read function readAddress(uint256 stream) internal pure returns (address res) { assembly { let pos := mload(stream) @@ -111,22 +122,16 @@ library LibPackedStream { } } - /** - * @notice Reads a length-prefixed data blob from the stream. - * @dev This is the key function for enabling multi-hop swaps. It allows the - * router to read the data for a single hop without consuming the rest - * of the stream, which may contain data for subsequent hops. - * It expects the stream to be encoded like this: - * [uint16 length_of_data][bytes memory data] - * For a multi-hop route, the stream would look like: - * [uint16 hop1_len][bytes hop1_data][uint16 hop2_len][bytes hop2_data]... - * @param stream The data stream to read from. - * @return res The data blob for the current hop. - */ + /// @notice Reads a length-prefixed byte array from the stream + /// @dev Format: [uint16 length][bytes data]. Used for multi-hop routes where each hop's + /// data is prefixed with its length. Example of a 2-hop route encoding: + /// [uint16 hop1_len][bytes hop1_data][uint16 hop2_len][bytes hop2_data] + /// @param stream The stream to read from + /// @return res The bytes array read from the stream (without length prefix) function readBytesWithLength( uint256 stream ) internal view returns (bytes memory res) { - // Read the 2-byte length prefix to know how many bytes to read next. + // Read the 2-byte length prefix uint16 len = LibPackedStream.readUint16(stream); if (len > 0) { @@ -135,11 +140,13 @@ library LibPackedStream { pos := mload(stream) } assembly { + // Allocate memory for result res := mload(0x40) mstore(0x40, add(res, add(len, 32))) + // Store length and copy data mstore(res, len) pop(staticcall(gas(), 4, pos, len, add(res, 32), len)) - // IMPORTANT: Update the stream's position pointer + // Advance stream pointer mstore(stream, add(pos, len)) } } From 318d8c09c21b7458b5669cc57e95b642442bcbb4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 25 Aug 2025 22:33:40 +0200 Subject: [PATCH 081/220] added PancakeV2 --- src/Interfaces/IUniV2StylePool.sol | 4 + .../Lda/BaseUniV2StyleDexFacet.t.sol | 241 ++++++++++++++++++ .../Periphery/Lda/Facets/PancakeV2.t.sol | 30 +++ 3 files changed, 275 insertions(+) create mode 100644 test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol create mode 100644 test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol diff --git a/src/Interfaces/IUniV2StylePool.sol b/src/Interfaces/IUniV2StylePool.sol index 253e5c504..e9f3ca2c1 100644 --- a/src/Interfaces/IUniV2StylePool.sol +++ b/src/Interfaces/IUniV2StylePool.sol @@ -11,6 +11,10 @@ pragma solidity ^0.8.17; /// - No callbacks during swaps (unlike V3-style pools) /// @custom:version 1.0.0 interface IUniV2StylePool { + /// @notice Returns the address of the token0 + function token0() external view returns (address); + /// @notice Returns the address of the token1 + function token1() external view returns (address); /// @notice Returns the current reserves of the pool and the last block timestamp /// @dev Values are stored as uint112 to fit into a single storage slot for gas optimization /// @return reserve0 The reserve of token0 diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol new file mode 100644 index 000000000..142b3f3d7 --- /dev/null +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; +import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; +import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; + +/// @title BaseUniV2StyleDEXFacetTest +/// @notice Shared UniV2-style testing helpers built atop BaseDEXFacetTest. +/// @dev Handles selector wiring, pool direction inference (token0/token1), and auto-execution flows. +abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { + /// @notice UniV2-style facet proxy handle (points to diamond after setup). + UniV2StyleFacet internal uniV2Facet; + + // ==== Types ==== + + /// @notice Parameters for a single UniV2-style swap step. + /// @param pool Target pool address. + /// @param direction Direction of the swap (token0->token1 or vice versa). + /// @param destinationAddress Destination address of the proceeds. + struct UniV2SwapParams { + address pool; + SwapDirection direction; + address destinationAddress; + uint24 fee; + } + + /// @notice Parameters for convenience auto-execution. + /// @param commandType Whether to use aggregator funds or user funds. + /// @param amountIn Input amount to test with. + struct UniV2AutoSwapParams { + CommandType commandType; + uint256 amountIn; + } + + // ==== Errors ==== + + /// @notice Thrown when a pool does not include the provided token. + /// @param token Token address not found in pool. + /// @param pool Pool address. + error TokenNotInPool(address token, address pool); + + // ==== Setup Functions ==== + + /// @notice Deploys UniV2Style facet and registers `swapUniV2` + DEX-specific callback selector. + /// @return facetAddress Address of the deployed facet implementation. + /// @return functionSelectors Selectors for swap and callback. + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { + uniV2Facet = new UniV2StyleFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = uniV2Facet.swapUniV2.selector; + return (address(uniV2Facet), functionSelectors); + } + + /// @notice Sets `uniV2Facet` to the diamond proxy (post-cut). + /// @param facetAddress Diamond proxy address. + function _setFacetInstance( + address payable facetAddress + ) internal override { + uniV2Facet = UniV2StyleFacet(facetAddress); + } + + // ==== Helper Functions ==== + + /// @notice Virtual function to get the pool fee. Child contracts should override this. + /// @return fee The pool fee in basis points (e.g., 3000 for 0.3%) + function _getPoolFee() internal virtual returns (uint24); + + /// @notice Builds packed swap data for UniV2-style swap dispatch. + /// @param params Struct including pool, direction and destinationAddress. + /// @return Packed payload starting with `swapUniV2` selector. + function _buildUniV2SwapData( + UniV2SwapParams memory params + ) internal returns (bytes memory) { + return + abi.encodePacked( + uniV2Facet.swapUniV2.selector, + params.pool, + uint8(params.direction), + params.destinationAddress, + _getPoolFee() + ); + } + + /// @notice Executes a UniV2-style swap for arbitrary pool and direction. + /// @param params Swap test params (sender/destinationAddress/funding mode). + /// @param pool Pool to swap on. + /// @param direction Swap direction to use. + /// @dev Funds sender or diamond accordingly, builds route and executes with default assertions. + function _executeUniV2StyleSwap( + SwapTestParams memory params, + address pool, + SwapDirection direction + ) internal { + // Fund the appropriate account + if (params.commandType == CommandType.ProcessMyERC20) { + // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. + deal(params.tokenIn, address(ldaDiamond), params.amountIn + 1); + } else { + deal(params.tokenIn, params.sender, params.amountIn); + } + + vm.startPrank(params.sender); + + bytes memory swapData = _buildUniV2SwapData( + UniV2SwapParams({ + pool: pool, + direction: direction, + destinationAddress: params.destinationAddress, + fee: _getPoolFee() + }) + ); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route); + + vm.stopPrank(); + } + + /// @notice Infers direction (token0->token1 or token1->token0) given a pool and `tokenIn`. + /// @param pool The target UniV2-style pool. + /// @param tokenIn The input token address. + /// @return Inferred direction. + function _getDirection( + address pool, + address tokenIn + ) internal view returns (SwapDirection) { + address t0 = IUniV3StylePool(pool).token0(); + address t1 = IUniV3StylePool(pool).token1(); + if (tokenIn == t0) return SwapDirection.Token0ToToken1; + if (tokenIn == t1) return SwapDirection.Token1ToToken0; + revert TokenNotInPool(tokenIn, pool); + } + + /// @notice Convenience flow: infers direction from `poolInOut` and executes with given funding mode. + /// @param params commandType + amountIn + /// @dev Funds sender or diamond, builds route for `poolInOut`, and executes. + function _executeUniV2StyleSwapAuto( + UniV2AutoSwapParams memory params + ) internal { + uint256 amountIn = params.commandType == CommandType.ProcessMyERC20 + ? params.amountIn + 1 + : params.amountIn; + + // Fund the appropriate account + if (params.commandType == CommandType.ProcessMyERC20) { + deal(address(tokenIn), address(ldaDiamond), amountIn + 1); + } else { + deal(address(tokenIn), USER_SENDER, amountIn); + } + + vm.startPrank(USER_SENDER); + + SwapDirection direction = _getDirection(poolInOut, address(tokenIn)); + bytes memory swapData = _buildUniV2SwapData( + UniV2SwapParams({ + pool: poolInOut, + direction: direction, + destinationAddress: USER_SENDER, + fee: _getPoolFee() // Replace params.fee with dynamic fee + }) + ); + + // Build route and execute + bytes memory route = _buildBaseRoute( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: params.commandType + }), + swapData + ); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: params.commandType + }), + route + ); + + vm.stopPrank(); + } + + // ==== Test Cases ==== + + /// @notice Intentionally skipped: UniV2 multi-hop unsupported due to amountSpecified=0 limitation on second hop. + function test_CanSwap_MultiHop() public virtual override { + // SKIPPED: UniV2 forke dex multi-hop unsupported due to AS (amount specified) requirement. + // UniV2 forke dex does not support a "one-pool" second hop today, + // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. UniV2-style pools immediately revert on + // require(amountSpecified != 0, 'AS'), so you can't chain two uniV2 pools + // in a single processRoute invocation. + } + + /// @notice Empty test as UniV2-style dexes does not use callbacks + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + function testRevert_CallbackFromUnexpectedSender() public override { + // UniV2-style dexes does not use callbacks - test intentionally empty + } + + /// @notice Empty test as UniV2-style dexes does not use callbacks + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + function testRevert_SwapWithoutCallback() public override { + // UniV2-style dexes does not use callbacks - test intentionally empty + } + + /// @notice User-funded single-hop swap on UniV2-style pool inferred from `poolInOut`. + function test_CanSwap() public virtual override { + _executeUniV2StyleSwapAuto( + UniV2AutoSwapParams({ + commandType: CommandType.ProcessUserERC20, + amountIn: _getDefaultAmountForTokenIn() + }) + ); + } + + /// @notice Aggregator-funded single-hop swap on UniV2-style. + function test_CanSwap_FromDexAggregator() public virtual override { + _executeUniV2StyleSwapAuto( + UniV2AutoSwapParams({ + commandType: CommandType.ProcessMyERC20, + amountIn: _getDefaultAmountForTokenIn() - 1 + }) + ); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol new file mode 100644 index 000000000..da4cadb44 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { BaseUniV2StyleDEXFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; + +/// @title PancakeV2FacetTest +/// @notice Fork-based UniV2-style tests for PancakeV2 integration. +/// @dev Selects BSC fork, sets pool/token addresses, and delegates logic to base UniV2 test helpers. +contract PancakeV2FacetTest is BaseUniV2StyleDEXFacetTest { + // ==== Setup Functions ==== + + /// @notice Selects `bsc` network and block for fork tests. + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ networkName: "bsc", blockNumber: 58868609 }); + } + + /// @notice Resolves tokenIn/out and pool address for Pancake V2 BUSD/WBNB pair. + function _setupDexEnv() internal override { + tokenIn = IERC20(0x55d398326f99059fF775485246999027B3197955); // BUSD + tokenOut = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c); // WBNB + poolInOut = 0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE; // Pancake V2 BUSD/WBNB pool + } + + /// @notice Returns the pool fee for Pancake V2 BUSD/WBNB pair. + /// @return fee The pool fee in basis points (e.g., 2500 for 0.25%) + function _getPoolFee() internal pure override returns (uint24) { + return 2500; + } +} From db268e8d5b70d76c4389172355db084d6f908561 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 26 Aug 2025 13:06:05 +0200 Subject: [PATCH 082/220] Refactor Curve interfaces: removed ICurveLegacy, added ICurveV2 with updated exchange function, and modified CurveFacet to support both legacy and modern pools. Added unit tests for CurveFacet functionality --- src/Interfaces/ICurve.sol | 7 +- src/Interfaces/ICurveLegacy.sol | 16 -- src/Interfaces/ICurveV2.sol | 24 ++ src/Periphery/Lda/Facets/CurveFacet.sol | 111 ++++++--- .../Periphery/Lda/Facets/CurveFacet.t.sol | 233 ++++++++++++++++++ 5 files changed, 332 insertions(+), 59 deletions(-) delete mode 100644 src/Interfaces/ICurveLegacy.sol create mode 100644 src/Interfaces/ICurveV2.sol create mode 100644 test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol diff --git a/src/Interfaces/ICurve.sol b/src/Interfaces/ICurve.sol index 097036166..ac53de9ec 100644 --- a/src/Interfaces/ICurve.sol +++ b/src/Interfaces/ICurve.sol @@ -6,10 +6,5 @@ pragma solidity ^0.8.17; /// @notice Minimal Curve pool interface for exchange operations /// @custom:version 1.0.0 interface ICurve { - function exchange( - int128 i, - int128 j, - uint256 dx, - uint256 min_dy - ) external payable returns (uint256); + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; } diff --git a/src/Interfaces/ICurveLegacy.sol b/src/Interfaces/ICurveLegacy.sol deleted file mode 100644 index 2b7f4bb9e..000000000 --- a/src/Interfaces/ICurveLegacy.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -/// @title Interface for Curve -/// @author LI.FI (https://li.fi) -/// @notice Minimal legacy Curve pool interface for exchange operations -/// @custom:version 1.0.0 -interface ICurveLegacy { - function exchange( - int128 i, - int128 j, - uint256 dx, - // solhint-disable-next-line var-name-mixedcase - uint256 min_dy - ) external payable; -} diff --git a/src/Interfaces/ICurveV2.sol b/src/Interfaces/ICurveV2.sol new file mode 100644 index 000000000..779d7ff2e --- /dev/null +++ b/src/Interfaces/ICurveV2.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +/// @title Interface for Curve V2 +/// @author LI.FI (https://li.fi) +/// @notice Minimal Curve V2 pool interface for exchange operations +/// @custom:version 1.0.0 +interface ICurveV2 { + function exchange( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy, + address receiver + ) external; + + function exchange_received( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy, + address receiver + ) external; +} diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index ccaf9065d..d8a7364c0 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -5,13 +5,29 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ICurve } from "lifi/Interfaces/ICurve.sol"; -import { ICurveLegacy } from "lifi/Interfaces/ICurveLegacy.sol"; +import { ICurveV2 } from "lifi/Interfaces/ICurveV2.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title CurveFacet /// @author LI.FI (https://li.fi) -/// @notice Handles Curve pool swaps for both legacy and modern pools -/// @dev Implements direct selector-callable swap function for Curve pools with balance tracking for legacy pools +/// @notice Swaps via Curve pools across legacy/main (4-arg) and factory/NG (5-arg) interfaces. +/// @dev +/// - Pool interface selection is driven by `isV2`. +/// - isV2 = false → legacy/main pools exposing `exchange(i,j,dx,min_dy)` (4 args). +/// - isV2 = true → modern pools exposing `exchange(i,j,dx,min_dy,receiver)` (5 args), +/// which includes Factory pools and Stable NG pools. +/// - NG-only “optimistic” swaps: NG pools also implement `exchange_received(...)`. +/// This facet will call `exchange_received` iff: +/// (a) isV2 == true, and +/// (b) `from == address(0)` is provided by the route, signaling the tokens were pre-sent +/// to the pool by a previous hop. In that case we pass a small positive dx (1) as a hint. +/// Notes/constraints for NG: +/// - `_dx` MUST be > 0 (pool asserts actual delta ≥ _dx). +/// - Reverts if the pool contains rebasing tokens. +/// - Amount out is always computed via balanceBefore/After: +/// - For 5-arg pools (isV2=true) we measure at `destinationAddress`. +/// - For 4-arg pools (isV2=false) we measure at this contract and forward tokens afterwards. +/// - Native ETH is not supported (use ERC20). /// @custom:version 1.0.0 contract CurveFacet { using LibPackedStream for uint256; @@ -19,12 +35,25 @@ contract CurveFacet { // ==== External Functions ==== /// @notice Executes a swap through a Curve pool - /// @dev Handles both modern pools that return amounts and legacy pools that require balance tracking - /// @param swapData Encoded swap parameters [pool, poolType, fromIndex, toIndex, destinationAddress, tokenOut] - /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller; - /// otherwise assumes tokens are already at this contract + /// @dev + /// - swapData is tightly packed as: + /// [ pool: address, + /// isV2: uint8 (0 = 4-arg legacy/main, 1 = 5-arg factory/NG), + /// fromIndex: uint8, + /// toIndex: uint8, + /// destinationAddress: address (receiver for 5-arg; post-transfer target for 4-arg), + /// tokenOut: address ] + /// - `from` controls token sourcing: + /// - if `from == msg.sender` and amountIn > 0 - facet pulls `amountIn` from caller, + /// - if `from != msg.sender` - tokens are assumed to be already available (e.g., previous hop). + /// Special case (NG optimistic): if `isV2 == 1` and `from == address(0)`, the facet calls + /// `exchange_received` on NG pools (tokens must have been pre-sent to the pool). + /// - Indices (i,j) must match the pool’s coin ordering. + /// @param swapData Encoded swap parameters [pool, isV2, fromIndex, toIndex, destinationAddress, tokenOut] + /// @param from Token source address; if equals msg.sender, tokens will be pulled; + /// if set to address(0) with isV2==1, signals NG optimistic hop (tokens pre-sent) /// @param tokenIn Input token address - /// @param amountIn Amount of input tokens + /// @param amountIn Amount of input tokens (ignored for NG optimistic hop) function swapCurve( bytes memory swapData, address from, @@ -34,7 +63,7 @@ contract CurveFacet { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); - uint8 poolType = stream.readUint8(); + bool isV2 = stream.readUint8() > 0; // Convert uint8 to bool. 0 = V1, 1 = V2 int128 fromIndex = int8(stream.readUint8()); int128 toIndex = int8(stream.readUint8()); address destinationAddress = stream.readAddress(); @@ -44,43 +73,51 @@ contract CurveFacet { revert InvalidCallData(); uint256 amountOut; - if (LibAsset.isNativeAsset(tokenIn)) { - amountOut = ICurve(pool).exchange{ value: amountIn }( - fromIndex, - toIndex, - amountIn, - 0 + + if (from == msg.sender && amountIn > 0) { + LibAsset.transferFromERC20( + tokenIn, + msg.sender, + address(this), + amountIn ); - } else { - if (from == msg.sender) { - LibAsset.transferFromERC20( - tokenIn, - msg.sender, - address(this), - amountIn - ); - } - LibAsset.maxApproveERC20(IERC20(tokenIn), pool, amountIn); - if (poolType == 0) { - amountOut = ICurve(pool).exchange( + } + + LibAsset.maxApproveERC20(IERC20(tokenIn), pool, amountIn); + + // Track balances at the actual receiver for V2, otherwise at this contract + address balAccount = isV2 ? destinationAddress : address(this); + uint256 balanceBefore = IERC20(tokenOut).balanceOf(balAccount); + + if (isV2) { + if (from == address(0)) { + // Optimistic NG hop: tokens already sent to pool by previous hop. + // NG requires _dx > 0 and asserts actual delta >= _dx. + ICurveV2(pool).exchange_received( fromIndex, toIndex, - amountIn, - 0 + 1, // minimal positive hint + 0, + destinationAddress ); } else { - uint256 balanceBefore = IERC20(tokenOut).balanceOf( - address(this) - ); - ICurveLegacy(pool).exchange(fromIndex, toIndex, amountIn, 0); - uint256 balanceAfter = IERC20(tokenOut).balanceOf( - address(this) + ICurveV2(pool).exchange( + fromIndex, + toIndex, + amountIn, + 0, + destinationAddress ); - amountOut = balanceAfter - balanceBefore; } + } else { + ICurve(pool).exchange(fromIndex, toIndex, amountIn, 0); } - if (destinationAddress != address(this)) { + uint256 balanceAfter = IERC20(tokenOut).balanceOf(balAccount); + amountOut = balanceAfter - balanceBefore; + + // Only transfer when legacy path kept tokens on this contract + if (!isV2 && destinationAddress != address(this)) { LibAsset.transferAsset( tokenOut, payable(destinationAddress), diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol new file mode 100644 index 000000000..8910c4976 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; +import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; + +/// @title CurveFacetTest +/// @notice Linea Curve tests via LDA route. +/// @dev Verifies single-hop, aggregator flow, and revert paths. +contract CurveFacetTest is BaseDEXFacetTest { + /// @notice Facet proxy for swaps bound to the diamond after setup. + CurveFacet internal curveFacet; + + /// @notice Selects Linea fork and block height used by tests. + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + networkName: "mainnet", + blockNumber: 23224347 + }); + } + + /// @notice Deploys CurveFacet and returns its swap selector for diamond cut. + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { + curveFacet = new CurveFacet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = curveFacet.swapCurve.selector; + return (address(curveFacet), functionSelectors); + } + + /// @notice Sets the facet instance to the diamond proxy after facet cut. + function _setFacetInstance( + address payable facetAddress + ) internal override { + curveFacet = CurveFacet(facetAddress); + } + + /// @notice Defines tokens and pools used by tests (WETH/USDC/USDT). + function _setupDexEnv() internal override { + tokenIn = IERC20(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E); // crvUSD + tokenMid = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // USDC + tokenOut = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); // USDT + poolInMid = 0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E; // crvUSD-USDC + poolMidOut = 0x4f493B7dE8aAC7d55F71853688b1F7C8F0243C85; // USDC-USDT + } + + /// @notice Single‐pool swap: USER sends crvUSD → receives USDC. + function test_CanSwap() public override { + // Transfer 1 000 crvUSD from whale to USER_SENDER + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolInMid, + isV2: true, // This is a V2 pool + fromIndex: 1, // Changed: crvUSD is at index 1 + toIndex: 0, // Changed: USDC is at index 0 + destinationAddress: address(USER_SENDER), + tokenOut: address(tokenMid) + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + // Fund the aggregator with 1 000 crvUSD + deal( + address(tokenIn), + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolInMid, + isV2: true, // This is a V2 pool + fromIndex: 1, // Changed: crvUSD is at index 1 + toIndex: 0, // Changed: USDC is at index 0 + destinationAddress: address(USER_SENDER), + tokenOut: address(tokenMid) + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessMyERC20 + }), + swapData + ); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); + + // Build swap data for both hops + bytes memory firstSwapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolInMid, + isV2: true, // This is a V2 pool + fromIndex: 1, // Changed: crvUSD is at index 1 + toIndex: 0, // Changed: USDC is at index 0 + destinationAddress: poolMidOut, + tokenOut: address(tokenMid) + }) + ); + + bytes memory secondSwapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolMidOut, + isV2: true, // was false; NG pool uses 5-arg + exchange_received + fromIndex: 0, + toIndex: 1, + destinationAddress: address(USER_SENDER), + tokenOut: address(tokenOut) + }) + ); + + // Rest of the function remains the same + SwapTestParams[] memory params = new SwapTestParams[](2); + bytes[] memory swapData = new bytes[](2); + + params[0] = SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: poolMidOut, + commandType: CommandType.ProcessUserERC20 + }); + swapData[0] = firstSwapData; + + params[1] = SwapTestParams({ + tokenIn: address(tokenMid), + tokenOut: address(tokenOut), + amountIn: 0, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessOnePool + }); + swapData[1] = secondSwapData; + + bytes memory route = _buildMultiHopRoute(params, swapData); + + _executeAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + route + ); + + vm.stopPrank(); + } + + /// @notice Empty test as Curve does not use callbacks + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + function testRevert_CallbackFromUnexpectedSender() public override { + // Curve does not use callbacks - test intentionally empty + } + + /// @notice Empty test as Curve does not use callbacks + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + function testRevert_SwapWithoutCallback() public override { + // Curve does not use callbacks - test intentionally empty + } + + /// @notice Curve swap parameter shape used for `swapCurve`. + struct CurveSwapParams { + address pool; + bool isV2; + int8 fromIndex; + int8 toIndex; + address destinationAddress; + address tokenOut; + } + + /// @notice Builds Curve swap payload for route steps. + /// @param params pool/to/withdrawMode/isV1Pool/vault tuple. + function _buildCurveSwapData( + CurveSwapParams memory params + ) internal view returns (bytes memory) { + return + abi.encodePacked( + curveFacet.swapCurve.selector, + params.pool, + params.isV2 ? 1 : 0, + params.fromIndex, + params.toIndex, + params.destinationAddress, + params.tokenOut + ); + } +} From aa4e0df7bb8bfee8971f61702b93535398e0eec0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 26 Aug 2025 14:05:21 +0200 Subject: [PATCH 083/220] Enhance CurveFacet documentation and add unit tests for legacy 3pool swaps. added tests --- src/Periphery/Lda/Facets/CurveFacet.sol | 31 ++-- .../Periphery/Lda/Facets/CurveFacet.t.sol | 140 ++++++++++++++++++ 2 files changed, 154 insertions(+), 17 deletions(-) diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index d8a7364c0..1ec6c6009 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -10,24 +10,21 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title CurveFacet /// @author LI.FI (https://li.fi) -/// @notice Swaps via Curve pools across legacy/main (4-arg) and factory/NG (5-arg) interfaces. +/// @notice Handles swaps across all Curve pool variants (Legacy, Factory, NG) /// @dev -/// - Pool interface selection is driven by `isV2`. -/// - isV2 = false → legacy/main pools exposing `exchange(i,j,dx,min_dy)` (4 args). -/// - isV2 = true → modern pools exposing `exchange(i,j,dx,min_dy,receiver)` (5 args), -/// which includes Factory pools and Stable NG pools. -/// - NG-only “optimistic” swaps: NG pools also implement `exchange_received(...)`. -/// This facet will call `exchange_received` iff: -/// (a) isV2 == true, and -/// (b) `from == address(0)` is provided by the route, signaling the tokens were pre-sent -/// to the pool by a previous hop. In that case we pass a small positive dx (1) as a hint. -/// Notes/constraints for NG: -/// - `_dx` MUST be > 0 (pool asserts actual delta ≥ _dx). -/// - Reverts if the pool contains rebasing tokens. -/// - Amount out is always computed via balanceBefore/After: -/// - For 5-arg pools (isV2=true) we measure at `destinationAddress`. -/// - For 4-arg pools (isV2=false) we measure at this contract and forward tokens afterwards. -/// - Native ETH is not supported (use ERC20). +/// Pool Types & Interface Selection: +/// 1. Legacy Pools (isV2 = false): +/// - Version <= 0.2.4 (like 3pool, compound, etc.) +/// - Uses 4-arg exchange(i, j, dx, min_dy) +/// - No receiver param, always sends to msg.sender +/// - We must transfer output tokens manually +/// +/// 2. Modern Pools (isV2 = true): +/// - Factory pools and StableNG pools +/// - Uses 5-arg exchange(i, j, dx, min_dy, receiver) +/// - Direct transfer to specified receiver +/// - For NG pools only: supports optimistic swap via exchange_received +/// when from == address(0) signals tokens were pre-sent /// @custom:version 1.0.0 contract CurveFacet { using LibPackedStream for uint256; diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 8910c4976..ce73e0fda 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title CurveFacetTest /// @notice Linea Curve tests via LDA route. @@ -204,6 +205,145 @@ contract CurveFacetTest is BaseDEXFacetTest { // Curve does not use callbacks - test intentionally empty } + /// @notice Legacy 3pool swap: USER sends USDC → receives USDT via 4-arg exchange (isV2=false). + function test_CanSwap_Legacy3Pool_USDC_to_USDT() public { + // 3pool (DAI,USDC,USDT) mainnet + address poolLegacy = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; + uint256 amountIn = 1_000 * 1e6; // 1,000 USDC (6 decimals) + + // Fund user with USDC + deal(address(tokenMid), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + // Build legacy swap data (isV2=false → 4-arg exchange) + bytes memory swapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolLegacy, + isV2: false, // legacy/main path + fromIndex: 1, // USDC index in 3pool + toIndex: 2, // USDT index in 3pool + destinationAddress: USER_SENDER, + tokenOut: address(tokenOut) // USDT + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenMid), // USDC + tokenOut: address(tokenOut), // USDT + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapData + ); + + vm.stopPrank(); + } + + /// @notice Legacy 3pool swap funded by aggregator: USDC → USDT via 4-arg exchange (isV2=false). + function test_CanSwap_FromDexAggregator_Legacy3Pool_USDC_to_USDT() public { + address poolLegacy = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; + uint256 amountIn = 1_000 * 1e6; + + // Fund aggregator with USDC + deal(address(tokenMid), address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolLegacy, + isV2: false, // legacy/main path + fromIndex: 1, // USDC + toIndex: 2, // USDT + destinationAddress: USER_SENDER, + tokenOut: address(tokenOut) + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenMid), // USDC + tokenOut: address(tokenOut), // USDT + amountIn: amountIn - 1, // follow undrain convention + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessMyERC20 + }), + swapData + ); + + vm.stopPrank(); + } + + /// @notice Tests that the facet reverts on zero pool or destination addresses. + /// @dev Verifies the InvalidCallData revert condition in swapCurve. + function testRevert_InvalidPoolOrDestinationAddress() public { + // Fund user with tokens to avoid underflow in approval + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + + // --- Test case 1: Zero pool address --- + bytes memory swapDataZeroPool = _buildCurveSwapData( + CurveSwapParams({ + pool: address(0), // Invalid pool + isV2: true, + fromIndex: 1, + toIndex: 0, + destinationAddress: USER_SENDER, + tokenOut: address(tokenMid) + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), // Use actual amount since we need valid approvals + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapDataZeroPool, + InvalidCallData.selector + ); + + // --- Test case 2: Zero destination address --- + bytes memory swapDataZeroDestination = _buildCurveSwapData( + CurveSwapParams({ + pool: poolInMid, + isV2: true, + fromIndex: 1, + toIndex: 0, + destinationAddress: address(0), // Invalid destination + tokenOut: address(tokenMid) + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), // Use actual amount since we need valid approvals + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapDataZeroDestination, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + /// @notice Curve swap parameter shape used for `swapCurve`. struct CurveSwapParams { address pool; From 2866e2aa78c28fae92bf716241f937503376a6a2 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 26 Aug 2025 14:58:12 +0200 Subject: [PATCH 084/220] Implement logic to distribute amounts in CoreRouteFacet, ensuring the last leg receives the remainder. Add unit tests for two and three leg distributions, verifying correct amounts are pulled and logged --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 8 +- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 191 ++++++++++++++++++ 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 5d553becf..2fa4938de 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -380,10 +380,14 @@ contract CoreRouteFacet is ) private { uint8 n = cur.readUint8(); unchecked { + uint256 remaining = total; for (uint256 i = 0; i < n; ++i) { uint16 share = cur.readUint16(); - uint256 amt = (total * share) / type(uint16).max; - total -= amt; + uint256 amt = i == n - 1 + ? remaining + : (total * share) / type(uint16).max; // compute vs original total + if (amt > remaining) amt = remaining; + remaining -= amt; _dispatchSwap(cur, from, tokenIn, amt); } } diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 51c371765..7f500150e 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -8,6 +8,7 @@ import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; +import { Vm } from "forge-std/Vm.sol"; /// @title CoreRouteFacetTest /// @notice Tests the CoreRouteFacet's command parser, permit handling, and minimal invariants. @@ -18,6 +19,9 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { /// @notice Cached selector for the mock pull facet to simplify route building in tests. bytes4 internal pullSel; + // ==== Events ==== + event Pulled(uint256 amt); + // ==== Setup Functions ==== /// @notice Registers a mock pull facet once and stores its selector for reuse. @@ -463,6 +467,193 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); vm.stopPrank(); } + + function test_DistributeUserERC20_TwoLegs_50_50_UsesOriginalTotalAndRemainder() + public + { + // Deploy + register mock facet that records each leg's amount via an event + MockRecordPullFacet mock = new MockRecordPullFacet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockRecordPullFacet.pullAndRecord.selector; + addFacet(address(ldaDiamond), address(mock), selectors); + + // Mint token to USER_SENDER + uint256 totalAmount = 1e18; + ERC20PermitMock token = new ERC20PermitMock( + "IN", + "IN", + USER_SENDER, + totalAmount + ); + + // Build route: ProcessUserERC20 with 2 legs, 50%/50% + uint16 shareScale = type(uint16).max; + uint16 halfShare = shareScale / 2; + bytes memory legCalldata = abi.encodePacked(selectors[0]); // 4 bytes + bytes memory route = abi.encodePacked( + uint8(2), // ProcessUserERC20 + address(token), // tokenIn + uint8(2), // n = 2 legs + halfShare, + uint16(4), + legCalldata, // leg1: 50% + halfShare, + uint16(4), + legCalldata // leg2: 50% + ); + + vm.startPrank(USER_SENDER); + IERC20(address(token)).approve(address(ldaDiamond), totalAmount); + + // Record logs and run + vm.recordLogs(); + coreRouteFacet.processRoute( + address(token), // tokenIn + totalAmount, // declared amount + address(0), // tokenOut (unused) + 0, + USER_RECEIVER, + route + ); + Vm.Log[] memory logs = vm.getRecordedLogs(); + vm.stopPrank(); + + // Collect Pulled(uint256) amounts + bytes32 pulledEventTopic = keccak256("Pulled(uint256)"); + uint256[] memory pulledAmounts = new uint256[](2); + uint256 eventCount = 0; + for (uint256 i = 0; i < logs.length; i++) { + if ( + logs[i].topics.length > 0 && + logs[i].topics[0] == pulledEventTopic + ) { + pulledAmounts[eventCount++] = uint256(bytes32(logs[i].data)); + } + } + assertEq(eventCount, 2, "expected two leg pulls"); + + // Expected amounts: first computed from original total, last is exact remainder + uint256 expectedFirstLeg = (totalAmount * halfShare) / shareScale; + uint256 expectedSecondLeg = totalAmount - expectedFirstLeg; + + assertEq(pulledAmounts[0], expectedFirstLeg, "leg1 amount mismatch"); + assertEq( + pulledAmounts[1], + expectedSecondLeg, + "leg2 remainder mismatch" + ); + assertEq( + pulledAmounts[0] + pulledAmounts[1], + totalAmount, + "sum != total" + ); + } + + function test_DistributeUserERC20_ThreeLegs_25_25_50_UsesOriginalTotalAndRemainder() + public + { + // Deploy + register mock facet + MockRecordPullFacet mock = new MockRecordPullFacet(); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = MockRecordPullFacet.pullAndRecord.selector; + addFacet(address(ldaDiamond), address(mock), selectors); + + uint256 totalAmount = 1e18; + ERC20PermitMock token = new ERC20PermitMock( + "IN2", + "IN2", + USER_SENDER, + totalAmount + ); + + uint16 shareScale = type(uint16).max; + uint16 quarterShare = shareScale / 4; + // Third leg share is arbitrary; last leg receives remainder by design + uint16 dummyShare = shareScale / 2; + + bytes memory legCalldata = abi.encodePacked(selectors[0]); // 4 bytes + bytes memory route = abi.encodePacked( + uint8(2), // ProcessUserERC20 + address(token), // tokenIn + uint8(3), // n = 3 legs + quarterShare, + uint16(4), + legCalldata, // 25% + quarterShare, + uint16(4), + legCalldata, // 25% + dummyShare, + uint16(4), + legCalldata // (ignored) gets remainder + ); + + vm.startPrank(USER_SENDER); + IERC20(address(token)).approve(address(ldaDiamond), totalAmount); + vm.recordLogs(); + coreRouteFacet.processRoute( + address(token), + totalAmount, + address(0), + 0, + USER_RECEIVER, + route + ); + Vm.Log[] memory logs = vm.getRecordedLogs(); + vm.stopPrank(); + + bytes32 pulledEventTopic = keccak256("Pulled(uint256)"); + uint256[] memory pulledAmounts = new uint256[](3); + uint256 eventCount = 0; + for (uint256 i = 0; i < logs.length; i++) { + if ( + logs[i].topics.length > 0 && + logs[i].topics[0] == pulledEventTopic + ) { + pulledAmounts[eventCount++] = uint256(bytes32(logs[i].data)); + } + } + assertEq(eventCount, 3, "expected three leg pulls"); + + uint256 expectedFirstLeg = (totalAmount * quarterShare) / shareScale; + uint256 expectedSecondLeg = (totalAmount * quarterShare) / shareScale; + uint256 expectedThirdLeg = totalAmount - + (expectedFirstLeg + expectedSecondLeg); + + assertEq(pulledAmounts[0], expectedFirstLeg, "leg1 amount mismatch"); + assertEq(pulledAmounts[1], expectedSecondLeg, "leg2 amount mismatch"); + assertEq( + pulledAmounts[2], + expectedThirdLeg, + "leg3 remainder mismatch" + ); + assertEq( + pulledAmounts[0] + pulledAmounts[1] + pulledAmounts[2], + totalAmount, + "sum != total" + ); + } +} + +/// @dev Mock facet that records each leg's amount via an event +contract MockRecordPullFacet { + event Pulled(uint256 amt); + function pullAndRecord( + bytes memory /*payload*/, + address from, + address tokenIn, + uint256 amountIn + ) external returns (uint256) { + if (from == msg.sender) { + LibAsset.transferFromERC20( + tokenIn, + msg.sender, + address(this), + amountIn + ); + } + emit Pulled(amountIn); + return amountIn; + } } /// @dev Mock facet implementing LDA's standard interface for testing ERC20 token pulls From 1b4d00016e32b5ea9f86b835267c04184069e8f5 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 26 Aug 2025 15:16:48 +0200 Subject: [PATCH 085/220] Optimize payload handling in CoreRouteFacet by computing payload length directly in assembly --- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 22 ++++++++------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 2fa4938de..c228d6335 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -415,14 +415,11 @@ contract CoreRouteFacet is // Extract function selector (first 4 bytes of data) bytes4 selector = _readSelector(data); - // Create an in-place alias for the payload (skip the first 4 bytes). - // This avoids allocating/copying a second bytes array for the payload. - bytes memory payload; + // Compute payload length directly (data = [len | selector(4) | payload]) + + uint256 payloadLen; assembly { - // payload points to data + 4 and shares the same buffer - payload := add(data, 4) - // payload.length = data.length - 4 - mstore(payload, sub(mload(data), 4)) + payloadLen := sub(mload(data), 4) } address facet = LibDiamondLoupe.facetAddress(selector); @@ -482,15 +479,12 @@ contract CoreRouteFacet is // Payload area (length + bytes) let d := add(args, 0x80) - let len := mload(payload) + let len := payloadLen mstore(d, len) - // Copy payload via the identity precompile (address 0x04). - // This is cheaper for arbitrary-length copies vs. manual loops. - // to = d+32; from = payload+32; size = len - pop( - staticcall(gas(), 0x04, add(payload, 32), len, add(d, 32), len) - ) + // Copy from data + 32 (skip length) + 4 (skip selector) + // to = d+32; from = data+36; size = len + pop(staticcall(gas(), 0x04, add(data, 36), len, add(d, 32), len)) // Round payload length up to a multiple of 32 and compute total calldata size let padded := and(add(len, 31), not(31)) From 8ec085a5adfe77bc90bc6c3df1e46c8bc934715a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 26 Aug 2025 17:21:35 +0200 Subject: [PATCH 086/220] Add error handling for zero addresses and invalid fees in LDADiamond and UniV2StyleFacet. Enhance unit tests --- src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 6 +- src/Periphery/Lda/LDADiamond.sol | 4 + .../Lda/BaseUniV2StyleDexFacet.t.sol | 93 ++++++++++++++++++- .../Periphery/Lda/Facets/PancakeV2.t.sol | 46 +++++++++ .../Periphery/Lda/utils/LdaDiamondTest.sol | 44 +++++++++ 5 files changed, 191 insertions(+), 2 deletions(-) diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index 0b0a208d9..f08f505fc 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -44,7 +44,11 @@ contract UniV2StyleFacet is BaseRouteConstants { address destinationAddress = stream.readAddress(); uint24 fee = stream.readUint24(); // pool fee in 1/1_000_000 - if (pool == address(0) || destinationAddress == address(0)) { + if ( + pool == address(0) || + destinationAddress == address(0) || + fee >= FEE_DENOMINATOR + ) { revert InvalidCallData(); } diff --git a/src/Periphery/Lda/LDADiamond.sol b/src/Periphery/Lda/LDADiamond.sol index f3d68fe7f..60d490213 100644 --- a/src/Periphery/Lda/LDADiamond.sol +++ b/src/Periphery/Lda/LDADiamond.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; import { LibDiamond } from "../../Libraries/LibDiamond.sol"; import { IDiamondCut } from "../../Interfaces/IDiamondCut.sol"; +import { InvalidConfig } from "../../Errors/GenericErrors.sol"; /// @title LDADiamond /// @author LI.FI (https://li.fi) @@ -10,6 +11,9 @@ import { IDiamondCut } from "../../Interfaces/IDiamondCut.sol"; /// @custom:version 1.0.0 contract LDADiamond { constructor(address _contractOwner, address _diamondCutFacet) payable { + if (_contractOwner == address(0) || _diamondCutFacet == address(0)) { + revert InvalidConfig(); + } LibDiamond.setContractOwner(_contractOwner); // Add the diamondCut external function from the diamondCutFacet diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index 142b3f3d7..891620967 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.17; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title BaseUniV2StyleDEXFacetTest /// @notice Shared UniV2-style testing helpers built atop BaseDEXFacetTest. @@ -82,7 +83,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { params.pool, uint8(params.direction), params.destinationAddress, - _getPoolFee() + params.fee ); } @@ -197,6 +198,96 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { // ==== Test Cases ==== + /// @notice Tests that the facet reverts when pool address is zero + function testRevert_InvalidPool() public { + vm.startPrank(USER_SENDER); + + bytes memory swapDataZeroPool = _buildUniV2SwapData( + UniV2SwapParams({ + pool: address(0), + direction: SwapDirection.Token0ToToken1, + destinationAddress: USER_SENDER, + fee: 3000 // 0.3% standard fee + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapDataZeroPool, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + + /// @notice Tests that the facet reverts when destination address is zero + function testRevert_InvalidDestinationAddress() public { + vm.startPrank(USER_SENDER); + + bytes memory swapDataZeroDestination = _buildUniV2SwapData( + UniV2SwapParams({ + pool: poolInOut, + direction: SwapDirection.Token0ToToken1, + destinationAddress: address(0), + fee: 3000 + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapDataZeroDestination, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + + /// @notice Tests that the facet reverts when fee is invalid (>= FEE_DENOMINATOR) + function testRevert_InvalidFee() public { + vm.startPrank(USER_SENDER); + + bytes memory swapDataInvalidFee = _buildUniV2SwapData( + UniV2SwapParams({ + pool: poolInOut, + direction: SwapDirection.Token0ToToken1, + destinationAddress: USER_SENDER, + fee: 1_000_000 // Equal to FEE_DENOMINATOR + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapDataInvalidFee, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + /// @notice Intentionally skipped: UniV2 multi-hop unsupported due to amountSpecified=0 limitation on second hop. function test_CanSwap_MultiHop() public virtual override { // SKIPPED: UniV2 forke dex multi-hop unsupported due to AS (amount specified) requirement. diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol index da4cadb44..2b8347a51 100644 --- a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol +++ b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; import { BaseUniV2StyleDEXFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; /// @title PancakeV2FacetTest @@ -27,4 +28,49 @@ contract PancakeV2FacetTest is BaseUniV2StyleDEXFacetTest { function _getPoolFee() internal pure override returns (uint24) { return 2500; } + + /// @notice Tests that the facet reverts when pool reserves are zero + function testRevert_WrongPoolReserves() public { + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + IERC20(address(tokenIn)).approve( + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); + + // Mock getReserves to return zero reserves + vm.mockCall( + poolInOut, + abi.encodeWithSelector(IUniV2StylePool.getReserves.selector), + abi.encode(0, 0, block.timestamp) + ); + + bytes memory swapData = _buildUniV2SwapData( + UniV2SwapParams({ + pool: poolInOut, + direction: SwapDirection.Token0ToToken1, + destinationAddress: USER_SENDER, + fee: _getPoolFee() + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.ProcessUserERC20 + }), + swapData, + WrongPoolReserves.selector + ); + + // Clean up mock + vm.clearMockedCalls(); + vm.stopPrank(); + } } diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 83bb8b6e8..4c4a57b79 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -7,6 +7,7 @@ import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; @@ -69,4 +70,47 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { vm.stopPrank(); return diamond; } + + /// @notice Tests that diamond creation fails when owner address is zero + function testRevert_CannotDeployDiamondWithZeroOwner() public { + address diamondCutFacet = address(new DiamondCutFacet()); + + vm.expectRevert(InvalidConfig.selector); + new LDADiamond( + address(0), // Zero owner address + diamondCutFacet + ); + } + + function testRevert_CannotDeployDiamondWithZeroDiamondCut() public { + vm.expectRevert(InvalidConfig.selector); + new LDADiamond( + USER_DIAMOND_OWNER, + address(0) // Zero diamondCut address + ); + } + + function testRevert_CannotCallNonExistentFunction() public { + // Create arbitrary calldata with non-existent selector + bytes memory nonExistentCalldata = abi.encodeWithSelector( + bytes4(keccak256("nonExistentFunction()")), + "" + ); + + vm.expectRevert(LibDiamond.FunctionDoesNotExist.selector); + address(ldaDiamond).call(nonExistentCalldata); + } + + function testRevert_CannotCallUnregisteredSelector() public { + // Use a real function selector that exists but hasn't been registered yet + bytes memory unregisteredCalldata = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, // Valid selector but not registered yet + new LibDiamond.FacetCut[](0), + address(0), + "" + ); + + vm.expectRevert(LibDiamond.FunctionDoesNotExist.selector); + address(ldaDiamond).call(unregisteredCalldata); + } } From 058fb8e3062f24436eec454dea475049214bbaf4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 00:19:09 +0200 Subject: [PATCH 087/220] Remove DeployLiFiDEXAggregator scripts and LiFiDEXAggregator test file. Add IWETH interface and NativeWrapperFacet implementation with corresponding tests. Update setup functions in various test contracts to override correctly --- .../facets/DeployLiFiDEXAggregator.s.sol | 37 - .../DeployLiFiDEXAggregator.zksync.s.sol | 36 - src/Interfaces/IWETH.sol | 21 + .../Lda/Facets/NativeWrapperFacet.sol | 99 + test/solidity/Facets/AccessManagerFacet.t.sol | 2 +- test/solidity/Facets/AcrossFacet.t.sol | 2 +- test/solidity/Facets/AcrossFacetPacked.t.sol | 2 +- .../solidity/Facets/AcrossFacetPackedV3.t.sol | 2 +- test/solidity/Facets/AcrossFacetV3.t.sol | 2 +- test/solidity/Facets/AllBridgeFacet.t.sol | 2 +- .../solidity/Facets/ArbitrumBridgeFacet.t.sol | 2 +- test/solidity/Facets/CBridge/CBridge.t.sol | 2 +- .../CBridge/CBridgeAndFeeCollection.t.sol | 2 +- .../Facets/CBridge/CBridgeFacetPacked.t.sol | 2 +- .../Facets/CalldataVerificationFacet.t.sol | 2 +- .../Facets/CelerCircleBridgeFacet.t.sol | 2 +- test/solidity/Facets/ChainflipFacet.t.sol | 2 +- test/solidity/Facets/DeBridgeDlnFacet.t.sol | 2 +- test/solidity/Facets/GasZipFacet.t.sol | 2 +- test/solidity/Facets/GenericSwapFacet.t.sol | 2 +- test/solidity/Facets/GenericSwapFacetV3.t.sol | 2 +- test/solidity/Facets/GlacisFacet.t.sol | 2 +- test/solidity/Facets/GnosisBridgeFacet.t.sol | 2 +- test/solidity/Facets/HopFacet.t.sol | 2 +- .../HopFacetOptimizedL1.t.sol | 2 +- .../HopFacetOptimizedL2.t.sol | 2 +- .../HopFacetPacked/HopFacetPackedL1.t.sol | 2 +- .../HopFacetPacked/HopFacetPackedL2.t.sol | 2 +- test/solidity/Facets/MayanFacet.t.sol | 2 +- .../OmniBridgeFacet/OmniBridgeFacet.t.sol | 2 +- .../OmniBridgeFacet/OmniBridgeL2Facet.t.sol | 2 +- .../solidity/Facets/OptimismBridgeFacet.t.sol | 2 +- test/solidity/Facets/OwnershipFacet.t.sol | 2 +- test/solidity/Facets/PioneerFacet.t.sol | 2 +- test/solidity/Facets/PolygonBridgeFacet.t.sol | 2 +- test/solidity/Facets/RelayFacet.t.sol | 2 +- test/solidity/Facets/SquidFacet.t.sol | 2 +- test/solidity/Facets/StargateFacetV2.t.sol | 2 +- test/solidity/Facets/SymbiosisFacet.t.sol | 2 +- test/solidity/Facets/ThorSwapFacet.t.sol | 2 +- .../Gas/CBridgeFacetPackedARB.gas.t.sol | 2 +- .../Gas/CBridgeFacetPackedETH.gas.t.sol | 2 +- test/solidity/Gas/Hop.t.sol | 2 +- test/solidity/Helpers/SwapperV2.t.sol | 2 +- .../Helpers/WithdrawablePeriphery.t.sol | 2 +- test/solidity/Libraries/LibAsset.t.sol | 2 +- test/solidity/Periphery/GasZipPeriphery.t.sol | 242 +- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 28 +- .../Lda/BaseDexFacetWithCallback.t.sol | 4 +- .../Lda/BaseUniV2StyleDexFacet.t.sol | 17 +- .../Lda/BaseUniV3StyleDexFacet.t.sol | 11 +- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 34 +- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 28 +- .../Periphery/Lda/Facets/CurveFacet.t.sol | 18 +- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 12 +- .../Lda/Facets/NativeWrapperFacet.t.sol | 365 ++ .../Periphery/Lda/Facets/PancakeV2.t.sol | 2 +- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 4 +- .../Lda/Facets/SyncSwapV2Facet.t.sol | 26 +- .../Lda/Facets/VelodromeV2Facet.t.sol | 32 +- .../Periphery/Lda/utils/LdaDiamondTest.sol | 4 +- .../Periphery/LiFiDEXAggregator.t.sol | 3382 ----------------- .../Periphery/LidoWrapper/LidoWrapper.t.sol | 2 +- .../LidoWrapper/LidoWrapperSON.t.sol | 2 +- .../LidoWrapper/LidoWrapperUNI.t.sol | 2 +- test/solidity/Periphery/Permit2Proxy.t.sol | 2 +- .../solidity/Periphery/ReceiverAcrossV3.t.sol | 2 +- .../Periphery/ReceiverChainflip.t.sol | 2 +- .../Periphery/ReceiverStargateV2.t.sol | 2 +- .../EmergencyPauseFacet.fork.t.sol | 2 +- .../EmergencyPauseFacet.local.t.sol | 2 +- test/solidity/utils/TestBase.sol | 4 + 72 files changed, 823 insertions(+), 3685 deletions(-) delete mode 100644 script/deploy/facets/DeployLiFiDEXAggregator.s.sol delete mode 100644 script/deploy/zksync/DeployLiFiDEXAggregator.zksync.s.sol create mode 100644 src/Interfaces/IWETH.sol create mode 100644 src/Periphery/Lda/Facets/NativeWrapperFacet.sol create mode 100644 test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol delete mode 100644 test/solidity/Periphery/LiFiDEXAggregator.t.sol diff --git a/script/deploy/facets/DeployLiFiDEXAggregator.s.sol b/script/deploy/facets/DeployLiFiDEXAggregator.s.sol deleted file mode 100644 index 2b2bd94af..000000000 --- a/script/deploy/facets/DeployLiFiDEXAggregator.s.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; -import { stdJson } from "forge-std/Script.sol"; -import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; - -contract DeployScript is DeployScriptBase { - using stdJson for string; - - constructor() DeployScriptBase("LiFiDEXAggregator") {} - - function run() - public - returns (LiFiDEXAggregator deployed, bytes memory constructorArgs) - { - constructorArgs = getConstructorArgs(); - - deployed = LiFiDEXAggregator( - deploy(type(LiFiDEXAggregator).creationCode) - ); - } - - function getConstructorArgs() internal override returns (bytes memory) { - string memory path = string.concat(root, "/config/global.json"); - string memory json = vm.readFile(path); - - address[] memory priviledgedUsers = new address[](1); - priviledgedUsers[0] = json.readAddress(".pauserWallet"); - - // the original RouteProcessor4.sol is also deployed with address(0) for _bentoBox - - address refundWalletAddress = json.readAddress(".refundWallet"); - - return abi.encode(address(0), priviledgedUsers, refundWalletAddress); - } -} diff --git a/script/deploy/zksync/DeployLiFiDEXAggregator.zksync.s.sol b/script/deploy/zksync/DeployLiFiDEXAggregator.zksync.s.sol deleted file mode 100644 index c579ad0ef..000000000 --- a/script/deploy/zksync/DeployLiFiDEXAggregator.zksync.s.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; -import { stdJson } from "forge-std/Script.sol"; -import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; - -contract DeployScript is DeployScriptBase { - using stdJson for string; - - constructor() DeployScriptBase("LiFiDEXAggregator") {} - - function run() - public - returns (LiFiDEXAggregator deployed, bytes memory constructorArgs) - { - constructorArgs = getConstructorArgs(); - - deployed = LiFiDEXAggregator( - deploy(type(LiFiDEXAggregator).creationCode) - ); - } - - function getConstructorArgs() internal override returns (bytes memory) { - string memory path = string.concat(root, "/config/global.json"); - string memory json = vm.readFile(path); - - address[] memory priviledgedUsers = new address[](1); - priviledgedUsers[0] = json.readAddress(".pauserWallet"); - - // the original RouteProcessor4.sol is also deployed with address(0) for _bentoBox - address refundWalletAddress = json.readAddress(".refundWallet"); - - return abi.encode(address(0), priviledgedUsers, refundWalletAddress); - } -} diff --git a/src/Interfaces/IWETH.sol b/src/Interfaces/IWETH.sol new file mode 100644 index 000000000..30548d693 --- /dev/null +++ b/src/Interfaces/IWETH.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +/// @title IWETH +/// @author LI.FI (https://li.fi) +/// @notice Interface for WETH token +/// @custom:version 1.0.0 +interface IWETH { + /// @notice Deposit native ETH into the WETH contract + /// @dev This function is used to deposit native ETH into the WETH contract + function deposit() external payable; + + /// @notice Withdraw WETH to native ETH + /// @dev This function is used to withdraw WETH to native ETH + function withdraw(uint256) external; + + /// @notice Get the balance of an address + /// @dev This function is used to get the balance of an address + /// @return The balance of the address + function balanceOf(address) external view returns (uint256); +} diff --git a/src/Periphery/Lda/Facets/NativeWrapperFacet.sol b/src/Periphery/Lda/Facets/NativeWrapperFacet.sol new file mode 100644 index 000000000..617518538 --- /dev/null +++ b/src/Periphery/Lda/Facets/NativeWrapperFacet.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { IWETH } from "lifi/Interfaces/IWETH.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; + +/// @title NativeWrapperFacet +/// @author LI.FI (https://li.fi) +/// @notice Handles wrapping/unwrapping of native tokens (ETH <-> WETH) +/// @custom:version 1.0.0 +contract NativeWrapperFacet is BaseRouteConstants { + using SafeTransferLib for address; + using LibPackedStream for uint256; + + // ==== Errors ==== + /// @dev Thrown when native token operations fail + error NativeTransferFailed(); + /// @dev Thrown when WETH operations fail + error WETHOperationFailed(); + + // ==== External Functions ==== + /// @notice Unwraps WETH to native ETH + /// @dev Handles unwrapping WETH and sending native ETH to recipient + /// @param swapData Encoded swap parameters [destinationAddress] + /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; + /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE + /// @param tokenIn WETH token address + /// @param amountIn Amount of WETH to unwrap + function unwrapNative( + bytes memory swapData, + address from, + address tokenIn, + uint256 amountIn + ) external { + uint256 stream = LibPackedStream.createStream(swapData); + + address destinationAddress = stream.readAddress(); + + if (destinationAddress == address(0)) { + revert InvalidCallData(); + } + + if (from == msg.sender) { + LibAsset.transferFromERC20( + tokenIn, + msg.sender, + address(this), + amountIn + ); + } else if (from != address(this)) { + // INTERNAL_INPUT_SOURCE means tokens are already at this contract + revert InvalidCallData(); + } + + try IWETH(tokenIn).withdraw(amountIn) { + destinationAddress.safeTransferETH(amountIn); + } catch { + revert WETHOperationFailed(); + } + } + + /// @notice Wraps native ETH to WETH + /// @dev Handles wrapping native ETH to WETH and sending to recipient + /// @param swapData Encoded swap parameters [wrappedNative, destinationAddress] + /// @param tokenIn Should be INTERNAL_INPUT_SOURCE + /// @param amountIn Amount of native ETH to wrap + function wrapNative( + bytes memory swapData, + address from, + address tokenIn, + uint256 amountIn + ) external payable { + uint256 stream = LibPackedStream.createStream(swapData); + address wrappedNative = stream.readAddress(); + address destinationAddress = stream.readAddress(); + + if ( + wrappedNative == address(0) || + destinationAddress == address(0) || + from != address(this) || // opcode 3 invariant + tokenIn != INTERNAL_INPUT_SOURCE // opcode 3 invariant + ) { + revert InvalidCallData(); + } + + IWETH(wrappedNative).deposit{ value: amountIn }(); + if (destinationAddress != address(this)) { + LibAsset.transferERC20( + wrappedNative, + destinationAddress, + amountIn + ); + } + } +} diff --git a/test/solidity/Facets/AccessManagerFacet.t.sol b/test/solidity/Facets/AccessManagerFacet.t.sol index 2b6d99c46..ddba693c4 100644 --- a/test/solidity/Facets/AccessManagerFacet.t.sol +++ b/test/solidity/Facets/AccessManagerFacet.t.sol @@ -18,7 +18,7 @@ contract AccessManagerFacetTest is TestBase { AccessManagerFacet internal accessMgr; RestrictedContract internal restricted; - function setUp() public { + function setUp() public override { initTestBase(); accessMgr = new AccessManagerFacet(); diff --git a/test/solidity/Facets/AcrossFacet.t.sol b/test/solidity/Facets/AcrossFacet.t.sol index a44332d55..c8aad3c9f 100644 --- a/test/solidity/Facets/AcrossFacet.t.sol +++ b/test/solidity/Facets/AcrossFacet.t.sol @@ -35,7 +35,7 @@ contract AcrossFacetTest is TestBaseFacet { AcrossFacet.AcrossData internal validAcrossData; TestAcrossFacet internal acrossFacet; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 17130542; initTestBase(); diff --git a/test/solidity/Facets/AcrossFacetPacked.t.sol b/test/solidity/Facets/AcrossFacetPacked.t.sol index e3edb15f6..1e0b81a12 100644 --- a/test/solidity/Facets/AcrossFacetPacked.t.sol +++ b/test/solidity/Facets/AcrossFacetPacked.t.sol @@ -60,7 +60,7 @@ contract AcrossFacetPackedTest is TestBase { event LiFiAcrossTransfer(bytes8 _transactionId); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19145375; initTestBase(); diff --git a/test/solidity/Facets/AcrossFacetPackedV3.t.sol b/test/solidity/Facets/AcrossFacetPackedV3.t.sol index 7c13b84b5..6bd08f1b4 100644 --- a/test/solidity/Facets/AcrossFacetPackedV3.t.sol +++ b/test/solidity/Facets/AcrossFacetPackedV3.t.sol @@ -65,7 +65,7 @@ contract AcrossFacetPackedV3Test is TestBase { event LiFiAcrossTransfer(bytes8 _transactionId); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19960294; initTestBase(); diff --git a/test/solidity/Facets/AcrossFacetV3.t.sol b/test/solidity/Facets/AcrossFacetV3.t.sol index 0f3910f57..054aa6acf 100644 --- a/test/solidity/Facets/AcrossFacetV3.t.sol +++ b/test/solidity/Facets/AcrossFacetV3.t.sol @@ -39,7 +39,7 @@ contract AcrossFacetV3Test is TestBaseFacet { error InvalidQuoteTimestamp(); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19960294; initTestBase(); diff --git a/test/solidity/Facets/AllBridgeFacet.t.sol b/test/solidity/Facets/AllBridgeFacet.t.sol index 17aa5d692..abce9e418 100644 --- a/test/solidity/Facets/AllBridgeFacet.t.sol +++ b/test/solidity/Facets/AllBridgeFacet.t.sol @@ -60,7 +60,7 @@ contract AllBridgeFacetTest is TestBaseFacet, LiFiData { AllBridgeFacet.AllBridgeData internal validAllBridgeData; TestAllBridgeFacet internal allBridgeFacet; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 17556456; initTestBase(); diff --git a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol index ce1557b29..32d0cdab2 100644 --- a/test/solidity/Facets/ArbitrumBridgeFacet.t.sol +++ b/test/solidity/Facets/ArbitrumBridgeFacet.t.sol @@ -37,7 +37,7 @@ contract ArbitrumBridgeFacetTest is TestBaseFacet { ArbitrumBridgeFacet.ArbitrumData internal arbitrumData; uint256 internal cost; - function setUp() public { + function setUp() public override { initTestBase(); arbitrumBridgeFacet = new TestArbitrumBridgeFacet( diff --git a/test/solidity/Facets/CBridge/CBridge.t.sol b/test/solidity/Facets/CBridge/CBridge.t.sol index 2ace03982..79e2949b5 100644 --- a/test/solidity/Facets/CBridge/CBridge.t.sol +++ b/test/solidity/Facets/CBridge/CBridge.t.sol @@ -77,7 +77,7 @@ contract CBridgeFacetTest is TestBaseFacet { } } - function setUp() public { + function setUp() public override { initTestBase(); cBridge = new TestCBridgeFacet(ICBridge(CBRIDGE_ROUTER)); bytes4[] memory functionSelectors = new bytes4[](5); diff --git a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol index 8fd7d1ff0..cd45b661e 100644 --- a/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeAndFeeCollection.t.sol @@ -29,7 +29,7 @@ contract CBridgeAndFeeCollectionTest is TestBase { TestCBridgeFacet internal cBridge; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 14847528; initTestBase(); diff --git a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol index dca351729..f6a1e434f 100644 --- a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol @@ -46,7 +46,7 @@ contract CBridgeFacetPackedTest is TestBase { uint256 amount ); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 58467500; customRpcUrlForForking = "ETH_NODE_URI_ARBITRUM"; initTestBase(); diff --git a/test/solidity/Facets/CalldataVerificationFacet.t.sol b/test/solidity/Facets/CalldataVerificationFacet.t.sol index d51cbe3c5..93c86552a 100644 --- a/test/solidity/Facets/CalldataVerificationFacet.t.sol +++ b/test/solidity/Facets/CalldataVerificationFacet.t.sol @@ -22,7 +22,7 @@ contract CalldataVerificationFacetTest is TestBase { error SliceOutOfBounds(); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19979843; initTestBase(); calldataVerificationFacet = new CalldataVerificationFacet(); diff --git a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol index 50424f46a..6981e1760 100644 --- a/test/solidity/Facets/CelerCircleBridgeFacet.t.sol +++ b/test/solidity/Facets/CelerCircleBridgeFacet.t.sol @@ -28,7 +28,7 @@ contract CelerCircleBridgeFacetTest is TestBaseFacet { TestCelerCircleBridgeFacet internal celerCircleBridgeFacet; - function setUp() public { + function setUp() public override { // Custom Config customBlockNumberForForking = 17118891; // after proxy+bridge configuration diff --git a/test/solidity/Facets/ChainflipFacet.t.sol b/test/solidity/Facets/ChainflipFacet.t.sol index 972329d52..41a51b0a5 100644 --- a/test/solidity/Facets/ChainflipFacet.t.sol +++ b/test/solidity/Facets/ChainflipFacet.t.sol @@ -35,7 +35,7 @@ contract ChainflipFacetTest is TestBaseFacet, LiFiData { uint256 internal constant CHAIN_ID_SOLANA = 1151111081099710; uint256 internal constant CHAIN_ID_BITCOIN = 20000000000001; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 18277082; initTestBase(); diff --git a/test/solidity/Facets/DeBridgeDlnFacet.t.sol b/test/solidity/Facets/DeBridgeDlnFacet.t.sol index a735101f5..293f20ea1 100644 --- a/test/solidity/Facets/DeBridgeDlnFacet.t.sol +++ b/test/solidity/Facets/DeBridgeDlnFacet.t.sol @@ -42,7 +42,7 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { bytes32 internal namespace = keccak256("com.lifi.facets.debridgedln"); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19279222; initTestBase(); diff --git a/test/solidity/Facets/GasZipFacet.t.sol b/test/solidity/Facets/GasZipFacet.t.sol index 10e0739a8..4c1d6d34c 100644 --- a/test/solidity/Facets/GasZipFacet.t.sol +++ b/test/solidity/Facets/GasZipFacet.t.sol @@ -45,7 +45,7 @@ contract GasZipFacetTest is TestBaseFacet { error OnlyNativeAllowed(); error TooManyChainIds(); - function setUp() public { + function setUp() public override { // set custom block no for mainnet forking customBlockNumberForForking = 20828620; diff --git a/test/solidity/Facets/GenericSwapFacet.t.sol b/test/solidity/Facets/GenericSwapFacet.t.sol index f90166377..60bfd9fd8 100644 --- a/test/solidity/Facets/GenericSwapFacet.t.sol +++ b/test/solidity/Facets/GenericSwapFacet.t.sol @@ -26,7 +26,7 @@ contract GenericSwapFacetTest is TestBase { TestGenericSwapFacet internal genericSwapFacet; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 15588208; initTestBase(); diff --git a/test/solidity/Facets/GenericSwapFacetV3.t.sol b/test/solidity/Facets/GenericSwapFacetV3.t.sol index 5b2425685..1ade6dd0a 100644 --- a/test/solidity/Facets/GenericSwapFacetV3.t.sol +++ b/test/solidity/Facets/GenericSwapFacetV3.t.sol @@ -57,7 +57,7 @@ contract GenericSwapFacetV3Test is TestBase { TestGenericSwapFacet internal genericSwapFacet; TestGenericSwapFacetV3 internal genericSwapFacetV3; - function setUp() public { + function setUp() public override { // set custom block number for forking customBlockNumberForForking = 19834820; initTestBase(); diff --git a/test/solidity/Facets/GlacisFacet.t.sol b/test/solidity/Facets/GlacisFacet.t.sol index d82aed5b9..6ad60ee7a 100644 --- a/test/solidity/Facets/GlacisFacet.t.sol +++ b/test/solidity/Facets/GlacisFacet.t.sol @@ -35,7 +35,7 @@ abstract contract GlacisFacetTestBase is TestBaseFacet { uint256 internal payableAmount = 1 ether; - function setUp() public virtual { + function setUp() public virtual override { initTestBase(); srcToken = ERC20(addressSrcToken); diff --git a/test/solidity/Facets/GnosisBridgeFacet.t.sol b/test/solidity/Facets/GnosisBridgeFacet.t.sol index a594b8042..37aad10d2 100644 --- a/test/solidity/Facets/GnosisBridgeFacet.t.sol +++ b/test/solidity/Facets/GnosisBridgeFacet.t.sol @@ -37,7 +37,7 @@ contract GnosisBridgeFacetTest is TestBaseFacet { TestGnosisBridgeFacet internal gnosisBridgeFacet; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 22566858; initTestBase(); defaultUSDSAmount = defaultDAIAmount; diff --git a/test/solidity/Facets/HopFacet.t.sol b/test/solidity/Facets/HopFacet.t.sol index fde6b92b5..23a983e0c 100644 --- a/test/solidity/Facets/HopFacet.t.sol +++ b/test/solidity/Facets/HopFacet.t.sol @@ -39,7 +39,7 @@ contract HopFacetTest is TestBaseFacet { ILiFi.BridgeData internal validBridgeData; HopFacet.HopData internal validHopData; - function setUp() public { + function setUp() public override { initTestBase(); hopFacet = new TestHopFacet(); bytes4[] memory functionSelectors = new bytes4[](6); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol index 929805cc2..abe8d5730 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL1.t.sol @@ -34,7 +34,7 @@ contract HopFacetOptimizedL1Test is TestBaseFacet { ILiFi.BridgeData internal validBridgeData; HopFacetOptimized.HopData internal validHopData; - function setUp() public { + function setUp() public override { initTestBase(); hopFacet = new TestHopFacet(); bytes4[] memory functionSelectors = new bytes4[](7); diff --git a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol index 25ff28ef9..f25e8ae78 100644 --- a/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol +++ b/test/solidity/Facets/HopFacetOptimized/HopFacetOptimizedL2.t.sol @@ -34,7 +34,7 @@ contract HopFacetOptimizedL2Test is TestBaseFacet { ILiFi.BridgeData internal validBridgeData; HopFacetOptimized.HopData internal validHopData; - function setUp() public { + function setUp() public override { // Custom Config customRpcUrlForForking = "ETH_NODE_URI_POLYGON"; customBlockNumberForForking = 59534582; diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol index ef7e3baab..d1b9c98bd 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL1.t.sol @@ -59,7 +59,7 @@ contract HopFacetPackedL1Test is TestBase { BridgeParams internal usdtParams; BridgeParams internal nativeParams; - function setUp() public { + function setUp() public override { // set custom block number for forking customBlockNumberForForking = 15588208; initTestBase(); diff --git a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol index 4b1ded25a..ef76b4dc7 100644 --- a/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol +++ b/test/solidity/Facets/HopFacetPacked/HopFacetPackedL2.t.sol @@ -69,7 +69,7 @@ contract HopFacetPackedL2Test is TestBase { BridgeParams internal usdcBridgeParams; BridgeParams internal usdtBridgeParams; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 58467500; customRpcUrlForForking = "ETH_NODE_URI_ARBITRUM"; initTestBase(); diff --git a/test/solidity/Facets/MayanFacet.t.sol b/test/solidity/Facets/MayanFacet.t.sol index 2235203e1..14d64f368 100644 --- a/test/solidity/Facets/MayanFacet.t.sol +++ b/test/solidity/Facets/MayanFacet.t.sol @@ -64,7 +64,7 @@ contract MayanFacetTest is TestBaseFacet { error InvalidReceiver(address expected, address actual); error ProtocolDataTooShort(); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19968172; initTestBase(); diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol index eb353e8ce..fe0c2a121 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeFacet.t.sol @@ -33,7 +33,7 @@ contract OmniBridgeFacetTest is TestBaseFacet { TestOmniBridgeFacet internal omniBridgeFacet; - function setUp() public { + function setUp() public override { initTestBase(); omniBridgeFacet = new TestOmniBridgeFacet( diff --git a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol index 8f024af49..656f1af14 100644 --- a/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol +++ b/test/solidity/Facets/OmniBridgeFacet/OmniBridgeL2Facet.t.sol @@ -33,7 +33,7 @@ contract OmniBridgeL2FacetTest is TestBaseFacet { TestOmniBridgeFacet internal omniBridgeFacet; - function setUp() public { + function setUp() public override { // Fork Gnosis chain customRpcUrlForForking = "ETH_NODE_URI_GNOSIS"; customBlockNumberForForking = 26862566; diff --git a/test/solidity/Facets/OptimismBridgeFacet.t.sol b/test/solidity/Facets/OptimismBridgeFacet.t.sol index d15755ea3..be349b253 100644 --- a/test/solidity/Facets/OptimismBridgeFacet.t.sol +++ b/test/solidity/Facets/OptimismBridgeFacet.t.sol @@ -44,7 +44,7 @@ contract OptimismBridgeFacetTest is TestBase { ILiFi.BridgeData internal validBridgeData; OptimismBridgeFacet.OptimismData internal validOptimismData; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 15876510; initTestBase(); diff --git a/test/solidity/Facets/OwnershipFacet.t.sol b/test/solidity/Facets/OwnershipFacet.t.sol index 4c7996cf1..f8bea896f 100644 --- a/test/solidity/Facets/OwnershipFacet.t.sol +++ b/test/solidity/Facets/OwnershipFacet.t.sol @@ -23,7 +23,7 @@ contract OwnershipFacetTest is TestBase { address indexed newOwner ); - function setUp() public { + function setUp() public override { initTestBase(); ownershipFacet = OwnershipFacet(address(diamond)); diff --git a/test/solidity/Facets/PioneerFacet.t.sol b/test/solidity/Facets/PioneerFacet.t.sol index 4ff2e84e5..08f17b344 100644 --- a/test/solidity/Facets/PioneerFacet.t.sol +++ b/test/solidity/Facets/PioneerFacet.t.sol @@ -31,7 +31,7 @@ contract PioneerFacetTest is TestBaseFacet { PioneerFacet.PioneerData internal pioneerData; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 17130542; initTestBase(); diff --git a/test/solidity/Facets/PolygonBridgeFacet.t.sol b/test/solidity/Facets/PolygonBridgeFacet.t.sol index bd3220d63..e51d93f51 100644 --- a/test/solidity/Facets/PolygonBridgeFacet.t.sol +++ b/test/solidity/Facets/PolygonBridgeFacet.t.sol @@ -33,7 +33,7 @@ contract PolygonBridgeFacetTest is TestBaseFacet { TestPolygonBridgeFacet internal polygonBridgeFacet; - function setUp() public { + function setUp() public override { initTestBase(); polygonBridgeFacet = new TestPolygonBridgeFacet( diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 6d94e5db1..cbd7b5430 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -52,7 +52,7 @@ contract RelayFacetTest is TestBaseFacet, LiFiData { error InvalidQuote(); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19767662; initTestBase(); relayFacet = new TestRelayFacet(RELAY_RECEIVER, relaySolver); diff --git a/test/solidity/Facets/SquidFacet.t.sol b/test/solidity/Facets/SquidFacet.t.sol index 9b371c28b..89049b86e 100644 --- a/test/solidity/Facets/SquidFacet.t.sol +++ b/test/solidity/Facets/SquidFacet.t.sol @@ -42,7 +42,7 @@ contract SquidFacetTest is TestBaseFacet { ISquidMulticall.Call internal sourceCall; TestSquidFacet internal squidFacet; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 18810880; customBlockNumberForForking = 19664946; initTestBase(); diff --git a/test/solidity/Facets/StargateFacetV2.t.sol b/test/solidity/Facets/StargateFacetV2.t.sol index 1bc9cb4a9..3425869e3 100644 --- a/test/solidity/Facets/StargateFacetV2.t.sol +++ b/test/solidity/Facets/StargateFacetV2.t.sol @@ -52,7 +52,7 @@ contract StargateFacetV2Test is TestBaseFacet { TestStargateFacetV2 internal stargateFacetV2; StargateFacetV2.StargateData internal stargateData; - function setUp() public { + function setUp() public override { // set custom block number for forking customBlockNumberForForking = 19979843; diff --git a/test/solidity/Facets/SymbiosisFacet.t.sol b/test/solidity/Facets/SymbiosisFacet.t.sol index 2beb9cb19..acbcb9713 100644 --- a/test/solidity/Facets/SymbiosisFacet.t.sol +++ b/test/solidity/Facets/SymbiosisFacet.t.sol @@ -26,7 +26,7 @@ contract SymbiosisFacetTest is TestBaseFacet { TestSymbiosisFacet internal symbiosisFacet; SymbiosisFacet.SymbiosisData internal symbiosisData; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 19317492; initTestBase(); // MockMetaRouter mockMetaRouter = new MockMetaRouter(); diff --git a/test/solidity/Facets/ThorSwapFacet.t.sol b/test/solidity/Facets/ThorSwapFacet.t.sol index 5174a7d3e..9c2863825 100644 --- a/test/solidity/Facets/ThorSwapFacet.t.sol +++ b/test/solidity/Facets/ThorSwapFacet.t.sol @@ -26,7 +26,7 @@ contract ThorSwapFacetTest is TestBaseFacet { ThorSwapFacet.ThorSwapData internal validThorSwapData; TestThorSwapFacet internal thorSwapFacet; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 16661275; initTestBase(); diff --git a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol index 667b60185..9dd7c3302 100644 --- a/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedARB.gas.t.sol @@ -27,7 +27,7 @@ contract CBridgeGasARBTest is TestBase { uint256 internal amountUSDC; bytes internal packedUSDC; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 58467500; customRpcUrlForForking = "ETH_NODE_URI_ARBITRUM"; initTestBase(); diff --git a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol index 06e98c078..efb3f6458 100644 --- a/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol +++ b/test/solidity/Gas/CBridgeFacetPackedETH.gas.t.sol @@ -41,7 +41,7 @@ contract CBridgeGasETHTest is TestBase { ILiFi.BridgeData internal bridgeDataUSDC; CBridgeFacet.CBridgeData internal cbridgeDataUSDC; - function setUp() public { + function setUp() public override { customBlockNumberForForking = 15588208; initTestBase(); diff --git a/test/solidity/Gas/Hop.t.sol b/test/solidity/Gas/Hop.t.sol index 3dcaf0431..1139b75e9 100644 --- a/test/solidity/Gas/Hop.t.sol +++ b/test/solidity/Gas/Hop.t.sol @@ -13,7 +13,7 @@ contract HopGasTest is TestBase { IHopBridge internal hop; HopFacet internal hopFacet; - function setUp() public { + function setUp() public override { // set custom block number for forking customBlockNumberForForking = 14847528; initTestBase(); diff --git a/test/solidity/Helpers/SwapperV2.t.sol b/test/solidity/Helpers/SwapperV2.t.sol index 218ba838b..f4db6da9b 100644 --- a/test/solidity/Helpers/SwapperV2.t.sol +++ b/test/solidity/Helpers/SwapperV2.t.sol @@ -39,7 +39,7 @@ contract SwapperV2Test is TestBase { TestAMM internal amm; TestSwapperV2 internal swapper; - function setUp() public { + function setUp() public override { diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); amm = new TestAMM(); swapper = new TestSwapperV2(); diff --git a/test/solidity/Helpers/WithdrawablePeriphery.t.sol b/test/solidity/Helpers/WithdrawablePeriphery.t.sol index d2df0fdce..881e0fc41 100644 --- a/test/solidity/Helpers/WithdrawablePeriphery.t.sol +++ b/test/solidity/Helpers/WithdrawablePeriphery.t.sol @@ -21,7 +21,7 @@ contract WithdrawablePeripheryTest is TestBase { error UnAuthorized(); - function setUp() public { + function setUp() public override { initTestBase(); // deploy contract diff --git a/test/solidity/Libraries/LibAsset.t.sol b/test/solidity/Libraries/LibAsset.t.sol index 2117ff71f..5c8ca5b8c 100644 --- a/test/solidity/Libraries/LibAsset.t.sol +++ b/test/solidity/Libraries/LibAsset.t.sol @@ -50,7 +50,7 @@ contract LibAssetImplementer { contract LibAssetTest is TestBase { LibAssetImplementer internal implementer; - function setUp() public { + function setUp() public override { implementer = new LibAssetImplementer(); initTestBase(); } diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 76ba452b8..d61f66fae 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; import { GasZipPeriphery } from "lifi/Periphery/GasZipPeriphery.sol"; -import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; import { LibSwap } from "lifi/Libraries/LibSwap.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { TestGnosisBridgeFacet } from "test/solidity/Facets/GnosisBridgeFacet.t.sol"; @@ -11,6 +10,10 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { LDADiamondTest } from "./Lda/utils/LdaDiamondTest.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/Lda/Facets/NativeWrapperFacet.sol"; // Stub GenericSwapFacet Contract contract TestGasZipPeriphery is GasZipPeriphery { @@ -38,12 +41,12 @@ contract GasZipPeripheryTest is TestBase { 0x2a37D63EAdFe4b4682a3c28C1c2cD4F109Cc2762; address internal constant GNOSIS_BRIDGE_ROUTER = 0x9a873656c19Efecbfb4f9FAb5B7acdeAb466a0B0; + address internal constant UNIV2_PAIR_DAI_WETH = + 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11; TestGnosisBridgeFacet internal gnosisBridgeFacet; TestGasZipPeriphery internal gasZipPeriphery; IGasZip.GasZipData internal defaultGasZipData; - LiFiDEXAggregator internal liFiDEXAggregator; - address[] internal privileged; bytes32 internal defaultReceiverBytes32 = bytes32(uint256(uint160(USER_RECEIVER))); uint256 internal defaultNativeDepositAmount = 1e16; @@ -55,30 +58,22 @@ contract GasZipPeripheryTest is TestBase { error TooManyChainIds(); error ETHTransferFailed(); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 22566858; initTestBase(); - - privileged = new address[](2); - privileged[0] = address(0xABC); - privileged[1] = address(0xEBC); - - liFiDEXAggregator = new LiFiDEXAggregator( - address(0xCAFE), - privileged, - USER_DIAMOND_OWNER - ); + LDADiamondTest.setUp(); // deploy contracts gasZipPeriphery = new TestGasZipPeriphery( GAS_ZIP_ROUTER_MAINNET, - address(liFiDEXAggregator), + address(ldaDiamond), USER_DIAMOND_OWNER ); defaultUSDCAmount = 10 * 10 ** usdc.decimals(); // 10 USDC // set up diamond with GnosisBridgeFacet so we have a bridge to test with gnosisBridgeFacet = _getGnosisBridgeFacet(); + _wireLDARouteFacets(); defaultGasZipData = IGasZip.GasZipData({ receiverAddress: defaultReceiverBytes32, @@ -91,13 +86,13 @@ contract GasZipPeripheryTest is TestBase { bridgeData.destinationChainId = 100; vm.label(address(gasZipPeriphery), "GasZipPeriphery"); - vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); + vm.label(address(ldaDiamond), "LiFiDEXAggregator"); } function test_WillStoreConstructorParametersCorrectly() public { gasZipPeriphery = new TestGasZipPeriphery( GAS_ZIP_ROUTER_MAINNET, - address(liFiDEXAggregator), + address(ldaDiamond), USER_DIAMOND_OWNER ); @@ -105,10 +100,7 @@ contract GasZipPeripheryTest is TestBase { address(gasZipPeriphery.gasZipRouter()), GAS_ZIP_ROUTER_MAINNET ); - assertEq( - gasZipPeriphery.liFiDEXAggregator(), - address(liFiDEXAggregator) - ); + assertEq(gasZipPeriphery.liFiDEXAggregator(), address(ldaDiamond)); } function test_CanDepositNative() public { @@ -228,13 +220,15 @@ contract GasZipPeripheryTest is TestBase { // // get swapData for gas zip uint256 gasZipERC20Amount = 2 * 10 ** dai.decimals(); - ( - LibSwap.SwapData memory gasZipSwapData, - - ) = _getLiFiDEXAggregatorCalldataForERC20ToNativeSwap( - address(liFiDEXAggregator), + LibSwap.SwapData + memory gasZipSwapData = _getLiFiDEXAggregatorCalldataForERC20ToNativeSwap( ADDRESS_DAI, - gasZipERC20Amount + gasZipERC20Amount, + 0, // minAmountOut + UNIV2_PAIR_DAI_WETH, + true, + 3000, + ADDRESS_WRAPPED_NATIVE ); swapData[2] = LibSwap.SwapData( @@ -406,13 +400,15 @@ contract GasZipPeripheryTest is TestBase { // // get swapData for gas zip uint256 gasZipERC20Amount = 2 * 10 ** dai.decimals(); - ( - LibSwap.SwapData memory gasZipSwapData, - - ) = _getLiFiDEXAggregatorCalldataForERC20ToNativeSwap( - address(liFiDEXAggregator), + LibSwap.SwapData + memory gasZipSwapData = _getLiFiDEXAggregatorCalldataForERC20ToNativeSwap( ADDRESS_DAI, - gasZipERC20Amount + gasZipERC20Amount, + 0, // minAmountOut + UNIV2_PAIR_DAI_WETH, + true, + 3000, + ADDRESS_WRAPPED_NATIVE ); // use an invalid function selector to force the call to LiFiDEXAggregator to fail @@ -514,50 +510,156 @@ contract GasZipPeripheryTest is TestBase { setFacetAddressInTestBase(address(gnosisBridgeFacet), "GnosisFacet"); } - function _getLiFiDEXAggregatorCalldataForERC20ToNativeSwap( - address _liFiDEXAggregator, - address _sendingAssetId, - uint256 _fromAmount + function _wireLDARouteFacets() internal { + bytes4[] memory selectors; + + CoreRouteFacet core = new CoreRouteFacet(USER_DIAMOND_OWNER); + selectors = new bytes4[](1); + selectors[0] = CoreRouteFacet.processRoute.selector; + addFacet(address(ldaDiamond), address(core), selectors); + + UniV2StyleFacet uni = new UniV2StyleFacet(); + selectors = new bytes4[](1); + selectors[0] = UniV2StyleFacet.swapUniV2.selector; + addFacet(address(ldaDiamond), address(uni), selectors); + + NativeWrapperFacet wrap = new NativeWrapperFacet(); + selectors = new bytes4[](1); + selectors[0] = NativeWrapperFacet.unwrapNative.selector; + addFacet(address(ldaDiamond), address(wrap), selectors); + } + + // Break into smaller functions to reduce stack variables + function _buildSelectorRoute_ERC20ToNative( + address tokenIn, + uint256 amountIn, + uint256 minAmountOut, + address uniV2Pool, + bool token0ToToken1, + uint24 fee, + address wrappedNative + ) private view returns (bytes memory) { + // Build legs first + ( + bytes memory uniV2LegWithLen, + bytes memory unwrapLegWithLen + ) = _buildLegs(uniV2Pool, token0ToToken1, fee); + + // Pack route data + return + _packRouteData( + tokenIn, + amountIn, + minAmountOut, + uniV2LegWithLen, + unwrapLegWithLen, + wrappedNative + ); + } + + function _buildLegs( + address uniV2Pool, + bool token0ToToken1, + uint24 fee ) - internal + private view - returns (LibSwap.SwapData memory swapData, uint256 amountOutMin) + returns (bytes memory uniV2LegWithLen, bytes memory unwrapLegWithLen) { - // prepare swap data - address[] memory path = new address[](2); - path[0] = _sendingAssetId; - path[1] = ADDRESS_WRAPPED_NATIVE; + // Build UniV2 leg + bytes memory uniV2Payload = abi.encodePacked( + uniV2Pool, + token0ToToken1 ? uint8(1) : uint8(0), + address(ldaDiamond), + fee + ); + bytes memory uniV2Leg = abi.encodePacked( + UniV2StyleFacet.swapUniV2.selector, + uniV2Payload + ); + uniV2LegWithLen = abi.encodePacked(uint16(uniV2Leg.length), uniV2Leg); - // Calculate USDC input amount - uint256[] memory amounts = uniswap.getAmountsOut(_fromAmount, path); - amountOutMin = amounts[1] - 1; - bytes memory route = abi.encodePacked( - hex"2646478b", - //pre-commit-checker: not a secret - hex"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", - abi.encode(_fromAmount), - //pre-commit-checker: not a secret - hex"000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - abi.encode(amountOutMin), - abi.encodePacked(hex"000000000000000000000000", gasZipPeriphery), - hex"00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000073026B175474E89094C44Da98b954EedeAC495271d0F01ffff00A478c2975Ab1Ea89e8196811F51A7B7Ade33eB1101", - _liFiDEXAggregator, - abi.encodePacked( - hex"000bb801C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc201ffff0200", - gasZipPeriphery - ), - hex"00000000000000000000000000" + // Build unwrap leg + bytes memory unwrapPayload = abi.encodePacked( + address(gasZipPeriphery) ); + bytes memory unwrapLeg = abi.encodePacked( + NativeWrapperFacet.unwrapNative.selector, + unwrapPayload + ); + unwrapLegWithLen = abi.encodePacked( + uint16(unwrapLeg.length), + unwrapLeg + ); + } - swapData = LibSwap.SwapData( - address(_liFiDEXAggregator), - address(_liFiDEXAggregator), - _sendingAssetId, - address(0), - _fromAmount, - // this is calldata for the DEXAggregator to swap 2 DAI to native + function _packRouteData( + address tokenIn, + uint256 amountIn, + uint256 minAmountOut, + bytes memory uniV2LegWithLen, + bytes memory unwrapLegWithLen, + address wrappedNative + ) private view returns (bytes memory) { + // Command 2: DistributeUserERC20 (DAI from GasZipPeriphery) → UniV2 leg sends WETH to diamond + bytes memory route = abi.encodePacked( + uint8(2), // DistributeUserERC20 + tokenIn, // token = DAI + uint8(1), // n = 1 leg + uint16(type(uint16).max), // share + uniV2LegWithLen // [len][selector|payload] + ); + // Command 1: DistributeSelfERC20 (WETH now on diamond) → unwrap to GasZipPeriphery + route = abi.encodePacked( route, - true + uint8(1), // DistributeSelfERC20 + wrappedNative, // token = WETH + uint8(1), // n = 1 leg + uint16(type(uint16).max), // share + unwrapLegWithLen // [len][selector|payload] + ); + + return + abi.encodeWithSelector( + CoreRouteFacet.processRoute.selector, + tokenIn, + amountIn, + address(0), // tokenOut = native + minAmountOut, + address(gasZipPeriphery), // receiver (native) + route // bytes route + ); + } + + function _getLiFiDEXAggregatorCalldataForERC20ToNativeSwap( + address tokenIn, + uint256 amountIn, + uint256 minAmountOut, + // params you already know in your test env + address uniV2Pool, + bool token0ToToken1, + uint24 fee, + address wrappedNative + ) internal view returns (LibSwap.SwapData memory) { + bytes memory routeCall = _buildSelectorRoute_ERC20ToNative( + tokenIn, + amountIn, + minAmountOut, + uniV2Pool, + token0ToToken1, + fee, + wrappedNative ); + + return + LibSwap.SwapData({ + callTo: address(ldaDiamond), // LDA diamond address (CoreRouteFacet lives here) + approveTo: address(ldaDiamond), + sendingAssetId: tokenIn, + receivingAssetId: address(0), // native + fromAmount: amountIn, + callData: routeCall, + requiresDeposit: true + }); } } diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 16c145e29..4eab85365 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -24,10 +24,10 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @dev Controls how `processRoute` resolves the source of funds. enum CommandType { None, // 0 - not used - ProcessMyERC20, // 1 - processMyERC20 (Aggregator's funds) - ProcessUserERC20, // 2 - processUserERC20 (User's funds) - ProcessNative, // 3 - processNative - ProcessOnePool, // 4 - processOnePool (Pool's funds) + DistributeSelfERC20, // 1 - distributeSelfERC20 (Aggregator's funds) + DistributeUserERC20, // 2 - distributeUserERC20 (User's funds) + DistributeNative, // 3 - distributeNative + DispatchSinglePoolSwap, // 4 - dispatchSinglePoolSwap (Pool's funds) ApplyPermit // 5 - applyPermit } @@ -141,8 +141,8 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @param swapData DEX-specific data (usually starts with facet.swapX.selector). /// @return Encoded hop bytes to be concatenated for multi-hop or passed directly for single-hop. /// @dev Format depends on command: - /// - ProcessNative: [cmd(1)][numPools(1)=1][share(2)=FULL][len(2)][data] - /// - ProcessOnePool: [cmd(1)][tokenIn(20)][len(2)][data] + /// - DistributeNative: [cmd(1)][numPools(1)=1][share(2)=FULL][len(2)][data] + /// - DispatchSinglePoolSwap: [cmd(1)][tokenIn(20)][len(2)][data] /// - Others (User/MyERC20): [cmd(1)][tokenIn(20)][numPools(1)=1][share(2)=FULL][len(2)][data] /// @custom:example Single-hop user ERC20 /// bytes memory data = abi.encodePacked(facet.swapUniV2.selector, pool, uint8(1), destinationAddress); @@ -151,7 +151,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { SwapTestParams memory params, bytes memory swapData ) internal pure returns (bytes memory) { - if (params.commandType == CommandType.ProcessNative) { + if (params.commandType == CommandType.DistributeNative) { return abi.encodePacked( uint8(params.commandType), @@ -160,7 +160,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { uint16(swapData.length), swapData ); - } else if (params.commandType == CommandType.ProcessOnePool) { + } else if (params.commandType == CommandType.DispatchSinglePoolSwap) { return abi.encodePacked( uint8(params.commandType), @@ -182,7 +182,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { } /// @notice Executes a built route and verifies balances and events. - /// @param params Swap params; if ProcessMyERC20, measures in/out at the diamond. + /// @param params Swap params; if DistributeSelfERC20, measures in/out at the diamond. /// @param route Pre-built route bytes (single or multi-hop). /// @param additionalEvents Additional external events to expect. /// @param isFeeOnTransferToken Whether tokenIn is fee-on-transfer (tolerates off-by-1 spent). @@ -196,7 +196,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { RouteEventVerification memory routeEventVerification ) internal { if ( - params.commandType != CommandType.ProcessMyERC20 && + params.commandType != CommandType.DistributeSelfERC20 && !LibAsset.isNativeAsset(params.tokenIn) ) { IERC20(params.tokenIn).approve( @@ -211,7 +211,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { : IERC20(params.tokenOut).balanceOf(params.destinationAddress); // For aggregator funds, check the diamond's balance - if (params.commandType == CommandType.ProcessMyERC20) { + if (params.commandType == CommandType.DistributeSelfERC20) { inBefore = LibAsset.isNativeAsset(params.tokenIn) ? address(ldaDiamond).balance : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); @@ -265,7 +265,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { : IERC20(params.tokenOut).balanceOf(params.destinationAddress); // Check balance change on the correct address - if (params.commandType == CommandType.ProcessMyERC20) { + if (params.commandType == CommandType.DistributeSelfERC20) { inAfter = LibAsset.isNativeAsset(params.tokenIn) ? address(ldaDiamond).balance : IERC20(params.tokenIn).balanceOf(address(ldaDiamond)); @@ -351,7 +351,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { bytes memory route, bytes4 expectedRevert ) internal { - if (params.commandType != CommandType.ProcessMyERC20) { + if (params.commandType != CommandType.DistributeSelfERC20) { IERC20(params.tokenIn).approve( address(ldaDiamond), params.amountIn @@ -361,7 +361,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { vm.expectRevert(expectedRevert); coreRouteFacet.processRoute( params.tokenIn, - params.commandType == CommandType.ProcessMyERC20 + params.commandType == CommandType.DistributeSelfERC20 ? params.amountIn : params.amountIn - 1, params.tokenOut, diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index 81c0ce892..104953506 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -78,7 +78,7 @@ abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -92,7 +92,7 @@ abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), route, SwapCallbackNotExecuted.selector diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index 891620967..b7d298eef 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -98,7 +98,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { SwapDirection direction ) internal { // Fund the appropriate account - if (params.commandType == CommandType.ProcessMyERC20) { + if (params.commandType == CommandType.DistributeSelfERC20) { // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. deal(params.tokenIn, address(ldaDiamond), params.amountIn + 1); } else { @@ -143,12 +143,13 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { function _executeUniV2StyleSwapAuto( UniV2AutoSwapParams memory params ) internal { - uint256 amountIn = params.commandType == CommandType.ProcessMyERC20 + uint256 amountIn = params.commandType == + CommandType.DistributeSelfERC20 ? params.amountIn + 1 : params.amountIn; // Fund the appropriate account - if (params.commandType == CommandType.ProcessMyERC20) { + if (params.commandType == CommandType.DistributeSelfERC20) { deal(address(tokenIn), address(ldaDiamond), amountIn + 1); } else { deal(address(tokenIn), USER_SENDER, amountIn); @@ -219,7 +220,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataZeroPool, InvalidCallData.selector @@ -249,7 +250,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataZeroDestination, InvalidCallData.selector @@ -279,7 +280,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataInvalidFee, InvalidCallData.selector @@ -314,7 +315,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { function test_CanSwap() public virtual override { _executeUniV2StyleSwapAuto( UniV2AutoSwapParams({ - commandType: CommandType.ProcessUserERC20, + commandType: CommandType.DistributeUserERC20, amountIn: _getDefaultAmountForTokenIn() }) ); @@ -324,7 +325,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { function test_CanSwap_FromDexAggregator() public virtual override { _executeUniV2StyleSwapAuto( UniV2AutoSwapParams({ - commandType: CommandType.ProcessMyERC20, + commandType: CommandType.DistributeSelfERC20, amountIn: _getDefaultAmountForTokenIn() - 1 }) ); diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 65b76fe61..0fa4c4461 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -92,7 +92,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { SwapDirection direction ) internal { // Fund the appropriate account - if (params.commandType == CommandType.ProcessMyERC20) { + if (params.commandType == CommandType.DistributeSelfERC20) { // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. deal(params.tokenIn, address(ldaDiamond), params.amountIn + 1); } else { @@ -136,12 +136,13 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { function _executeUniV3StyleSwapAuto( UniV3AutoSwapParams memory params ) internal { - uint256 amountIn = params.commandType == CommandType.ProcessMyERC20 + uint256 amountIn = params.commandType == + CommandType.DistributeSelfERC20 ? params.amountIn + 1 : params.amountIn; // Fund the appropriate account - if (params.commandType == CommandType.ProcessMyERC20) { + if (params.commandType == CommandType.DistributeSelfERC20) { deal(address(tokenIn), address(ldaDiamond), amountIn + 1); } else { deal(address(tokenIn), USER_SENDER, amountIn); @@ -224,7 +225,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { function test_CanSwap() public virtual override { _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ - commandType: CommandType.ProcessUserERC20, + commandType: CommandType.DistributeUserERC20, amountIn: _getDefaultAmountForTokenIn() }) ); @@ -234,7 +235,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { function test_CanSwap_FromDexAggregator() public virtual override { _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ - commandType: CommandType.ProcessMyERC20, + commandType: CommandType.DistributeSelfERC20, amountIn: _getDefaultAmountForTokenIn() - 1 }) ); diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index b018ff2aa..43b9beee2 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -178,7 +178,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { // Build route for algebra swap with command code 2 (user funds) bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, + commandCode: CommandType.DistributeUserERC20, tokenIn: address(tokenIn), destinationAddress: RANDOM_APE_ETH_HOLDER_APECHAIN, pool: poolInOut, @@ -194,7 +194,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: RANDOM_APE_ETH_HOLDER_APECHAIN, destinationAddress: RANDOM_APE_ETH_HOLDER_APECHAIN, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, new ExpectedEvent[](0), @@ -307,7 +307,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, SwapCallbackNotExecuted.selector @@ -334,7 +334,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { // Build route with address(0) as pool bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, + commandCode: CommandType.DistributeUserERC20, tokenIn: address(tokenIn), destinationAddress: USER_SENDER, pool: address(0), // Zero address pool @@ -350,7 +350,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, InvalidCallData.selector @@ -403,7 +403,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { // Build route with address(0) as destination address bytes memory swapData = _buildAlgebraSwapData( AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, + commandCode: CommandType.DistributeUserERC20, tokenIn: address(tokenIn), destinationAddress: address(0), // Zero address destination address pool: poolInOut, @@ -418,7 +418,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); _buildRouteAndExecuteSwap(params, swapData, InvalidCallData.selector); @@ -535,13 +535,13 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: address(ldaDiamond), // Send to aggregator for next hop - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); // Build first hop swap data swapData[0] = _buildAlgebraSwapData( AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, + commandCode: CommandType.DistributeUserERC20, tokenIn: address(state.tokenA), destinationAddress: address(ldaDiamond), pool: state.pool1, @@ -553,17 +553,17 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { swapParams[1] = SwapTestParams({ tokenIn: address(state.tokenB), tokenOut: address(state.tokenC), - amountIn: 0, // Not used for ProcessMyERC20 + amountIn: 0, // Not used for DistributeSelfERC20 minOut: 0, sender: address(ldaDiamond), destinationAddress: USER_SENDER, - commandType: CommandType.ProcessMyERC20 + commandType: CommandType.DistributeSelfERC20 }); // Build second hop swap data swapData[1] = _buildAlgebraSwapData( AlgebraRouteParams({ - commandCode: CommandType.ProcessMyERC20, + commandCode: CommandType.DistributeSelfERC20, tokenIn: address(state.tokenB), destinationAddress: USER_SENDER, pool: state.pool2, @@ -581,7 +581,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), route ); @@ -708,8 +708,8 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. CommandType commandCode = params.from == address(ldaDiamond) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; + ? CommandType.DistributeSelfERC20 + : CommandType.DistributeUserERC20; // Pack the specific data for this swap bytes memory swapData = _buildAlgebraSwapData( @@ -745,8 +745,8 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { sender: params.from, destinationAddress: params.destinationAddress, commandType: params.from == address(ldaDiamond) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 + ? CommandType.DistributeSelfERC20 + : CommandType.DistributeUserERC20 }), route, new ExpectedEvent[](0), diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 7f500150e..779a00e51 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -111,9 +111,9 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { new CoreRouteFacet(address(0)); } - /// @notice Verifies ProcessNative command passes ETH to receiver and emits Route with exact out. + /// @notice Verifies DistributeNative command passes ETH to receiver and emits Route with exact out. /// @dev Builds a route with a mock native handler and funds USER_SENDER with 1 ETH. - function test_ProcessNativeCommandSendsEthToReceiver() public { + function test_DistributeNativeCommandSendsEthToReceiver() public { _addMockNativeFacet(); uint256 amount = 1 ether; @@ -135,7 +135,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { minOut: 0, sender: USER_SENDER, // Use USER_SENDER directly destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessNative + commandType: CommandType.DistributeNative }); bytes memory route = _buildBaseRoute(params, swapData); @@ -231,7 +231,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { bytes memory swapData = abi.encodePacked(bytes4(0xdeadbeef)); - // ProcessUserERC20: [2][tokenIn][num=1][share=FULL_SHARE][len=4][selector=0xdeadbeef] + // DistributeUserERC20: [2][tokenIn][num=1][share=FULL_SHARE][len=4][selector=0xdeadbeef] bytes memory route = _buildBaseRoute( SwapTestParams({ tokenIn: address(token), @@ -240,7 +240,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -257,7 +257,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { ); } - /// @notice TokenInSpendingExceeded: trigger by charging the user twice via two ProcessUserERC20 steps. + /// @notice TokenInSpendingExceeded: trigger by charging the user twice via two DistributeUserERC20 steps. function testRevert_WhenInputBalanceIsInsufficientForTwoSteps() public { // Prepare token and approvals uint256 amountIn = 1e18; @@ -282,7 +282,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -338,7 +338,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -383,7 +383,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { bytes memory swapData = abi.encodePacked(sel); - // Build one ProcessUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] + // Build one DistributeUserERC20 step: [2][tokenIn][num=1][share=FULL_SHARE][len=4][sel] bytes memory route = _buildBaseRoute( SwapTestParams({ tokenIn: address(tokenIn), @@ -392,7 +392,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { minOut: 1, sender: USER_SENDER, destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); // single step; no tokenOut will be sent to receiver @@ -441,7 +441,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { minOut: 1, sender: USER_SENDER, destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -486,12 +486,12 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { totalAmount ); - // Build route: ProcessUserERC20 with 2 legs, 50%/50% + // Build route: DistributeUserERC20 with 2 legs, 50%/50% uint16 shareScale = type(uint16).max; uint16 halfShare = shareScale / 2; bytes memory legCalldata = abi.encodePacked(selectors[0]); // 4 bytes bytes memory route = abi.encodePacked( - uint8(2), // ProcessUserERC20 + uint8(2), // DistributeUserERC20 address(token), // tokenIn uint8(2), // n = 2 legs halfShare, @@ -573,7 +573,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { bytes memory legCalldata = abi.encodePacked(selectors[0]); // 4 bytes bytes memory route = abi.encodePacked( - uint8(2), // ProcessUserERC20 + uint8(2), // DistributeUserERC20 address(token), // tokenIn uint8(3), // n = 3 legs quarterShare, diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index ce73e0fda..68c52f10e 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -75,7 +75,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -112,7 +112,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessMyERC20 + commandType: CommandType.DistributeSelfERC20 }), swapData ); @@ -160,7 +160,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: poolMidOut, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); swapData[0] = firstSwapData; @@ -171,7 +171,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessOnePool + commandType: CommandType.DispatchSinglePoolSwap }); swapData[1] = secondSwapData; @@ -185,7 +185,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), route ); @@ -236,7 +236,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -273,7 +273,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessMyERC20 + commandType: CommandType.DistributeSelfERC20 }), swapData ); @@ -309,7 +309,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataZeroPool, InvalidCallData.selector @@ -335,7 +335,7 @@ contract CurveFacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataZeroDestination, InvalidCallData.selector diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 5cc429490..88bead39d 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -133,7 +133,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -168,7 +168,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: address(coreRouteFacet), destinationAddress: USER_SENDER, - commandType: CommandType.ProcessMyERC20 + commandType: CommandType.DistributeSelfERC20 }), swapData ); @@ -212,7 +212,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: address(coreRouteFacet), - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); swapData[0] = firstSwapData; @@ -224,7 +224,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessMyERC20 + commandType: CommandType.DistributeSelfERC20 }); swapData[1] = secondSwapData; @@ -240,7 +240,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), route ); @@ -285,7 +285,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_RECEIVER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, InvalidCallData.selector diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol new file mode 100644 index 000000000..bdcbde4b5 --- /dev/null +++ b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { IWETH } from "lifi/Interfaces/IWETH.sol"; +import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; + +/// @title NativeWrapperFacetTest +/// @notice Tests for NativeWrapperFacet wrapping/unwrapping functionality +/// @dev Verifies unwrapNative (WETH->ETH) and wrapNative (ETH->WETH) operations with various funding sources +contract NativeWrapperFacetTest is BaseCoreRouteTest { + /// @notice Facet proxy for native wrapping/unwrapping operations + NativeWrapperFacet internal nativeWrapperFacet; + + /// @notice WETH token instance for testing + IWETH internal weth; + + // ==== Custom Errors ==== + + /// @dev Thrown when native token operations fail + error NativeTransferFailed(); + /// @dev Thrown when WETH operations fail + error WETHOperationFailed(); + + // ==== Types ==== + + /// @notice Parameters for unwrapNative operation + /// @param destinationAddress Address to receive unwrapped ETH + struct UnwrapParams { + address destinationAddress; + } + + /// @notice Parameters for wrapNative operation + /// @param wrappedNative WETH contract address + /// @param destinationAddress Address to receive wrapped tokens + struct WrapParams { + address wrappedNative; + address destinationAddress; + } + + // ==== Setup Functions ==== + + function setUp() public override { + // Set fork config for mainnet WETH testing + customBlockNumberForForking = 23228012; + customRpcUrlForForking = vm.envString("ETH_NODE_URI_MAINNET"); + + fork(); + super.setUp(); + + // Deploy and register NativeWrapperFacet + nativeWrapperFacet = new NativeWrapperFacet(); + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = nativeWrapperFacet.unwrapNative.selector; + functionSelectors[1] = nativeWrapperFacet.wrapNative.selector; + + addFacet( + address(ldaDiamond), + address(nativeWrapperFacet), + functionSelectors + ); + + // Set facet instance to diamond proxy + nativeWrapperFacet = NativeWrapperFacet(address(ldaDiamond)); + + // Setup WETH token + weth = IWETH(ADDRESS_WRAPPED_NATIVE); // Use constant from TestBase + } + + // ==== Positive Test Cases ==== + + /// @notice Tests unwrapping WETH to ETH from user funds + function test_CanUnwrap() public { + uint256 amountIn = 1 ether; + + // Fund user with WETH + deal(address(weth), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + // Record initial ETH balance + uint256 initialETHBalance = USER_RECEIVER.balance; + + bytes memory swapData = _buildUnwrapSwapData( + UnwrapParams({ destinationAddress: USER_RECEIVER }) + ); + + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(weth), + tokenOut: address(0), // Native ETH + amountIn: amountIn, + minOut: amountIn, // Expect 1:1 unwrapping + sender: USER_SENDER, + destinationAddress: USER_RECEIVER, + commandType: CommandType.DistributeUserERC20 + }); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route); + + // Verify ETH was received + assertEq(USER_RECEIVER.balance, initialETHBalance + amountIn); + + vm.stopPrank(); + } + + /// @notice Tests unwrapping WETH to ETH from aggregator funds + function test_CanUnwrap_FromDexAggregator() public { + uint256 amountIn = 1 ether; + + // Fund aggregator with WETH + deal(address(weth), address(ldaDiamond), amountIn + 1); + + vm.startPrank(USER_SENDER); + + // Record initial ETH balance + uint256 initialETHBalance = USER_RECEIVER.balance; + + bytes memory swapData = _buildUnwrapSwapData( + UnwrapParams({ destinationAddress: USER_RECEIVER }) + ); + + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(weth), + tokenOut: address(0), // Native ETH + amountIn: amountIn, + minOut: amountIn, // Expect 1:1 unwrapping + sender: USER_SENDER, + destinationAddress: USER_RECEIVER, + commandType: CommandType.DistributeSelfERC20 + }); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route); + + // Verify ETH was received + assertEq(USER_RECEIVER.balance, initialETHBalance + amountIn); + + vm.stopPrank(); + } + + /// @notice Tests wrapping ETH to WETH + function test_CanWrap2() public { + uint256 amountIn = 1 ether; + + // Fund aggregator with ETH + vm.deal(USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + // // Record initial WETH balance + uint256 initialWETHBalance = weth.balanceOf(USER_RECEIVER); + + bytes memory swapData = _buildWrapSwapData( + WrapParams({ + wrappedNative: address(weth), + destinationAddress: USER_RECEIVER + }) + ); + + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(0), // Native ETH + tokenOut: address(weth), + amountIn: amountIn, + minOut: amountIn, // Expect 1:1 wrapping + sender: USER_SENDER, + destinationAddress: USER_RECEIVER, + commandType: CommandType.DistributeNative + }); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route); + + // Verify WETH was received + assertEq(weth.balanceOf(USER_RECEIVER), initialWETHBalance + amountIn); + + vm.stopPrank(); + } + + /// @notice Tests wrapping ETH to WETH and keeping on aggregator + function test_CanWrap_ToAggregator() public { + uint256 amountIn = 1 ether; + + // Fund aggregator with ETH + vm.deal(USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + // Record initial WETH balance + uint256 initialWETHBalance = weth.balanceOf(address(ldaDiamond)); + + bytes memory swapData = _buildWrapSwapData( + WrapParams({ + wrappedNative: address(weth), + destinationAddress: address(ldaDiamond) + }) + ); + + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(0), // Native ETH + tokenOut: address(weth), + amountIn: amountIn, + minOut: amountIn, // Expect 1:1 wrapping + sender: USER_SENDER, + destinationAddress: address(ldaDiamond), + commandType: CommandType.DistributeNative + }); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route); + + // Verify WETH was received by aggregator + assertEq( + weth.balanceOf(address(ldaDiamond)), + initialWETHBalance + amountIn + ); + + vm.stopPrank(); + } + + // ==== Negative Test Cases ==== + + /// @notice Tests that unwrapNative reverts with zero destination address + function testRevert_UnwrapNative_ZeroDestinationAddress() public { + uint256 amountIn = 1 ether; + + // Fund user with WETH + deal(address(weth), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildUnwrapSwapData( + UnwrapParams({ + destinationAddress: address(0) // Invalid destination + }) + ); + + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(weth), + tokenOut: address(0), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_RECEIVER, + commandType: CommandType.DistributeUserERC20 + }); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route, InvalidCallData.selector); + + vm.stopPrank(); + } + + /// @notice Tests that wrapNative reverts with zero wrapped native address + function testRevert_WrapNative_ZeroWrappedNative() public { + uint256 amountIn = 1 ether; + + // Fund aggregator with ETH + vm.deal(address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildWrapSwapData( + WrapParams({ + wrappedNative: address(0), // Invalid wrapped native + destinationAddress: USER_RECEIVER + }) + ); + + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(0), + tokenOut: address(weth), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_RECEIVER, + commandType: CommandType.DistributeNative + }); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route, InvalidCallData.selector); + + vm.stopPrank(); + } + + /// @notice Tests that wrapNative reverts with zero destination address + function testRevert_WrapNative_ZeroDestinationAddress() public { + uint256 amountIn = 1 ether; + + // Fund aggregator with ETH + vm.deal(address(ldaDiamond), amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildWrapSwapData( + WrapParams({ + wrappedNative: address(weth), + destinationAddress: address(0) // Invalid destination + }) + ); + + SwapTestParams memory params = SwapTestParams({ + tokenIn: address(0), + tokenOut: address(weth), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_RECEIVER, + commandType: CommandType.DistributeNative + }); + + bytes memory route = _buildBaseRoute(params, swapData); + _executeAndVerifySwap(params, route, InvalidCallData.selector); + + vm.stopPrank(); + } + + /// @notice Tests that unwrapNative reverts with invalid from address (INTERNAL_INPUT_SOURCE) + function testRevert_UnwrapNative_InvalidFromAddress() public { + uint256 amountIn = 1 ether; + + vm.startPrank(USER_SENDER); + + // Manually call the facet with INTERNAL_INPUT_SOURCE to trigger the error + vm.expectRevert(InvalidCallData.selector); + nativeWrapperFacet.unwrapNative( + abi.encodePacked(USER_RECEIVER), + 0x000000000000000000000000000000000000dEaD, // INTERNAL_INPUT_SOURCE + address(weth), + amountIn + ); + + vm.stopPrank(); + } + + // ==== Helper Functions ==== + + /// @notice Builds swap data for unwrapNative operation + /// @param params Unwrap parameters + /// @return Packed swap data with selector and parameters + function _buildUnwrapSwapData( + UnwrapParams memory params + ) internal view returns (bytes memory) { + return + abi.encodePacked( + nativeWrapperFacet.unwrapNative.selector, + params.destinationAddress + ); + } + + /// @notice Builds swap data for wrapNative operation + /// @param params Wrap parameters + /// @return Packed swap data with selector and parameters + function _buildWrapSwapData( + WrapParams memory params + ) internal view returns (bytes memory) { + return + abi.encodePacked( + nativeWrapperFacet.wrapNative.selector, + params.wrappedNative, + params.destinationAddress + ); + } +} diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol index 2b8347a51..9a30042d3 100644 --- a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol +++ b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol @@ -63,7 +63,7 @@ contract PancakeV2FacetTest is BaseUniV2StyleDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, WrongPoolReserves.selector diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 94b859743..2ac1c634b 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -53,7 +53,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, InvalidCallData.selector @@ -85,7 +85,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, InvalidCallData.selector diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 89ea210d4..a6cead1af 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -8,7 +8,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title SyncSwapV2FacetTest /// @notice Linea SyncSwap V2 tests via LDA route; includes both v1 and v2 pool wiring. -/// @dev Verifies single-hop, aggregator flow, multi-hop (with ProcessOnePool), and revert paths. +/// @dev Verifies single-hop, aggregator flow, multi-hop (with DispatchSinglePoolSwap), and revert paths. contract SyncSwapV2FacetTest is BaseDEXFacetTest { /// @notice Facet proxy for swaps bound to the diamond after setup. SyncSwapV2Facet internal syncSwapV2Facet; @@ -82,7 +82,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -115,7 +115,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData ); @@ -152,7 +152,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessMyERC20 + commandType: CommandType.DistributeSelfERC20 }), swapData ); @@ -189,7 +189,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessMyERC20 + commandType: CommandType.DistributeSelfERC20 }), swapData ); @@ -237,7 +237,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: SYNC_SWAP_VAULT, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); swapData[0] = firstSwapData; @@ -245,11 +245,11 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { params[1] = SwapTestParams({ tokenIn: address(tokenMid), tokenOut: address(tokenOut), - amountIn: 0, // Not used in ProcessOnePool + amountIn: 0, // Not used in DispatchSinglePoolSwap minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessOnePool + commandType: CommandType.DistributeSelfERC20 }); swapData[1] = secondSwapData; @@ -264,7 +264,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), route ); @@ -297,7 +297,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapData, InvalidCallData.selector @@ -331,7 +331,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, // Send to next pool destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataWithInvalidPool, InvalidCallData.selector @@ -356,7 +356,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataWithInvalidDestinationAddress, InvalidCallData.selector @@ -388,7 +388,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataWithInvalidWithdrawMode, InvalidCallData.selector diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 1e31a052a..4b0ff3a1e 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -310,7 +310,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: params.pool2, // Send to next pool - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); // Build first hop swap data @@ -331,7 +331,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { sender: params.pool2, minOut: 0, destinationAddress: USER_SENDER, // Send to next pool - commandType: CommandType.ProcessOnePool + commandType: CommandType.DispatchSinglePoolSwap }); // Build second hop swap data @@ -355,7 +355,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), route ); @@ -407,7 +407,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: params.pool2, // Send to next pool - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); hopData[0] = _buildVelodromeV2SwapData( @@ -427,7 +427,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { sender: params.pool2, minOut: 0, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessOnePool + commandType: CommandType.DispatchSinglePoolSwap }); hopData[1] = _buildVelodromeV2SwapData( @@ -449,7 +449,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), route ); @@ -487,7 +487,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataZeroPool, InvalidCallData.selector @@ -512,7 +512,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }), swapDataZeroDestinationAddress, InvalidCallData.selector @@ -545,7 +545,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { minOut: 0, sender: USER_SENDER, destinationAddress: params.pool2, // Send to next pool - commandType: CommandType.ProcessUserERC20 + commandType: CommandType.DistributeUserERC20 }); hopData[0] = _buildVelodromeV2SwapData( @@ -561,11 +561,11 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { hopParams[1] = SwapTestParams({ tokenIn: params.tokenMid, tokenOut: params.tokenOut, - amountIn: 0, // Not used in ProcessOnePool + amountIn: 0, // Not used in DispatchSinglePoolSwap minOut: 0, sender: params.pool2, destinationAddress: USER_SENDER, - commandType: CommandType.ProcessOnePool + commandType: CommandType.DispatchSinglePoolSwap }); hopData[1] = _buildVelodromeV2SwapData( @@ -586,7 +586,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { _getDefaultAmountForTokenIn() ); - // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves + // Mock getReserves for the second pool (which uses dispatchSinglePoolSwap) to return zero reserves vm.mockCall( params.pool2, abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), @@ -654,8 +654,8 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. CommandType commandCode = params.from == address(ldaDiamond) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; + ? CommandType.DistributeSelfERC20 + : CommandType.DistributeUserERC20; // 1. Pack the data for the specific swap FIRST bytes memory swapData = _buildVelodromeV2SwapData( @@ -713,8 +713,8 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { sender: params.from, destinationAddress: params.to, commandType: params.from == address(ldaDiamond) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20 + ? CommandType.DistributeSelfERC20 + : CommandType.DistributeUserERC20 }), route, expectedEvents, diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 4c4a57b79..281f3e929 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -21,14 +21,14 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. function setUp() public virtual { - ldaDiamond = createLdaDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + ldaDiamond = createLDADiamond(USER_DIAMOND_OWNER, USER_PAUSER); } /// @notice Creates an LDA diamond and wires up Loupe, Ownership and EmergencyPause facets. /// @param _diamondOwner Owner address for the diamond. /// @param _pauserWallet Pauser address for the emergency pause facet. /// @return diamond The newly created diamond instance. - function createLdaDiamond( + function createLDADiamond( address _diamondOwner, address _pauserWallet ) internal returns (LDADiamond) { diff --git a/test/solidity/Periphery/LiFiDEXAggregator.t.sol b/test/solidity/Periphery/LiFiDEXAggregator.t.sol deleted file mode 100644 index 69e948f35..000000000 --- a/test/solidity/Periphery/LiFiDEXAggregator.t.sol +++ /dev/null @@ -1,3382 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.17; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; -import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; -import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; -import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; -import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { IHyperswapV3QuoterV2 } from "lifi/Interfaces/IHyperswapV3QuoterV2.sol"; -import { LiFiDEXAggregator } from "lifi/Periphery/LiFiDEXAggregator.sol"; -import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { TestBase } from "../utils/TestBase.sol"; -import { TestToken as ERC20 } from "../utils/TestToken.sol"; -import { MockFeeOnTransferToken } from "../utils/MockTokenFeeOnTransfer.sol"; - -// Command codes for route processing -enum CommandType { - None, // 0 - not used - ProcessMyERC20, // 1 - processMyERC20 - ProcessUserERC20, // 2 - processUserERC20 - ProcessNative, // 3 - processNative - ProcessOnePool, // 4 - processOnePool - ProcessInsideBento, // 5 - processInsideBento - ApplyPermit // 6 - applyPermit -} - -// Pool type identifiers -enum PoolType { - UniV2, // 0 - UniV3, // 1 - WrapNative, // 2 - BentoBridge, // 3 - Trident, // 4 - Curve, // 5 - VelodromeV2, // 6 - Algebra, // 7 - iZiSwap, // 8 - SyncSwapV2 // 9 -} - -// Direction constants -enum SwapDirection { - Token1ToToken0, // 0 - Token0ToToken1 // 1 -} - -// Callback constants -enum CallbackStatus { - Disabled, // 0 - Enabled // 1 -} - -// Other constants -uint16 constant FULL_SHARE = 65535; // 100% share for single pool swaps - -contract MockVelodromeV2FlashLoanCallbackReceiver is IVelodromeV2PoolCallee { - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - function hook( - address sender, - uint256 amount0, - uint256 amount1, - bytes calldata data - ) external { - emit HookCalled(sender, amount0, amount1, data); - } -} -/** - * @title LiFiDexAggregatorTest - * @notice Base test contract with common functionality and abstractions for DEX-specific tests - */ -abstract contract LiFiDexAggregatorTest is TestBase { - using SafeERC20 for IERC20; - - // Common variables - LiFiDEXAggregator internal liFiDEXAggregator; - address[] internal privileged; - - // Common events and errors - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - event HookCalled( - address sender, - uint256 amount0, - uint256 amount1, - bytes data - ); - - error WrongPoolReserves(); - error PoolDoesNotExist(); - - // helper function to initialize the aggregator - function _initializeDexAggregator(address owner) internal { - privileged = new address[](1); - privileged[0] = owner; - - liFiDEXAggregator = new LiFiDEXAggregator( - address(0xCAFE), - privileged, - owner - ); - vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); - } - - // Setup function for Apechain tests - function setupApechain() internal { - customRpcUrlForForking = "ETH_NODE_URI_APECHAIN"; - customBlockNumberForForking = 12912470; - fork(); - - _initializeDexAggregator(address(USER_DIAMOND_OWNER)); - } - - function setupHyperEVM() internal { - customRpcUrlForForking = "ETH_NODE_URI_HYPEREVM"; - customBlockNumberForForking = 4433562; - fork(); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - function setUp() public virtual { - initTestBase(); - vm.label(USER_SENDER, "USER_SENDER"); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - function test_ContractIsSetUpCorrectly() public { - assertEq(address(liFiDEXAggregator.BENTO_BOX()), address(0xCAFE)); - assertEq( - liFiDEXAggregator.priviledgedUsers(address(USER_DIAMOND_OWNER)), - true - ); - assertEq(liFiDEXAggregator.owner(), USER_DIAMOND_OWNER); - } - - function testRevert_FailsIfOwnerIsZeroAddress() public { - vm.expectRevert(InvalidConfig.selector); - - liFiDEXAggregator = new LiFiDEXAggregator( - address(0xCAFE), - privileged, - address(0) - ); - } - - // ============================ Abstract DEX Tests ============================ - /** - * @notice Abstract test for basic token swapping functionality - * Each DEX implementation should override this - */ - function test_CanSwap() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap: Not implemented"); - } - - /** - * @notice Abstract test for swapping tokens from the DEX aggregator - * Each DEX implementation should override this - */ - function test_CanSwap_FromDexAggregator() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_FromDexAggregator: Not implemented"); - } - - /** - * @notice Abstract test for multi-hop swapping - * Each DEX implementation should override this - */ - function test_CanSwap_MultiHop() public virtual { - // Each DEX implementation must override this - // solhint-disable-next-line gas-custom-errors - revert("test_CanSwap_MultiHop: Not implemented"); - } -} - -/** - * @title VelodromeV2 tests - * @notice Tests specific to Velodrome V2 pool type - */ -contract LiFiDexAggregatorVelodromeV2Test is LiFiDexAggregatorTest { - // ==================== Velodrome V2 specific variables ==================== - IVelodromeV2Router internal constant VELODROME_V2_ROUTER = - IVelodromeV2Router(0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858); // optimism router - address internal constant VELODROME_V2_FACTORY_REGISTRY = - 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a; - IERC20 internal constant STG_TOKEN = - IERC20(0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97); - IERC20 internal constant USDC_E_TOKEN = - IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607); - - MockVelodromeV2FlashLoanCallbackReceiver - internal mockFlashloanCallbackReceiver; - - // Velodrome V2 structs - struct VelodromeV2SwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; - bool stable; - SwapDirection direction; - bool callback; - } - - struct MultiHopTestParams { - address tokenIn; - address tokenMid; - address tokenOut; - address pool1; - address pool2; - uint256[] amounts1; - uint256[] amounts2; - uint256 pool1Fee; - uint256 pool2Fee; - } - - struct ReserveState { - uint256 reserve0Pool1; - uint256 reserve1Pool1; - uint256 reserve0Pool2; - uint256 reserve1Pool2; - } - - // Setup function for Optimism tests - function setupOptimism() internal { - customRpcUrlForForking = "ETH_NODE_URI_OPTIMISM"; - customBlockNumberForForking = 133999121; - initTestBase(); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - function setUp() public override { - setupOptimism(); - } - - // // ============================ Velodrome V2 Tests ============================ - - // no stable swap - function test_CanSwap() public override { - vm.startPrank(USER_SENDER); - - _testSwap( - VelodromeV2SwapTestParams({ - from: address(USER_SENDER), - to: address(USER_SENDER), - tokenIn: ADDRESS_USDC, - amountIn: 1_000 * 1e6, - tokenOut: address(STG_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callback: false - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_NoStable_Reverse() public { - // first perform the forward swap. - test_CanSwap(); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(STG_TOKEN), - amountIn: 500 * 1e18, - tokenOut: ADDRESS_USDC, - stable: false, - direction: SwapDirection.Token1ToToken0, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_Stable() public { - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: ADDRESS_USDC, - amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), - stable: true, - direction: SwapDirection.Token0ToToken1, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_Stable_Reverse() public { - // first perform the forward stable swap. - test_CanSwap_Stable(); - - vm.startPrank(USER_SENDER); - - _testSwap( - VelodromeV2SwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(USDC_E_TOKEN), - amountIn: 500 * 1e6, - tokenOut: ADDRESS_USDC, - stable: false, - direction: SwapDirection.Token1ToToken0, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - // fund dex aggregator contract so that the contract holds USDC - deal(ADDRESS_USDC, address(liFiDEXAggregator), 100_000 * 1e6); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: address(liFiDEXAggregator), - to: address(USER_SENDER), - tokenIn: ADDRESS_USDC, - amountIn: IERC20(ADDRESS_USDC).balanceOf( - address(liFiDEXAggregator) - ) - 1, // adjust for slot undrain protection: subtract 1 token so that the aggregator's balance isn't completely drained, matching the contract's safeguard - tokenOut: address(USDC_E_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callback: false - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_FlashloanCallback() public { - mockFlashloanCallbackReceiver = new MockVelodromeV2FlashLoanCallbackReceiver(); - - vm.startPrank(USER_SENDER); - _testSwap( - VelodromeV2SwapTestParams({ - from: address(USER_SENDER), - to: address(mockFlashloanCallbackReceiver), - tokenIn: ADDRESS_USDC, - amountIn: 1_000 * 1e6, - tokenOut: address(USDC_E_TOKEN), - stable: false, - direction: SwapDirection.Token0ToToken1, - callback: true - }) - ); - vm.stopPrank(); - } - - // Override the abstract test with VelodromeV2 implementation - function test_CanSwap_MultiHop() public override { - vm.startPrank(USER_SENDER); - - // Setup routes and get amounts - MultiHopTestParams memory params = _setupRoutes( - ADDRESS_USDC, - address(STG_TOKEN), - address(USDC_E_TOKEN), - false, - false - ); - - // Get initial reserves BEFORE the swap - ReserveState memory initialReserves; - ( - initialReserves.reserve0Pool1, - initialReserves.reserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - initialReserves.reserve0Pool2, - initialReserves.reserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - - // Build route and execute swap - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 1); - - // Approve and execute - IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); - - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - params.tokenIn, - params.tokenOut, - 1000 * 1e6, - params.amounts2[1], - params.amounts2[1] - ); - - liFiDEXAggregator.processRoute( - params.tokenIn, - 1000 * 1e6, - params.tokenOut, - params.amounts2[1], - USER_SENDER, - route - ); - - _verifyUserBalances(params, initialBalance1, initialBalance2); - _verifyReserves(params, initialReserves); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop_WithStable() public { - vm.startPrank(USER_SENDER); - - // Setup routes and get amounts for stable->volatile path - MultiHopTestParams memory params = _setupRoutes( - ADDRESS_USDC, - address(USDC_E_TOKEN), - address(STG_TOKEN), - true, // stable pool for first hop - false // volatile pool for second hop - ); - - // Get initial reserves BEFORE the swap - ReserveState memory initialReserves; - ( - initialReserves.reserve0Pool1, - initialReserves.reserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - initialReserves.reserve0Pool2, - initialReserves.reserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - // Record initial balances - uint256 initialBalance1 = IERC20(params.tokenIn).balanceOf( - USER_SENDER - ); - uint256 initialBalance2 = IERC20(params.tokenOut).balanceOf( - USER_SENDER - ); - - // Build route and execute swap - bytes memory route = _buildMultiHopRoute(params, USER_SENDER, 1, 0); - - // Approve and execute - IERC20(params.tokenIn).approve(address(liFiDEXAggregator), 1000 * 1e6); - - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - params.tokenIn, - params.tokenOut, - 1000 * 1e6, - params.amounts2[1], - params.amounts2[1] - ); - - liFiDEXAggregator.processRoute( - params.tokenIn, - 1000 * 1e6, - params.tokenOut, - params.amounts2[1], - USER_SENDER, - route - ); - - _verifyUserBalances(params, initialBalance1, initialBalance2); - _verifyReserves(params, initialReserves); - - vm.stopPrank(); - } - - function testRevert_InvalidPoolOrRecipient() public { - vm.startPrank(USER_SENDER); - - // Get a valid pool address first for comparison - address validPool = VELODROME_V2_ROUTER.poolFor( - ADDRESS_USDC, - address(STG_TOKEN), - false, - VELODROME_V2_FACTORY_REGISTRY - ); - - // Test case 1: Zero pool address - bytes memory routeWithZeroPool = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - ADDRESS_USDC, - uint8(1), - FULL_SHARE, - uint8(PoolType.VelodromeV2), - address(0), - uint8(SwapDirection.Token1ToToken0), - USER_SENDER, - uint8(CallbackStatus.Disabled) - ); - - IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); - - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - ADDRESS_USDC, - 1000 * 1e6, - address(STG_TOKEN), - 0, - USER_SENDER, - routeWithZeroPool - ); - - // Test case 2: Zero recipient address - bytes memory routeWithZeroRecipient = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - ADDRESS_USDC, - uint8(1), - FULL_SHARE, - uint8(PoolType.VelodromeV2), - validPool, - uint8(SwapDirection.Token1ToToken0), - address(0), - uint8(CallbackStatus.Disabled) - ); - - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - ADDRESS_USDC, - 1000 * 1e6, - address(STG_TOKEN), - 0, - USER_SENDER, - routeWithZeroRecipient - ); - - vm.stopPrank(); - } - - function testRevert_WrongPoolReserves() public { - vm.startPrank(USER_SENDER); - - // Setup multi-hop route: USDC -> STG -> USDC.e - MultiHopTestParams memory params = _setupRoutes( - ADDRESS_USDC, - address(STG_TOKEN), - address(USDC_E_TOKEN), - false, - false - ); - - // Build multi-hop route - bytes memory firstHop = _buildFirstHop( - params.tokenIn, - params.pool1, - params.pool2, - 1 // direction - ); - - bytes memory secondHop = _buildSecondHop( - params.tokenMid, - params.pool2, - USER_SENDER, - 0 // direction - ); - - bytes memory route = bytes.concat(firstHop, secondHop); - - deal(ADDRESS_USDC, USER_SENDER, 1000 * 1e6); - - IERC20(ADDRESS_USDC).approve(address(liFiDEXAggregator), 1000 * 1e6); - - // Mock getReserves for the second pool (which uses processOnePool) to return zero reserves - vm.mockCall( - params.pool2, - abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector), - abi.encode(0, 0, block.timestamp) - ); - - vm.expectRevert(WrongPoolReserves.selector); - - liFiDEXAggregator.processRoute( - ADDRESS_USDC, - 1000 * 1e6, - address(USDC_E_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - // ============================ Velodrome V2 Helper Functions ============================ - - /** - * @dev Helper function to test a VelodromeV2 swap. - * Uses a struct to group parameters and reduce stack depth. - */ - function _testSwap(VelodromeV2SwapTestParams memory params) internal { - // get expected output amounts from the router. - IVelodromeV2Router.Route[] - memory routes = new IVelodromeV2Router.Route[](1); - routes[0] = IVelodromeV2Router.Route({ - from: params.tokenIn, - to: params.tokenOut, - stable: params.stable, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - uint256[] memory amounts = VELODROME_V2_ROUTER.getAmountsOut( - params.amountIn, - routes - ); - emit log_named_uint("Expected amount out", amounts[1]); - - // Retrieve the pool address. - address pool = VELODROME_V2_ROUTER.poolFor( - params.tokenIn, - params.tokenOut, - params.stable, - VELODROME_V2_FACTORY_REGISTRY - ); - emit log_named_uint("Pool address:", uint256(uint160(pool))); - - // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. - CommandType commandCode = params.from == address(liFiDEXAggregator) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; - - // build the route. - bytes memory route = abi.encodePacked( - uint8(commandCode), - params.tokenIn, - uint8(1), - FULL_SHARE, - uint8(PoolType.VelodromeV2), - pool, - params.direction, - params.to, - params.callback - ? uint8(CallbackStatus.Enabled) - : uint8(CallbackStatus.Disabled) - ); - - // approve the aggregator to spend tokenIn. - IERC20(params.tokenIn).approve( - address(liFiDEXAggregator), - params.amountIn - ); - - // capture initial token balances. - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - emit log_named_uint("Initial tokenIn balance", initialTokenIn); - - address from = params.from == address(liFiDEXAggregator) - ? USER_SENDER - : params.from; - if (params.callback == true) { - vm.expectEmit(true, false, false, false); - emit HookCalled( - address(liFiDEXAggregator), - 0, - 0, - abi.encode(params.tokenIn) - ); - } - vm.expectEmit(true, true, true, true); - emit Route( - from, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - amounts[1], - amounts[1] - ); - - // execute the swap - liFiDEXAggregator.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - amounts[1], - params.to, - route - ); - - uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - emit log_named_uint("TokenIn spent", initialTokenIn - finalTokenIn); - emit log_named_uint( - "TokenOut received", - finalTokenOut - initialTokenOut - ); - - assertApproxEqAbs( - initialTokenIn - finalTokenIn, - params.amountIn, - 1, // 1 wei tolerance - "TokenIn amount mismatch" - ); - assertEq( - finalTokenOut - initialTokenOut, - amounts[1], - "TokenOut amount mismatch" - ); - } - - // Helper function to set up routes and get amounts - function _setupRoutes( - address tokenIn, - address tokenMid, - address tokenOut, - bool isStableFirst, - bool isStableSecond - ) private view returns (MultiHopTestParams memory params) { - params.tokenIn = tokenIn; - params.tokenMid = tokenMid; - params.tokenOut = tokenOut; - - // Setup first hop route - IVelodromeV2Router.Route[] - memory routes1 = new IVelodromeV2Router.Route[](1); - routes1[0] = IVelodromeV2Router.Route({ - from: tokenIn, - to: tokenMid, - stable: isStableFirst, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - params.amounts1 = VELODROME_V2_ROUTER.getAmountsOut( - 1000 * 1e6, - routes1 - ); - - // Setup second hop route - IVelodromeV2Router.Route[] - memory routes2 = new IVelodromeV2Router.Route[](1); - routes2[0] = IVelodromeV2Router.Route({ - from: tokenMid, - to: tokenOut, - stable: isStableSecond, - factory: address(VELODROME_V2_FACTORY_REGISTRY) - }); - params.amounts2 = VELODROME_V2_ROUTER.getAmountsOut( - params.amounts1[1], - routes2 - ); - - // Get pool addresses - params.pool1 = VELODROME_V2_ROUTER.poolFor( - tokenIn, - tokenMid, - isStableFirst, - VELODROME_V2_FACTORY_REGISTRY - ); - - params.pool2 = VELODROME_V2_ROUTER.poolFor( - tokenMid, - tokenOut, - isStableSecond, - VELODROME_V2_FACTORY_REGISTRY - ); - - // Get pool fees info - params.pool1Fee = IVelodromeV2PoolFactory( - VELODROME_V2_FACTORY_REGISTRY - ).getFee(params.pool1, isStableFirst); - params.pool2Fee = IVelodromeV2PoolFactory( - VELODROME_V2_FACTORY_REGISTRY - ).getFee(params.pool2, isStableSecond); - - return params; - } - - // function to build first hop of the route - function _buildFirstHop( - address tokenIn, - address pool1, - address pool2, - uint8 direction - ) private pure returns (bytes memory) { - return - abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - tokenIn, - uint8(1), - FULL_SHARE, - uint8(PoolType.VelodromeV2), - pool1, - direction, - pool2, - uint8(CallbackStatus.Disabled) - ); - } - - // function to build second hop of the route - function _buildSecondHop( - address tokenMid, - address pool2, - address recipient, - uint8 direction - ) private pure returns (bytes memory) { - return - abi.encodePacked( - uint8(CommandType.ProcessOnePool), - tokenMid, - uint8(PoolType.VelodromeV2), - pool2, - direction, - recipient, - uint8(CallbackStatus.Disabled) - ); - } - - // route building function - function _buildMultiHopRoute( - MultiHopTestParams memory params, - address recipient, - uint8 firstHopDirection, - uint8 secondHopDirection - ) private pure returns (bytes memory) { - bytes memory firstHop = _buildFirstHop( - params.tokenIn, - params.pool1, - params.pool2, - firstHopDirection - ); - - bytes memory secondHop = _buildSecondHop( - params.tokenMid, - params.pool2, - recipient, - secondHopDirection - ); - - return bytes.concat(firstHop, secondHop); - } - - function _verifyUserBalances( - MultiHopTestParams memory params, - uint256 initialBalance1, - uint256 initialBalance2 - ) private { - // Verify token balances - uint256 finalBalance1 = IERC20(params.tokenIn).balanceOf(USER_SENDER); - uint256 finalBalance2 = IERC20(params.tokenOut).balanceOf(USER_SENDER); - - assertApproxEqAbs( - initialBalance1 - finalBalance1, - 1000 * 1e6, - 1, // 1 wei tolerance - "Token1 spent amount mismatch" - ); - assertEq( - finalBalance2 - initialBalance2, - params.amounts2[1], - "Token2 received amount mismatch" - ); - } - - function _verifyReserves( - MultiHopTestParams memory params, - ReserveState memory initialReserves - ) private { - // Get reserves after swap - ( - uint256 finalReserve0Pool1, - uint256 finalReserve1Pool1, - - ) = IVelodromeV2Pool(params.pool1).getReserves(); - ( - uint256 finalReserve0Pool2, - uint256 finalReserve1Pool2, - - ) = IVelodromeV2Pool(params.pool2).getReserves(); - - address token0Pool1 = IVelodromeV2Pool(params.pool1).token0(); - address token0Pool2 = IVelodromeV2Pool(params.pool2).token0(); - - // Calculate exact expected changes - uint256 amountInAfterFees = 1000 * - 1e6 - - ((1000 * 1e6 * params.pool1Fee) / 10000); - - // Assert exact reserve changes for Pool1 - if (token0Pool1 == params.tokenIn) { - // tokenIn is token0, so reserve0 should increase and reserve1 should decrease - assertEq( - finalReserve0Pool1 - initialReserves.reserve0Pool1, - amountInAfterFees, - "Pool1 reserve0 (tokenIn) change incorrect" - ); - assertEq( - initialReserves.reserve1Pool1 - finalReserve1Pool1, - params.amounts1[1], - "Pool1 reserve1 (tokenMid) change incorrect" - ); - } else { - // tokenIn is token1, so reserve1 should increase and reserve0 should decrease - assertEq( - finalReserve1Pool1 - initialReserves.reserve1Pool1, - amountInAfterFees, - "Pool1 reserve1 (tokenIn) change incorrect" - ); - assertEq( - initialReserves.reserve0Pool1 - finalReserve0Pool1, - params.amounts1[1], - "Pool1 reserve0 (tokenMid) change incorrect" - ); - } - - // Assert exact reserve changes for Pool2 - if (token0Pool2 == params.tokenMid) { - // tokenMid is token0, so reserve0 should increase and reserve1 should decrease - assertEq( - finalReserve0Pool2 - initialReserves.reserve0Pool2, - params.amounts1[1] - - ((params.amounts1[1] * params.pool2Fee) / 10000), - "Pool2 reserve0 (tokenMid) change incorrect" - ); - assertEq( - initialReserves.reserve1Pool2 - finalReserve1Pool2, - params.amounts2[1], - "Pool2 reserve1 (tokenOut) change incorrect" - ); - } else { - // tokenMid is token1, so reserve1 should increase and reserve0 should decrease - assertEq( - finalReserve1Pool2 - initialReserves.reserve1Pool2, - params.amounts1[1] - - ((params.amounts1[1] * params.pool2Fee) / 10000), - "Pool2 reserve1 (tokenMid) change incorrect" - ); - assertEq( - initialReserves.reserve0Pool2 - finalReserve0Pool2, - params.amounts2[1], - "Pool2 reserve0 (tokenOut) change incorrect" - ); - } - } -} - -contract AlgebraLiquidityAdderHelper { - address public immutable TOKEN_0; - address public immutable TOKEN_1; - - constructor(address _token0, address _token1) { - TOKEN_0 = _token0; - TOKEN_1 = _token1; - } - - function addLiquidity( - address pool, - int24 bottomTick, - int24 topTick, - uint128 amount - ) - external - returns (uint256 amount0, uint256 amount1, uint128 liquidityActual) - { - // Get balances before - uint256 balance0Before = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1Before = IERC20(TOKEN_1).balanceOf(address(this)); - - // Call mint - (amount0, amount1, liquidityActual) = IAlgebraPool(pool).mint( - address(this), - address(this), - bottomTick, - topTick, - amount, - abi.encode(TOKEN_0, TOKEN_1) - ); - - // Get balances after to account for fees - uint256 balance0After = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1After = IERC20(TOKEN_1).balanceOf(address(this)); - - // Calculate actual amounts transferred accounting for fees - amount0 = balance0Before - balance0After; - amount1 = balance1Before - balance1After; - - return (amount0, amount1, liquidityActual); - } - - function algebraMintCallback( - uint256 amount0Owed, - uint256 amount1Owed, - bytes calldata - ) external { - // Check token balances - uint256 balance0 = IERC20(TOKEN_0).balanceOf(address(this)); - uint256 balance1 = IERC20(TOKEN_1).balanceOf(address(this)); - - // Transfer what we can, limited by actual balance - if (amount0Owed > 0) { - uint256 amount0ToSend = amount0Owed > balance0 - ? balance0 - : amount0Owed; - uint256 balance0Before = IERC20(TOKEN_0).balanceOf( - address(msg.sender) - ); - IERC20(TOKEN_0).transfer(msg.sender, amount0ToSend); - uint256 balance0After = IERC20(TOKEN_0).balanceOf( - address(msg.sender) - ); - // solhint-disable-next-line gas-custom-errors - require(balance0After > balance0Before, "Transfer failed"); - } - - if (amount1Owed > 0) { - uint256 amount1ToSend = amount1Owed > balance1 - ? balance1 - : amount1Owed; - uint256 balance1Before = IERC20(TOKEN_1).balanceOf( - address(msg.sender) - ); - IERC20(TOKEN_1).transfer(msg.sender, amount1ToSend); - uint256 balance1After = IERC20(TOKEN_1).balanceOf( - address(msg.sender) - ); - // solhint-disable-next-line gas-custom-errors - require(balance1After > balance1Before, "Transfer failed"); - } - } -} - -/** - * @title Algebra tests - * @notice Tests specific to Algebra pool type - */ -contract LiFiDexAggregatorAlgebraTest is LiFiDexAggregatorTest { - address private constant APE_ETH_TOKEN = - 0xcF800F4948D16F23333508191B1B1591daF70438; - address private constant WETH_TOKEN = - 0xf4D9235269a96aaDaFc9aDAe454a0618eBE37949; - address private constant ALGEBRA_FACTORY_APECHAIN = - 0x10aA510d94E094Bd643677bd2964c3EE085Daffc; - address private constant ALGEBRA_QUOTER_V2_APECHAIN = - 0x60A186019F81bFD04aFc16c9C01804a04E79e68B; - address private constant ALGEBRA_POOL_APECHAIN = - 0x217076aa74eFF7D54837D00296e9AEBc8c06d4F2; - address private constant APE_ETH_HOLDER_APECHAIN = - address(0x1EA5Df273F1b2e0b10554C8F6f7Cc7Ef34F6a51b); - - address private constant IMPOSSIBLE_POOL_ADDRESS = - 0x0000000000000000000000000000000000000001; - - struct AlgebraSwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; - SwapDirection direction; - bool supportsFeeOnTransfer; - } - - error AlgebraSwapUnexpected(); - - function setUp() public override { - setupApechain(); - } - - // Override the abstract test with Algebra implementation - function test_CanSwap_FromDexAggregator() public override { - // Fund LDA from whale address - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(address(liFiDEXAggregator), 1 * 1e18); - - vm.startPrank(USER_SENDER); - - _testAlgebraSwap( - AlgebraSwapTestParams({ - from: address(liFiDEXAggregator), - to: address(USER_SENDER), - tokenIn: APE_ETH_TOKEN, - amountIn: IERC20(APE_ETH_TOKEN).balanceOf( - address(liFiDEXAggregator) - ) - 1, - tokenOut: address(WETH_TOKEN), - direction: SwapDirection.Token0ToToken1, - supportsFeeOnTransfer: true - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_FeeOnTransferToken() public { - setupApechain(); - - uint256 amountIn = 534451326669177; - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(APE_ETH_HOLDER_APECHAIN, amountIn); - - vm.startPrank(APE_ETH_HOLDER_APECHAIN); - - IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), amountIn); - - // Build route for algebra swap with command code 2 (user funds) - bytes memory route = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: APE_ETH_HOLDER_APECHAIN, - pool: ALGEBRA_POOL_APECHAIN, - supportsFeeOnTransfer: true - }) - ); - - // Track initial balance - uint256 beforeBalance = IERC20(WETH_TOKEN).balanceOf( - APE_ETH_HOLDER_APECHAIN - ); - - // Execute the swap - liFiDEXAggregator.processRoute( - APE_ETH_TOKEN, - amountIn, - WETH_TOKEN, - 0, // minOut = 0 for this test - APE_ETH_HOLDER_APECHAIN, - route - ); - - // Verify balances - uint256 afterBalance = IERC20(WETH_TOKEN).balanceOf( - APE_ETH_HOLDER_APECHAIN - ); - assertGt(afterBalance - beforeBalance, 0, "Should receive some WETH"); - - vm.stopPrank(); - } - - function test_CanSwap() public override { - vm.startPrank(APE_ETH_HOLDER_APECHAIN); - - // Transfer tokens from whale to USER_SENDER - uint256 amountToTransfer = 100 * 1e18; - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, amountToTransfer); - - vm.stopPrank(); - - vm.startPrank(USER_SENDER); - - _testAlgebraSwap( - AlgebraSwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: APE_ETH_TOKEN, - amountIn: 10 * 1e18, - tokenOut: address(WETH_TOKEN), - direction: SwapDirection.Token0ToToken1, - supportsFeeOnTransfer: true - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_Reverse() public { - test_CanSwap(); - - vm.startPrank(USER_SENDER); - - _testAlgebraSwap( - AlgebraSwapTestParams({ - from: USER_SENDER, - to: USER_SENDER, - tokenIn: address(WETH_TOKEN), - amountIn: 5 * 1e18, - tokenOut: APE_ETH_TOKEN, - direction: SwapDirection.Token1ToToken0, - supportsFeeOnTransfer: false - }) - ); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop_WithFeeOnTransferToken() public { - MultiHopTestState memory state; - state.isFeeOnTransfer = true; - - // Setup tokens and pools - state = _setupTokensAndPools(state); - - // Execute and verify swap - _executeAndVerifyMultiHopSwap(state); - } - - function test_CanSwap_MultiHop() public override { - MultiHopTestState memory state; - state.isFeeOnTransfer = false; - - // Setup tokens and pools - state = _setupTokensAndPools(state); - - // Execute and verify swap - _executeAndVerifyMultiHopSwap(state); - } - - // Test that the proper error is thrown when algebra swap fails - function testRevert_SwapUnexpected() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Create invalid pool address - address invalidPool = address(0x999); - - // Mock token0() call on invalid pool - vm.mockCall( - invalidPool, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) - ); - - // Create a route with an invalid pool - bytes memory invalidRoute = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: USER_SENDER, - pool: invalidPool, - supportsFeeOnTransfer: true - }) - ); - - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - - // Mock the algebra pool to not reset lastCalledPool - vm.mockCall( - invalidPool, - abi.encodeWithSelector( - IAlgebraPool.swapSupportingFeeOnInputTokens.selector - ), - abi.encode(0, 0) - ); - - // Expect the AlgebraSwapUnexpected error - vm.expectRevert(AlgebraSwapUnexpected.selector); - - liFiDEXAggregator.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - invalidRoute - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - // Helper function to setup tokens and pools - function _setupTokensAndPools( - MultiHopTestState memory state - ) private returns (MultiHopTestState memory) { - // Create tokens - ERC20 tokenA = new ERC20( - "Token A", - state.isFeeOnTransfer ? "FTA" : "TA", - 18 - ); - IERC20 tokenB; - ERC20 tokenC = new ERC20( - "Token C", - state.isFeeOnTransfer ? "FTC" : "TC", - 18 - ); - - if (state.isFeeOnTransfer) { - tokenB = IERC20( - address( - new MockFeeOnTransferToken("Fee Token B", "FTB", 18, 300) - ) - ); - } else { - tokenB = IERC20(address(new ERC20("Token B", "TB", 18))); - } - - state.tokenA = IERC20(address(tokenA)); - state.tokenB = tokenB; - state.tokenC = IERC20(address(tokenC)); - - // Label addresses - vm.label(address(state.tokenA), "Token A"); - vm.label(address(state.tokenB), "Token B"); - vm.label(address(state.tokenC), "Token C"); - - // Mint initial token supplies - tokenA.mint(address(this), 1_000_000 * 1e18); - if (!state.isFeeOnTransfer) { - ERC20(address(tokenB)).mint(address(this), 1_000_000 * 1e18); - } else { - MockFeeOnTransferToken(address(tokenB)).mint( - address(this), - 1_000_000 * 1e18 - ); - } - tokenC.mint(address(this), 1_000_000 * 1e18); - - // Create pools - state.pool1 = _createAlgebraPool( - address(state.tokenA), - address(state.tokenB) - ); - state.pool2 = _createAlgebraPool( - address(state.tokenB), - address(state.tokenC) - ); - - vm.label(state.pool1, "Pool 1"); - vm.label(state.pool2, "Pool 2"); - - // Add liquidity - _addLiquidityToPool( - state.pool1, - address(state.tokenA), - address(state.tokenB) - ); - _addLiquidityToPool( - state.pool2, - address(state.tokenB), - address(state.tokenC) - ); - - state.amountToTransfer = 100 * 1e18; - state.amountIn = 50 * 1e18; - - // Transfer tokens to USER_SENDER - IERC20(address(state.tokenA)).transfer( - USER_SENDER, - state.amountToTransfer - ); - - return state; - } - - // Helper function to execute and verify the swap - function _executeAndVerifyMultiHopSwap( - MultiHopTestState memory state - ) private { - vm.startPrank(USER_SENDER); - - uint256 initialBalanceA = IERC20(address(state.tokenA)).balanceOf( - USER_SENDER - ); - uint256 initialBalanceC = IERC20(address(state.tokenC)).balanceOf( - USER_SENDER - ); - - // Approve spending - IERC20(address(state.tokenA)).approve( - address(liFiDEXAggregator), - state.amountIn - ); - - // Build route - bytes memory route = _buildMultiHopRouteForTest(state); - - // Execute swap - liFiDEXAggregator.processRoute( - address(state.tokenA), - state.amountIn, - address(state.tokenC), - 0, // No minimum amount out for testing - USER_SENDER, - route - ); - - // Verify results - _verifyMultiHopResults(state, initialBalanceA, initialBalanceC); - - vm.stopPrank(); - } - - // Helper function to build the multi-hop route for test - function _buildMultiHopRouteForTest( - MultiHopTestState memory state - ) private view returns (bytes memory) { - bytes memory firstHop = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: address(state.tokenA), - recipient: address(liFiDEXAggregator), - pool: state.pool1, - supportsFeeOnTransfer: false - }) - ); - - bytes memory secondHop = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: CommandType.ProcessMyERC20, - tokenIn: address(state.tokenB), - recipient: USER_SENDER, - pool: state.pool2, - supportsFeeOnTransfer: state.isFeeOnTransfer - }) - ); - - return bytes.concat(firstHop, secondHop); - } - - // Helper function to verify multi-hop results - function _verifyMultiHopResults( - MultiHopTestState memory state, - uint256 initialBalanceA, - uint256 initialBalanceC - ) private { - uint256 finalBalanceA = IERC20(address(state.tokenA)).balanceOf( - USER_SENDER - ); - uint256 finalBalanceC = IERC20(address(state.tokenC)).balanceOf( - USER_SENDER - ); - - assertApproxEqAbs( - initialBalanceA - finalBalanceA, - state.amountIn, - 1, // 1 wei tolerance - "TokenA spent amount mismatch" - ); - assertGt(finalBalanceC, initialBalanceC, "TokenC not received"); - - emit log_named_uint( - state.isFeeOnTransfer - ? "Output amount with fee tokens" - : "Output amount with regular tokens", - finalBalanceC - initialBalanceC - ); - } - - // Helper function to create an Algebra pool - function _createAlgebraPool( - address tokenA, - address tokenB - ) internal returns (address pool) { - // Call the actual Algebra factory to create a pool - pool = IAlgebraFactory(ALGEBRA_FACTORY_APECHAIN).createPool( - tokenA, - tokenB - ); - return pool; - } - - // Helper function to add liquidity to a pool - function _addLiquidityToPool( - address pool, - address token0, - address token1 - ) internal { - // For fee-on-transfer tokens, we need to send more to account for the fee - // We'll use a small amount and send extra to cover fees - uint256 initialAmount0 = 1e17; // 0.1 token - uint256 initialAmount1 = 1e17; // 0.1 token - - // Send extra for fee-on-transfer tokens (10% extra should be enough for our test tokens with 5% fee) - uint256 transferAmount0 = (initialAmount0 * 110) / 100; - uint256 transferAmount1 = (initialAmount1 * 110) / 100; - - // Initialize with 1:1 price ratio (Q64.96 format) - uint160 initialPrice = uint160(1 << 96); - IAlgebraPool(pool).initialize(initialPrice); - - // Create AlgebraLiquidityAdderHelper with safe transfer logic - AlgebraLiquidityAdderHelper algebraLiquidityAdderHelper = new AlgebraLiquidityAdderHelper( - token0, - token1 - ); - - // Transfer tokens with extra amounts to account for fees - IERC20(token0).transfer( - address(algebraLiquidityAdderHelper), - transferAmount0 - ); - IERC20(token1).transfer( - address(algebraLiquidityAdderHelper), - transferAmount1 - ); - - // Get actual balances to use for liquidity, accounting for any fees - uint256 actualBalance0 = IERC20(token0).balanceOf( - address(algebraLiquidityAdderHelper) - ); - uint256 actualBalance1 = IERC20(token1).balanceOf( - address(algebraLiquidityAdderHelper) - ); - - // Use the smaller of the two balances for liquidity amount - uint128 liquidityAmount = uint128( - actualBalance0 < actualBalance1 ? actualBalance0 : actualBalance1 - ); - - // Add liquidity using the actual token amounts we have - algebraLiquidityAdderHelper.addLiquidity( - pool, - -887220, - 887220, - liquidityAmount / 2 // Use half of available liquidity to ensure success - ); - } - - struct MultiHopTestState { - IERC20 tokenA; - IERC20 tokenB; // Can be either regular ERC20 or MockFeeOnTransferToken - IERC20 tokenC; - address pool1; - address pool2; - uint256 amountIn; - uint256 amountToTransfer; - bool isFeeOnTransfer; - } - - struct AlgebraRouteParams { - CommandType commandCode; // 1 for contract funds, 2 for user funds - address tokenIn; // Input token address - address recipient; // Address receiving the output tokens - address pool; // Algebra pool address - bool supportsFeeOnTransfer; // Whether to support fee-on-transfer tokens - } - - // Helper function to build route for Apechain Algebra swap - function _buildAlgebraRoute( - AlgebraRouteParams memory params - ) internal view returns (bytes memory route) { - address token0 = IAlgebraPool(params.pool).token0(); - bool zeroForOne = (params.tokenIn == token0); - SwapDirection direction = zeroForOne - ? SwapDirection.Token0ToToken1 - : SwapDirection.Token1ToToken0; - - route = abi.encodePacked( - params.commandCode, - params.tokenIn, - uint8(1), // one pool - FULL_SHARE, // 100% share - uint8(PoolType.Algebra), - params.pool, - uint8(direction), - params.recipient, - params.supportsFeeOnTransfer ? uint8(1) : uint8(0) - ); - - return route; - } - - // Helper function to test an Algebra swap - function _testAlgebraSwap(AlgebraSwapTestParams memory params) internal { - // Find or create a pool - address pool = _getPool(params.tokenIn, params.tokenOut); - - vm.label(pool, "AlgebraPool"); - - // Get token0 from pool and label tokens accordingly - address token0 = IAlgebraPool(pool).token0(); - if (params.tokenIn == token0) { - vm.label( - params.tokenIn, - string.concat("token0 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( - params.tokenOut, - string.concat("token1 (", ERC20(params.tokenOut).symbol(), ")") - ); - } else { - vm.label( - params.tokenIn, - string.concat("token1 (", ERC20(params.tokenIn).symbol(), ")") - ); - vm.label( - params.tokenOut, - string.concat("token0 (", ERC20(params.tokenOut).symbol(), ")") - ); - } - - // Record initial balances - uint256 initialTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 initialTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - - // Get expected output from QuoterV2 - // NOTE: There may be a small discrepancy between the quoted amount and the actual output - // because the Quoter uses the regular swap() function for simulation while the actual - // execution may use swapSupportingFeeOnInputTokens() for fee-on-transfer tokens. - // The Quoter cannot accurately predict transfer fees taken by the token contract itself, - // resulting in minor "dust" differences that are normal and expected when dealing with - // non-standard token implementations. - uint256 expectedOutput = _getQuoteExactInput( - params.tokenIn, - params.tokenOut, - params.amountIn - ); - - // Build the route - CommandType commandCode = params.from == address(liFiDEXAggregator) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; - bytes memory route = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: commandCode, - tokenIn: params.tokenIn, - recipient: params.to, - pool: pool, - supportsFeeOnTransfer: params.supportsFeeOnTransfer - }) - ); - - // Approve tokens - IERC20(params.tokenIn).approve( - address(liFiDEXAggregator), - params.amountIn - ); - - // Execute the swap - address from = params.from == address(liFiDEXAggregator) - ? USER_SENDER - : params.from; - - vm.expectEmit(true, true, true, false); - emit Route( - from, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - expectedOutput, - expectedOutput - ); - - uint256 minOut = (expectedOutput * 995) / 1000; // 0.5% slippage - - liFiDEXAggregator.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - minOut, - params.to, - route - ); - - uint256 finalTokenIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalTokenOut = IERC20(params.tokenOut).balanceOf(params.to); - - assertApproxEqAbs( - initialTokenIn - finalTokenIn, - params.amountIn, - 1, // 1 wei tolerance - "TokenIn amount mismatch" - ); - assertGt(finalTokenOut, initialTokenOut, "TokenOut not received"); - } - - function _getPool( - address tokenA, - address tokenB - ) private view returns (address pool) { - pool = IAlgebraRouter(ALGEBRA_FACTORY_APECHAIN).poolByPair( - tokenA, - tokenB - ); - if (pool == address(0)) revert PoolDoesNotExist(); - return pool; - } - - function _getQuoteExactInput( - address tokenIn, - address tokenOut, - uint256 amountIn - ) private returns (uint256 amountOut) { - (amountOut, ) = IAlgebraQuoter(ALGEBRA_QUOTER_V2_APECHAIN) - .quoteExactInputSingle(tokenIn, tokenOut, amountIn, 0); - return amountOut; - } - - function testRevert_AlgebraSwap_ZeroAddressPool() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Mock token0() call on address(0) - vm.mockCall( - address(0), - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) - ); - - // Build route with address(0) as pool - bytes memory route = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: USER_SENDER, - pool: address(0), // Zero address pool - supportsFeeOnTransfer: true - }) - ); - - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - - liFiDEXAggregator.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - function testRevert_AlgebraSwap_ImpossiblePoolAddress() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Mock token0() call on IMPOSSIBLE_POOL_ADDRESS - vm.mockCall( - IMPOSSIBLE_POOL_ADDRESS, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) - ); - - // Build route with IMPOSSIBLE_POOL_ADDRESS as pool - bytes memory route = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: USER_SENDER, - pool: IMPOSSIBLE_POOL_ADDRESS, // Impossible pool address - supportsFeeOnTransfer: true - }) - ); - - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - - liFiDEXAggregator.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - function testRevert_AlgebraSwap_ZeroAddressRecipient() public { - // Transfer tokens from whale to user - vm.prank(APE_ETH_HOLDER_APECHAIN); - IERC20(APE_ETH_TOKEN).transfer(USER_SENDER, 1 * 1e18); - - vm.startPrank(USER_SENDER); - - // Mock token0() call on the pool - vm.mockCall( - ALGEBRA_POOL_APECHAIN, - abi.encodeWithSelector(IAlgebraPool.token0.selector), - abi.encode(APE_ETH_TOKEN) - ); - - // Build route with address(0) as recipient - bytes memory route = _buildAlgebraRoute( - AlgebraRouteParams({ - commandCode: CommandType.ProcessUserERC20, - tokenIn: APE_ETH_TOKEN, - recipient: address(0), // Zero address recipient - pool: ALGEBRA_POOL_APECHAIN, - supportsFeeOnTransfer: true - }) - ); - - // Approve tokens - IERC20(APE_ETH_TOKEN).approve(address(liFiDEXAggregator), 1 * 1e18); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - - liFiDEXAggregator.processRoute( - APE_ETH_TOKEN, - 1 * 1e18, - address(WETH_TOKEN), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } -} - -/** - * @title LiFiDexAggregatorIzumiV3Test - * @notice Tests specific to iZiSwap V3 pool type - */ -contract LiFiDexAggregatorIzumiV3Test is LiFiDexAggregatorTest { - // ==================== iZiSwap V3 specific variables ==================== - // Base constants - address internal constant USDC = - 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - address internal constant WETH = - 0x4200000000000000000000000000000000000006; - address internal constant USDB_C = - 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA; - - // iZiSwap pools - address internal constant IZUMI_WETH_USDC_POOL = - 0xb92A9A91a9F7E8e6Bb848508A6DaF08f9D718554; - address internal constant IZUMI_WETH_USDB_C_POOL = - 0xdb5D62f06EEcEf0Da7506e0700c2f03c57016De5; - - // Test parameters - uint256 internal constant AMOUNT_USDC = 100 * 1e6; // 100 USDC with 6 decimals - uint256 internal constant AMOUNT_WETH = 1 * 1e18; // 1 WETH with 18 decimals - - // structs - struct IzumiV3SwapTestParams { - address from; - address to; - address tokenIn; - uint256 amountIn; - address tokenOut; - SwapDirection direction; - } - - struct MultiHopTestParams { - address tokenIn; - address tokenMid; - address tokenOut; - address pool1; - address pool2; - uint256 amountIn; - SwapDirection direction1; - SwapDirection direction2; - } - - error IzumiV3SwapUnexpected(); - error IzumiV3SwapCallbackUnknownSource(); - error IzumiV3SwapCallbackNotPositiveAmount(); - - function setUp() public override { - super.setUp(); - - string memory baseRpc = vm.envString("ETH_NODE_URI_BASE"); - vm.createSelectFork(baseRpc, 29831758); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - - // Setup labels - vm.label(address(liFiDEXAggregator), "LiFiDEXAggregator"); - vm.label(USDC, "USDC"); - vm.label(WETH, "WETH"); - vm.label(USDB_C, "USDB-C"); - vm.label(IZUMI_WETH_USDC_POOL, "WETH-USDC Pool"); - vm.label(IZUMI_WETH_USDB_C_POOL, "WETH-USDB-C Pool"); - } - - function test_CanSwap_FromDexAggregator() public override { - // Test USDC -> WETH - deal(USDC, address(liFiDEXAggregator), AMOUNT_USDC); - - vm.startPrank(USER_SENDER); - _testSwap( - IzumiV3SwapTestParams({ - from: address(liFiDEXAggregator), - to: USER_SENDER, - tokenIn: USDC, - amountIn: AMOUNT_USDC, - tokenOut: WETH, - direction: SwapDirection.Token1ToToken0 - }) - ); - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - _testMultiHopSwap( - MultiHopTestParams({ - tokenIn: USDC, - tokenMid: WETH, - tokenOut: USDB_C, - pool1: IZUMI_WETH_USDC_POOL, - pool2: IZUMI_WETH_USDB_C_POOL, - amountIn: AMOUNT_USDC, - direction1: SwapDirection.Token1ToToken0, - direction2: SwapDirection.Token0ToToken1 - }) - ); - } - - function test_CanSwap() public override { - deal(address(USDC), USER_SENDER, AMOUNT_USDC); - - vm.startPrank(USER_SENDER); - IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - - // fix the swap data encoding - bytes memory swapData = _buildIzumiV3Route( - CommandType.ProcessUserERC20, - USDC, - uint8(SwapDirection.Token1ToToken0), - IZUMI_WETH_USDC_POOL, - USER_RECEIVER - ); - - vm.expectEmit(true, true, true, false); - emit Route(USER_SENDER, USER_RECEIVER, USDC, WETH, AMOUNT_USDC, 0, 0); - - liFiDEXAggregator.processRoute( - USDC, - AMOUNT_USDC, - WETH, - 0, - USER_RECEIVER, - swapData - ); - - vm.stopPrank(); - } - - function testRevert_IzumiV3SwapUnexpected() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); - - vm.startPrank(USER_SENDER); - - // create invalid pool address - address invalidPool = address(0x999); - - // create a route with an invalid pool - bytes memory invalidRoute = _buildIzumiV3Route( - CommandType.ProcessUserERC20, - USDC, - uint8(SwapDirection.Token1ToToken0), - invalidPool, - USER_SENDER - ); - - IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - - // mock the iZiSwap pool to return without updating lastCalledPool - vm.mockCall( - invalidPool, - abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), - abi.encode(0, 0) // return amountX and amountY without triggering callback or updating lastCalledPool - ); - - vm.expectRevert(IzumiV3SwapUnexpected.selector); - - liFiDEXAggregator.processRoute( - USDC, - AMOUNT_USDC, - WETH, - 0, - USER_SENDER, - invalidRoute - ); - - vm.stopPrank(); - vm.clearMockedCalls(); - } - - function testRevert_IzumiV3SwapCallbackUnknownSource() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); - - // create invalid pool address - address invalidPool = address(0x999); - - vm.prank(USER_SENDER); - IERC20(USDC).approve(address(liFiDEXAggregator), AMOUNT_USDC); - - // mock the pool to call the callback directly without setting lastCalledPool - vm.mockCall( - invalidPool, - abi.encodeWithSignature("swapY2X(address,uint128,int24,bytes)"), - abi.encode(0, 0) - ); - - // try to call the callback directly from the pool without setting lastCalledPool - vm.prank(invalidPool); - vm.expectRevert(IzumiV3SwapCallbackUnknownSource.selector); - liFiDEXAggregator.swapY2XCallback(0, AMOUNT_USDC, abi.encode(USDC)); - - vm.clearMockedCalls(); - } - - function testRevert_IzumiV3SwapCallbackNotPositiveAmount() public { - deal(USDC, USER_SENDER, AMOUNT_USDC); - - // set lastCalledPool to the pool address to pass the unknown source check - vm.store( - address(liFiDEXAggregator), - bytes32(uint256(3)), // slot for lastCalledPool - bytes32(uint256(uint160(IZUMI_WETH_USDC_POOL))) - ); - - // try to call the callback with zero amount - vm.prank(IZUMI_WETH_USDC_POOL); - vm.expectRevert(IzumiV3SwapCallbackNotPositiveAmount.selector); - liFiDEXAggregator.swapY2XCallback( - 0, - 0, // zero amount should trigger the error - abi.encode(USDC) - ); - } - - function testRevert_FailsIfAmountInIsTooLarge() public { - deal(address(WETH), USER_SENDER, type(uint256).max); - - vm.startPrank(USER_SENDER); - IERC20(WETH).approve(address(liFiDEXAggregator), type(uint256).max); - - // fix the swap data encoding - bytes memory swapData = _buildIzumiV3Route( - CommandType.ProcessUserERC20, - WETH, - uint8(SwapDirection.Token0ToToken1), - IZUMI_WETH_USDC_POOL, - USER_RECEIVER - ); - - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - WETH, - type(uint216).max, - USDC, - 0, - USER_RECEIVER, - swapData - ); - - vm.stopPrank(); - } - - function _testSwap(IzumiV3SwapTestParams memory params) internal { - // Fund the sender with tokens if not the contract itself - if (params.from != address(liFiDEXAggregator)) { - deal(params.tokenIn, params.from, params.amountIn); - } - - // Capture initial token balances - uint256 initialBalanceIn = IERC20(params.tokenIn).balanceOf( - params.from - ); - uint256 initialBalanceOut = IERC20(params.tokenOut).balanceOf( - params.to - ); - - // Build the route based on the command type - CommandType commandCode = params.from == address(liFiDEXAggregator) - ? CommandType.ProcessMyERC20 - : CommandType.ProcessUserERC20; - - // Construct the route - bytes memory route = _buildIzumiV3Route( - commandCode, - params.tokenIn, - uint8(params.direction == SwapDirection.Token0ToToken1 ? 1 : 0), - IZUMI_WETH_USDC_POOL, - params.to - ); - - // Approve tokens if necessary - if (params.from == USER_SENDER) { - vm.startPrank(USER_SENDER); - IERC20(params.tokenIn).approve( - address(liFiDEXAggregator), - params.amountIn - ); - } - - // Expect the Route event emission - address from = params.from == address(liFiDEXAggregator) - ? USER_SENDER - : params.from; - - vm.expectEmit(true, true, true, false); - emit Route( - from, - params.to, - params.tokenIn, - params.tokenOut, - params.amountIn, - 0, // No minimum amount enforced in test - 0 // Actual amount will be checked after the swap - ); - - // Execute the swap - uint256 amountOut = liFiDEXAggregator.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - 0, // No minimum amount for testing - params.to, - route - ); - - if (params.from == USER_SENDER) { - vm.stopPrank(); - } - - // Verify balances have changed correctly - uint256 finalBalanceIn = IERC20(params.tokenIn).balanceOf(params.from); - uint256 finalBalanceOut = IERC20(params.tokenOut).balanceOf(params.to); - - assertApproxEqAbs( - initialBalanceIn - finalBalanceIn, - params.amountIn, - 1, // 1 wei tolerance because of undrain protection for dex aggregator - "TokenIn amount mismatch" - ); - assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); - assertEq( - amountOut, - finalBalanceOut - initialBalanceOut, - "AmountOut mismatch" - ); - - emit log_named_uint("Amount In", params.amountIn); - emit log_named_uint("Amount Out", amountOut); - } - - function _testMultiHopSwap(MultiHopTestParams memory params) internal { - // Fund the sender with tokens - deal(params.tokenIn, USER_SENDER, params.amountIn); - - // Capture initial token balances - uint256 initialBalanceIn; - uint256 initialBalanceOut; - - initialBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); - initialBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - - // Build multi-hop route - bytes memory route = _buildIzumiV3MultiHopRoute(params); - - // Approve tokens - vm.startPrank(USER_SENDER); - IERC20(params.tokenIn).approve( - address(liFiDEXAggregator), - params.amountIn - ); - - // Execute the swap - uint256 amountOut = liFiDEXAggregator.processRoute( - params.tokenIn, - params.amountIn, - params.tokenOut, - 0, // No minimum amount for testing - USER_SENDER, - route - ); - vm.stopPrank(); - - // Verify balances have changed correctly - uint256 finalBalanceIn; - uint256 finalBalanceOut; - - finalBalanceIn = IERC20(params.tokenIn).balanceOf(USER_SENDER); - finalBalanceOut = IERC20(params.tokenOut).balanceOf(USER_SENDER); - - assertEq( - initialBalanceIn - finalBalanceIn, - params.amountIn, - "TokenIn amount mismatch" - ); - assertGt(finalBalanceOut, initialBalanceOut, "TokenOut not received"); - assertEq( - amountOut, - finalBalanceOut - initialBalanceOut, - "AmountOut mismatch" - ); - } - - function _buildIzumiV3Route( - CommandType commandCode, - address tokenIn, - uint8 direction, - address pool, - address recipient - ) internal pure returns (bytes memory) { - return - abi.encodePacked( - uint8(commandCode), - tokenIn, - uint8(1), // number of pools (1) - FULL_SHARE, // 100% share - uint8(PoolType.iZiSwap), // pool type - pool, - uint8(direction), - recipient - ); - } - - function _buildIzumiV3MultiHopRoute( - MultiHopTestParams memory params - ) internal view returns (bytes memory) { - // First hop: USER_ERC20 -> LDA - bytes memory firstHop = _buildIzumiV3Route( - CommandType.ProcessUserERC20, - params.tokenIn, - uint8(params.direction1), - params.pool1, - address(liFiDEXAggregator) - ); - - // Second hop: MY_ERC20 (LDA) -> pool2 - bytes memory secondHop = _buildIzumiV3Route( - CommandType.ProcessMyERC20, - params.tokenMid, - uint8(params.direction2), - params.pool2, - USER_SENDER // final recipient - ); - - // Combine the two hops - return bytes.concat(firstHop, secondHop); - } -} - -// ----------------------------------------------------------------------------- -// HyperswapV3 on HyperEVM -// ----------------------------------------------------------------------------- -contract LiFiDexAggregatorHyperswapV3Test is LiFiDexAggregatorTest { - using SafeERC20 for IERC20; - - /// @dev HyperswapV3 router on HyperEVM chain - IHyperswapV3Factory internal constant HYPERSWAP_FACTORY = - IHyperswapV3Factory(0xB1c0fa0B789320044A6F623cFe5eBda9562602E3); - /// @dev HyperswapV3 quoter on HyperEVM chain - IHyperswapV3QuoterV2 internal constant HYPERSWAP_QUOTER = - IHyperswapV3QuoterV2(0x03A918028f22D9E1473B7959C927AD7425A45C7C); - - /// @dev a liquid USDT on HyperEVM - IERC20 internal constant USDT0 = - IERC20(0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb); - /// @dev WHYPE on HyperEVM - IERC20 internal constant WHYPE = - IERC20(0x5555555555555555555555555555555555555555); - - struct HyperswapV3Params { - CommandType commandCode; // ProcessMyERC20 or ProcessUserERC20 - address tokenIn; // Input token address - address recipient; // Address receiving the output tokens - address pool; // HyperswapV3 pool address - bool zeroForOne; // Direction of the swap - } - - function setUp() public override { - setupHyperEVM(); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - - deal(address(USDT0), USER_SENDER, amountIn); - - // user approves - vm.prank(USER_SENDER); - USDT0.approve(address(liFiDEXAggregator), amountIn); - - // fetch the real pool and quote - address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), - 3000 - ); - - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn, - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params - ); - - // build the "off-chain" route - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDT0), - uint8(1), // 1 pool - uint16(65535), // FULL_SHARE - uint8(1), // POOL_TYPE_UNIV3 - pool, - uint8(0), // zeroForOne = true if USDT0 < WHYPE - address(USER_SENDER) - ); - - // expect the Route event - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn, - quoted, - quoted - ); - - // execute - vm.prank(USER_SENDER); - liFiDEXAggregator.processRoute( - address(USDT0), - amountIn, - address(WHYPE), - quoted, - USER_SENDER, - route - ); - } - - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e6; // 1000 USDT0 - - // Fund dex aggregator contract - deal(address(USDT0), address(liFiDEXAggregator), amountIn); - - // fetch the real pool and quote - address pool = HYPERSWAP_FACTORY.getPool( - address(USDT0), - address(WHYPE), - 3000 - ); - - // Create the params struct for quoting - IHyperswapV3QuoterV2.QuoteExactInputSingleParams - memory params = IHyperswapV3QuoterV2.QuoteExactInputSingleParams({ - tokenIn: address(USDT0), - tokenOut: address(WHYPE), - amountIn: amountIn - 1, // Subtract 1 to match slot undrain protection - fee: 3000, - sqrtPriceLimitX96: 0 - }); - - // Get the quote using the struct - (uint256 quoted, , , ) = HYPERSWAP_QUOTER.quoteExactInputSingle( - params - ); - - // Build route using our helper function - bytes memory route = _buildHyperswapV3Route( - HyperswapV3Params({ - commandCode: CommandType.ProcessMyERC20, - tokenIn: address(USDT0), - recipient: USER_SENDER, - pool: pool, - zeroForOne: true // USDT0 < WHYPE - }) - ); - - // expect the Route event - vm.expectEmit(true, true, true, true); - emit Route( - USER_SENDER, - USER_SENDER, - address(USDT0), - address(WHYPE), - amountIn - 1, // Account for slot undrain protection - quoted, - quoted - ); - - // execute - vm.prank(USER_SENDER); - liFiDEXAggregator.processRoute( - address(USDT0), - amountIn - 1, // Account for slot undrain protection - address(WHYPE), - quoted, - USER_SENDER, - route - ); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: HyperswapV3 multi-hop unsupported due to AS requirement. - // HyperswapV3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. HyperswapV3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } - - function _buildHyperswapV3Route( - HyperswapV3Params memory params - ) internal pure returns (bytes memory route) { - route = abi.encodePacked( - uint8(params.commandCode), - params.tokenIn, - uint8(1), // 1 pool - FULL_SHARE, // 65535 - 100% share - uint8(PoolType.UniV3), // POOL_TYPE_UNIV3 = 1 - params.pool, - uint8(params.zeroForOne ? 0 : 1), // Convert bool to uint8: 0 for true, 1 for false - params.recipient - ); - - return route; - } -} - -// ----------------------------------------------------------------------------- -// LaminarV3 on HyperEVM -// ----------------------------------------------------------------------------- -contract LiFiDexAggregatorLaminarV3Test is LiFiDexAggregatorTest { - using SafeERC20 for IERC20; - - IERC20 internal constant WHYPE = - IERC20(0x5555555555555555555555555555555555555555); - IERC20 internal constant LHYPE = - IERC20(0x5748ae796AE46A4F1348a1693de4b50560485562); - - address internal constant WHYPE_LHYPE_POOL = - 0xdAA8a66380fb35b35CB7bc1dBC1925AbfdD0ae45; - - function setUp() public override { - setupHyperEVM(); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e18; - - // Fund the user with WHYPE - deal(address(WHYPE), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WHYPE.approve(address(liFiDEXAggregator), amountIn); - - // Build a single-pool UniV3 route - bool zeroForOne = address(WHYPE) > address(LHYPE); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(WHYPE), - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.UniV3), - WHYPE_LHYPE_POOL, - uint8(zeroForOne ? 0 : 1), - address(USER_SENDER) - ); - - // Record balances - uint256 inBefore = WHYPE.balanceOf(USER_SENDER); - uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - - // Execute swap (minOut = 0 for test) - liFiDEXAggregator.processRoute( - address(WHYPE), - amountIn, - address(LHYPE), - 0, - USER_SENDER, - route - ); - - // Verify - uint256 inAfter = WHYPE.balanceOf(USER_SENDER); - uint256 outAfter = LHYPE.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "WHYPE spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the aggregator directly - deal(address(WHYPE), address(liFiDEXAggregator), amountIn); - - vm.startPrank(USER_SENDER); - - bool zeroForOne = address(WHYPE) > address(LHYPE); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(WHYPE), - uint8(1), - FULL_SHARE, - uint8(PoolType.UniV3), - WHYPE_LHYPE_POOL, - uint8(zeroForOne ? 0 : 1), - address(USER_SENDER) - ); - - uint256 outBefore = LHYPE.balanceOf(USER_SENDER); - - // Withdraw 1 wei to avoid slot-undrain protection - liFiDEXAggregator.processRoute( - address(WHYPE), - amountIn - 1, - address(LHYPE), - 0, - USER_SENDER, - route - ); - - uint256 outAfter = LHYPE.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive LHYPE"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: Laminar V3 multi-hop unsupported due to AS requirement. - // Laminar V3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. Laminar V3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } -} - -contract LiFiDexAggregatorXSwapV3Test is LiFiDexAggregatorTest { - using SafeERC20 for IERC20; - - address internal constant USDC_E_WXDC_POOL = - 0x81B4afF811E94fb084A0d3B3ca456D09AeC14EB0; - - /// @dev our two tokens: USDC.e and wrapped XDC - IERC20 internal constant USDC_E = - IERC20(0x2A8E898b6242355c290E1f4Fc966b8788729A4D4); - IERC20 internal constant WXDC = - IERC20(0x951857744785E80e2De051c32EE7b25f9c458C42); - - function setUp() public override { - customRpcUrlForForking = "ETH_NODE_URI_XDC"; - customBlockNumberForForking = 89279495; - fork(); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e6; - deal(address(USDC_E), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - USDC_E.approve(address(liFiDEXAggregator), amountIn); - - // Build a one-pool V3 route - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(USDC_E), - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.UniV3), - USDC_E_WXDC_POOL, - uint8(1), // zeroForOne (USDC.e > WXDC) - USER_SENDER - ); - - // Record balances before swap - uint256 inBefore = USDC_E.balanceOf(USER_SENDER); - uint256 outBefore = WXDC.balanceOf(USER_SENDER); - - // Execute swap (minOut = 0 for test) - liFiDEXAggregator.processRoute( - address(USDC_E), - amountIn, - address(WXDC), - 0, - USER_SENDER, - route - ); - - // Verify balances after swap - uint256 inAfter = USDC_E.balanceOf(USER_SENDER); - uint256 outAfter = WXDC.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "USDC.e spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - - vm.stopPrank(); - } - - /// @notice single-pool swap: aggregator contract sends USDC.e → user receives WXDC - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 5_000 * 1e6; - - // fund the aggregator - deal(address(USDC_E), address(liFiDEXAggregator), amountIn); - - vm.startPrank(USER_SENDER); - - // Account for slot-undrain protection - uint256 swapAmount = amountIn - 1; - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(USDC_E), - uint8(1), - FULL_SHARE, - uint8(PoolType.UniV3), - USDC_E_WXDC_POOL, - uint8(1), // zeroForOne (USDC.e > WXDC) - USER_SENDER - ); - - // Record balances before swap - uint256 outBefore = WXDC.balanceOf(USER_SENDER); - - liFiDEXAggregator.processRoute( - address(USDC_E), - swapAmount, - address(WXDC), - 0, - USER_SENDER, - route - ); - - // Verify balances after swap - uint256 outAfter = WXDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive WXDC"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: XSwap V3 multi-hop unsupported due to AS requirement. - // XSwap V3 does not support a "one-pool" second hop today, because - // the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. XSwap V3's swap() immediately reverts on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } -} - -// ----------------------------------------------------------------------------- -// RabbitSwap on Viction -// ----------------------------------------------------------------------------- -contract LiFiDexAggregatorRabbitSwapTest is LiFiDexAggregatorTest { - using SafeERC20 for IERC20; - - // Constants for RabbitSwap on Viction - IERC20 internal constant SOROS = - IERC20(0xB786D9c8120D311b948cF1e5Aa48D8fBacf477E2); - IERC20 internal constant C98 = - IERC20(0x0Fd0288AAAE91eaF935e2eC14b23486f86516c8C); - address internal constant SOROS_C98_POOL = - 0xF10eFaE2DdAC396c4ef3c52009dB429A120d0C0D; - - function setUp() public override { - // setup for Viction network - customRpcUrlForForking = "ETH_NODE_URI_VICTION"; - customBlockNumberForForking = 94490946; - fork(); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - function test_CanSwap() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the user with SOROS - deal(address(SOROS), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - SOROS.approve(address(liFiDEXAggregator), amountIn); - - // build a single-pool UniV3-style route - bool zeroForOne = address(SOROS) > address(C98); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.UniV3), // RabbitSwap uses UniV3 pool type - SOROS_C98_POOL, - uint8(zeroForOne ? 0 : 1), - address(USER_SENDER) - ); - - // record balances before swap - uint256 inBefore = SOROS.balanceOf(USER_SENDER); - uint256 outBefore = C98.balanceOf(USER_SENDER); - - // execute swap (minOut = 0 for test) - liFiDEXAggregator.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route - ); - - // verify balances after swap - uint256 inAfter = SOROS.balanceOf(USER_SENDER); - uint256 outAfter = C98.balanceOf(USER_SENDER); - assertEq(inBefore - inAfter, amountIn, "SOROS spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive C98"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - uint256 amountIn = 1_000 * 1e18; - - // fund the aggregator directly - deal(address(SOROS), address(liFiDEXAggregator), amountIn); - - vm.startPrank(USER_SENDER); - - bool zeroForOne = address(SOROS) > address(C98); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint8(PoolType.UniV3), - SOROS_C98_POOL, - uint8(zeroForOne ? 0 : 1), - address(USER_SENDER) - ); - - uint256 outBefore = C98.balanceOf(USER_SENDER); - - // withdraw 1 wei less to avoid slot-undrain protection - liFiDEXAggregator.processRoute( - address(SOROS), - amountIn - 1, - address(C98), - 0, - USER_SENDER, - route - ); - - uint256 outAfter = C98.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive C98"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: RabbitSwap multi-hop unsupported due to AS requirement. - // RabbitSwap (being a UniV3 fork) does not support a "one-pool" second hop today, - // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. UniV3-style pools immediately revert on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } - - function testRevert_RabbitSwapInvalidPool() public { - uint256 amountIn = 1_000 * 1e18; - deal(address(SOROS), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - SOROS.approve(address(liFiDEXAggregator), amountIn); - - // build route with invalid pool address - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint8(PoolType.UniV3), - address(0), // invalid pool address - uint8(0), - USER_SENDER - ); - - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - } - - function testRevert_RabbitSwapInvalidRecipient() public { - uint256 amountIn = 1_000 * 1e18; - deal(address(SOROS), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - SOROS.approve(address(liFiDEXAggregator), amountIn); - - // build route with invalid recipient - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(SOROS), - uint8(1), - FULL_SHARE, - uint8(PoolType.UniV3), - SOROS_C98_POOL, - uint8(0), - address(0) // invalid recipient - ); - - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - address(SOROS), - amountIn, - address(C98), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - } -} - -// ---------------------------------------------- -// EnosysDexV3 on Flare -// ---------------------------------------------- -contract LiFiDexAggregatorEnosysDexV3Test is LiFiDexAggregatorTest { - using SafeERC20 for IERC20; - - /// @dev HLN token on Flare - IERC20 internal constant HLN = - IERC20(0x140D8d3649Ec605CF69018C627fB44cCC76eC89f); - - /// @dev USDT0 token on Flare - IERC20 internal constant USDT0 = - IERC20(0xe7cd86e13AC4309349F30B3435a9d337750fC82D); - - /// @dev The single EnosysDexV3 pool for HLN–USDT0 - address internal constant ENOSYS_V3_POOL = - 0xA7C9E7343bD8f1eb7000F25dE5aeb52c6B78B1b7; - - /// @notice Set up a fork of Flare at block 42652369 and initialize the aggregator - function setUp() public override { - customRpcUrlForForking = "ETH_NODE_URI_FLARE"; - customBlockNumberForForking = 42652369; - fork(); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - /// @notice Single‐pool swap: USER sends HLN → receives USDT0 - function test_CanSwap() public override { - // Mint 1 000 HLN to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(HLN), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - HLN.approve(address(liFiDEXAggregator), amountIn); - - bool zeroForOne = address(HLN) > address(USDT0); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(HLN), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.UniV3), // V3‐style pool - ENOSYS_V3_POOL, // pool address - uint8(zeroForOne ? 0 : 1), // 0 = token0→token1, 1 = token1→token0 - address(USER_SENDER) // recipient - ); - - // Record balances before swap - uint256 inBefore = HLN.balanceOf(USER_SENDER); - uint256 outBefore = USDT0.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - liFiDEXAggregator.processRoute( - address(HLN), - amountIn, - address(USDT0), - 0, - USER_SENDER, - route - ); - - // Verify that HLN was spent and some USDT0 was received - uint256 inAfter = HLN.balanceOf(USER_SENDER); - uint256 outAfter = USDT0.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "HLN spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - - vm.stopPrank(); - } - - /// @notice Single‐pool swap: aggregator holds HLN → user receives USDT0 - function test_CanSwap_FromDexAggregator() public override { - // Fund the aggregator with 1 000 HLN - uint256 amountIn = 1_000 * 1e18; - deal(address(HLN), address(liFiDEXAggregator), amountIn); - - vm.startPrank(USER_SENDER); - bool zeroForOne = address(HLN) > address(USDT0); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(HLN), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.UniV3), // V3‐style pool - ENOSYS_V3_POOL, // pool address - uint8(zeroForOne ? 0 : 1), // 0 = token0→token1 - address(USER_SENDER) // recipient - ); - - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDT0.balanceOf(USER_SENDER); - - liFiDEXAggregator.processRoute( - address(HLN), - swapAmount, - address(USDT0), - 0, - USER_SENDER, - route - ); - - // Verify that some USDT0 was received - uint256 outAfter = USDT0.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDT0"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - // SKIPPED: EnosysDexV3 multi-hop unsupported due to AS requirement. - // EnosysDexV3 (being a UniV3 fork) does not support a "one-pool" second hop today, - // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into - // the pool.swap call. UniV3-style pools immediately revert on - // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools - // in a single processRoute invocation. - } -} - -// ---------------------------------------------- -// SyncSwapV2 on Linea -// ---------------------------------------------- -contract LiFiDexAggregatorSyncSwapV2Test is LiFiDexAggregatorTest { - using SafeERC20 for IERC20; - - IERC20 internal constant USDC = - IERC20(0x176211869cA2b568f2A7D4EE941E073a821EE1ff); - IERC20 internal constant WETH = - IERC20(0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f); - address internal constant USDC_WETH_POOL_V1 = - address(0x5Ec5b1E9b1Bd5198343ABB6E55Fb695d2F7Bb308); - address internal constant SYNC_SWAP_VAULT = - address(0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B); - - address internal constant USDC_WETH_POOL_V2 = - address(0xDDed227D71A096c6B5D87807C1B5C456771aAA94); - - IERC20 internal constant USDT = - IERC20(0xA219439258ca9da29E9Cc4cE5596924745e12B93); - address internal constant USDC_USDT_POOL_V1 = - address(0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2); - - /// @notice Set up a fork of Linea at block 20077881 and initialize the aggregator - function setUp() public override { - customRpcUrlForForking = "ETH_NODE_URI_LINEA"; - customBlockNumberForForking = 20077881; - fork(); - - _initializeDexAggregator(USER_DIAMOND_OWNER); - } - - /// @notice Single‐pool swap: USER sends WETH → receives USDC - function test_CanSwap() public override { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(liFiDEXAggregator), amountIn); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - USDC_WETH_POOL_V1, // pool address - address(USER_SENDER), // recipient - uint8(2), // withdrawMode - uint8(1), // isV1Pool - address(SYNC_SWAP_VAULT) // vault - ); - - // Record balances before swap - uint256 inBefore = WETH.balanceOf(USER_SENDER); - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - liFiDEXAggregator.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that WETH was spent and some USDC_C was received - uint256 inAfter = WETH.balanceOf(USER_SENDER); - uint256 outAfter = USDC.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_PoolV2() public { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(liFiDEXAggregator), amountIn); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - USDC_WETH_POOL_V2, // pool address - address(USER_SENDER), // recipient - uint8(2), // withdrawMode - uint8(0) // isV1Pool - ); - - // Record balances before swap - uint256 inBefore = WETH.balanceOf(USER_SENDER); - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - // Execute the swap (minOut = 0 for test) - liFiDEXAggregator.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that WETH was spent and some USDC_C was received - uint256 inAfter = WETH.balanceOf(USER_SENDER); - uint256 outAfter = USDC.balanceOf(USER_SENDER); - - assertEq(inBefore - inAfter, amountIn, "WETH spent mismatch"); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator() public override { - // Fund the aggregator with 1 000 WETH - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), address(liFiDEXAggregator), amountIn); - - vm.startPrank(USER_SENDER); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - USDC_WETH_POOL_V1, // pool address - address(USER_SENDER), // recipient - uint8(2), // withdrawMode - uint8(1), // isV1Pool - address(SYNC_SWAP_VAULT) // vault - ); - - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - liFiDEXAggregator.processRoute( - address(WETH), - swapAmount, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that some USDC was received - uint256 outAfter = USDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_FromDexAggregator_PoolV2() public { - // Fund the aggregator with 1 000 WETH - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), address(liFiDEXAggregator), amountIn); - - vm.startPrank(USER_SENDER); - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessMyERC20), // aggregator's funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - USDC_WETH_POOL_V2, // pool address - address(USER_SENDER), // recipient - uint8(2) // withdrawMode - ); - - // Subtract 1 to protect against slot‐undrain - uint256 swapAmount = amountIn - 1; - uint256 outBefore = USDC.balanceOf(USER_SENDER); - - liFiDEXAggregator.processRoute( - address(WETH), - swapAmount, - address(USDC), - 0, - USER_SENDER, - route - ); - - // Verify that some USDC was received - uint256 outAfter = USDC.balanceOf(USER_SENDER); - assertGt(outAfter - outBefore, 0, "Should receive USDC"); - - vm.stopPrank(); - } - - function test_CanSwap_MultiHop() public override { - uint256 amountIn = 1_000e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(liFiDEXAggregator), amountIn); - - uint256 initialBalanceIn = WETH.balanceOf(USER_SENDER); - uint256 initialBalanceOut = USDT.balanceOf(USER_SENDER); - - // - // 1) PROCESS_USER_ERC20: WETH → USDC (SyncSwap V1 → withdrawMode=2 → vault that still holds USDC) - // - bytes memory hop1 = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), - address(WETH), - uint8(1), // one pool - FULL_SHARE, // 100% of the WETH - uint8(PoolType.SyncSwapV2), - USDC_WETH_POOL_V1, // the V1 pool - SYNC_SWAP_VAULT, // “to” = the vault address - uint8(2), // withdrawMode = 2 - uint8(1), // isV1Pool = true - address(SYNC_SWAP_VAULT) // vault - ); - - // - // 2) PROCESS_ONE_POOL: now swap that USDC → USDT via SyncSwap pool V1 - // - bytes memory hop2 = abi.encodePacked( - uint8(CommandType.ProcessOnePool), - address(USDC), - uint8(PoolType.SyncSwapV2), - USDC_USDT_POOL_V1, // V1 USDC⟶USDT pool - address(USER_SENDER), // send the USDT home - uint8(2), // withdrawMode = 2 - uint8(1), // isV1Pool = true - SYNC_SWAP_VAULT // vault - ); - - bytes memory route = bytes.concat(hop1, hop2); - - uint256 amountOut = liFiDEXAggregator.processRoute( - address(WETH), - amountIn, - address(USDT), - 0, - USER_SENDER, - route - ); - - uint256 afterBalanceIn = WETH.balanceOf(USER_SENDER); - uint256 afterBalanceOut = USDT.balanceOf(USER_SENDER); - - assertEq( - initialBalanceIn - afterBalanceIn, - amountIn, - "WETH spent mismatch" - ); - assertEq( - amountOut, - afterBalanceOut - initialBalanceOut, - "USDT amountOut mismatch" - ); - vm.stopPrank(); - } - - function testRevert_V1PoolMissingVaultAddress() public { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(liFiDEXAggregator), amountIn); - - bytes memory route = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - USDC_WETH_POOL_V1, // pool address - address(USER_SENDER), // recipient - uint8(2), // withdrawMode - uint8(1), // isV1Pool - address(0) // vault (invalid address) - ); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - route - ); - - vm.stopPrank(); - } - - function testRevert_InvalidPoolOrRecipient() public { - // Transfer 1 000 WETH from whale to USER_SENDER - uint256 amountIn = 1_000 * 1e18; - deal(address(WETH), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - WETH.approve(address(liFiDEXAggregator), amountIn); - - bytes memory routeWithInvalidPool = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - address(0), // pool address (invalid address) - address(USER_SENDER), // recipient - uint8(2), // withdrawMode - uint8(1), // isV1Pool - address(SYNC_SWAP_VAULT) // vault - ); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidPool - ); - - bytes memory routeWithInvalidRecipient = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - USDC_WETH_POOL_V1, // pool address - address(0), // recipient (invalid address) - uint8(2), // withdrawMode - uint8(1), // isV1Pool - address(SYNC_SWAP_VAULT) // vault - ); - - // Expect revert with InvalidCallData - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - address(WETH), - amountIn, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidRecipient - ); - - vm.stopPrank(); - } - - function testRevert_InvalidWithdrawMode() public { - vm.startPrank(USER_SENDER); - - bytes memory routeWithInvalidWithdrawMode = abi.encodePacked( - uint8(CommandType.ProcessUserERC20), // user funds - address(WETH), // tokenIn - uint8(1), // one pool - FULL_SHARE, // 100% - uint8(PoolType.SyncSwapV2), // SyncSwapV2 - USDC_WETH_POOL_V1, // pool address (invalid address) - address(USER_SENDER), // recipient - uint8(3), // withdrawMode (invalid) - uint8(1), // isV1Pool - address(SYNC_SWAP_VAULT) // vault - ); - - // Expect revert with InvalidCallData because withdrawMode is invalid - vm.expectRevert(InvalidCallData.selector); - liFiDEXAggregator.processRoute( - address(WETH), - 1, - address(USDC), - 0, - USER_SENDER, - routeWithInvalidWithdrawMode - ); - - vm.stopPrank(); - } -} diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol index 031710864..6d87862a3 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapper.t.sol @@ -44,7 +44,7 @@ contract LidoWrapperTest is TestBase, LiFiData { error ContractNotYetReadyForMainnet(); - function setUp() public { + function setUp() public override { vm.label(ST_ETH_ADDRESS_OPTIMISM, "stETH"); vm.label(WST_ETH_ADDRESS_OPTIMISM, "wstETH"); diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol index d5d325ecb..8d40fbafd 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol @@ -14,7 +14,7 @@ contract LidoWrapperTestSON is TestBase { address private constant ST_ETH_WHALE = 0xB67FB1422ACa6F017BFdF1c40b372dA9eEdD03BF; - function setUp() public { + function setUp() public override { vm.label(ST_ETH_ADDRESS, "stETH"); vm.label(WST_ETH_ADDRESS, "wstETH"); diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol index e8738c862..d03357314 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol @@ -16,7 +16,7 @@ contract LidoWrapperTestUNI is TestBase { uint256 private whaleBalance; - function setUp() public { + function setUp() public override { vm.label(ST_ETH_ADDRESS, "stETH"); vm.label(WST_ETH_ADDRESS, "wstETH"); diff --git a/test/solidity/Periphery/Permit2Proxy.t.sol b/test/solidity/Periphery/Permit2Proxy.t.sol index 34710c55d..077903447 100644 --- a/test/solidity/Periphery/Permit2Proxy.t.sol +++ b/test/solidity/Periphery/Permit2Proxy.t.sol @@ -117,7 +117,7 @@ contract Permit2ProxyTest is TestBase { error DiamondAddressNotWhitelisted(); error CallToDiamondFailed(bytes); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 20261175; initTestBase(); diff --git a/test/solidity/Periphery/ReceiverAcrossV3.t.sol b/test/solidity/Periphery/ReceiverAcrossV3.t.sol index 4a01ada82..9c8fe776c 100644 --- a/test/solidity/Periphery/ReceiverAcrossV3.t.sol +++ b/test/solidity/Periphery/ReceiverAcrossV3.t.sol @@ -25,7 +25,7 @@ contract ReceiverAcrossV3Test is TestBase { event ExecutorSet(address indexed executor); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 20024274; initTestBase(); diff --git a/test/solidity/Periphery/ReceiverChainflip.t.sol b/test/solidity/Periphery/ReceiverChainflip.t.sol index 72825fc30..6f6038624 100644 --- a/test/solidity/Periphery/ReceiverChainflip.t.sol +++ b/test/solidity/Periphery/ReceiverChainflip.t.sol @@ -27,7 +27,7 @@ contract ReceiverChainflipTest is TestBase { event ExecutorSet(address indexed executor); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 18277082; initTestBase(); diff --git a/test/solidity/Periphery/ReceiverStargateV2.t.sol b/test/solidity/Periphery/ReceiverStargateV2.t.sol index 23f24f3a1..cbbf5daa0 100644 --- a/test/solidity/Periphery/ReceiverStargateV2.t.sol +++ b/test/solidity/Periphery/ReceiverStargateV2.t.sol @@ -38,7 +38,7 @@ contract ReceiverStargateV2Test is TestBase { event ExecutorSet(address indexed executor); event RecoverGasSet(uint256 indexed recoverGas); - function setUp() public { + function setUp() public override { customBlockNumberForForking = 20024274; initTestBase(); diff --git a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol index defd6ae92..b37ffbaec 100644 --- a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol +++ b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol @@ -40,7 +40,7 @@ contract EmergencyPauseFacetPRODTest is TestBase { TestEmergencyPauseFacet internal emergencyPauseFacet; address[] internal blacklist = new address[](0); - function setUp() public { + function setUp() public override { // set custom block number for forking customBlockNumberForForking = 19979843; diff --git a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol index 1611d8af5..4afc8faf5 100644 --- a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol @@ -33,7 +33,7 @@ contract EmergencyPauseFacetLOCALTest is TestBase { EmergencyPauseFacet internal emergencyPauseFacet; address[] internal blacklist = new address[](0); - function setUp() public { + function setUp() public override { // set custom block number for forking customBlockNumberForForking = 19979843; diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 88501aef8..20e8ced95 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -15,6 +15,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; +import { LDADiamondTest } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; using stdJson for string; @@ -98,6 +99,7 @@ abstract contract TestBase is TestBaseRandomConstants, TestHelpers, DiamondTest, + LDADiamondTest, ILiFi { address internal _facetTestContractAddress; @@ -241,6 +243,8 @@ abstract contract TestBase is // deploy & configure diamond diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + // deploy & configure ldaDiamond + LDADiamondTest.setUp(); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From 3bade332b22f3bd016596abe8edb6ee7fd50abad Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 00:35:41 +0200 Subject: [PATCH 088/220] Add custom error handling for token transfer failures in AlgebraLiquidityAdderHelper --- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 43b9beee2..2ca66159e 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -790,6 +790,9 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { /// @notice Adds liquidity to an Algebra pool using balances available on this helper. /// @dev Implements `algebraMintCallback` to transfer owed amounts to the pool. contract AlgebraLiquidityAdderHelper { + error Token0TransferFailed(); + error Token1TransferFailed(); + /// @notice token0 used by the target pool. address public immutable TOKEN_0; /// @notice token1 used by the target pool. @@ -872,8 +875,7 @@ contract AlgebraLiquidityAdderHelper { uint256 balance0After = IERC20(TOKEN_0).balanceOf( address(msg.sender) ); - // solhint-disable-next-line gas-custom-errors - require(balance0After > balance0Before, "Transfer failed"); + if (balance0After <= balance0Before) revert Token0TransferFailed(); } if (amount1Owed > 0) { @@ -887,8 +889,7 @@ contract AlgebraLiquidityAdderHelper { uint256 balance1After = IERC20(TOKEN_1).balanceOf( address(msg.sender) ); - // solhint-disable-next-line gas-custom-errors - require(balance1After > balance1Before, "Transfer failed"); + if (balance1After <= balance1Before) revert Token1TransferFailed(); } } } From 44d328a1c11e384ea77cedb8b78522cb87b15ad4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 14:32:25 +0200 Subject: [PATCH 089/220] Refactor NativeWrapperFacet to streamline ETH withdrawal logic and improve error handling. Update unit tests --- .../Lda/Facets/NativeWrapperFacet.sol | 34 ++++++------------- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 5 ++- .../Lda/Facets/NativeWrapperFacet.t.sol | 20 +---------- 3 files changed, 15 insertions(+), 44 deletions(-) diff --git a/src/Periphery/Lda/Facets/NativeWrapperFacet.sol b/src/Periphery/Lda/Facets/NativeWrapperFacet.sol index 617518538..299cf9ab5 100644 --- a/src/Periphery/Lda/Facets/NativeWrapperFacet.sol +++ b/src/Periphery/Lda/Facets/NativeWrapperFacet.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; @@ -13,15 +12,8 @@ import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @notice Handles wrapping/unwrapping of native tokens (ETH <-> WETH) /// @custom:version 1.0.0 contract NativeWrapperFacet is BaseRouteConstants { - using SafeTransferLib for address; using LibPackedStream for uint256; - // ==== Errors ==== - /// @dev Thrown when native token operations fail - error NativeTransferFailed(); - /// @dev Thrown when WETH operations fail - error WETHOperationFailed(); - // ==== External Functions ==== /// @notice Unwraps WETH to native ETH /// @dev Handles unwrapping WETH and sending native ETH to recipient @@ -51,39 +43,33 @@ contract NativeWrapperFacet is BaseRouteConstants { address(this), amountIn ); - } else if (from != address(this)) { - // INTERNAL_INPUT_SOURCE means tokens are already at this contract - revert InvalidCallData(); } - try IWETH(tokenIn).withdraw(amountIn) { - destinationAddress.safeTransferETH(amountIn); - } catch { - revert WETHOperationFailed(); + IWETH(tokenIn).withdraw(amountIn); + if (destinationAddress != address(this)) { + LibAsset.transferNativeAsset( + payable(destinationAddress), + amountIn + ); } } /// @notice Wraps native ETH to WETH /// @dev Handles wrapping native ETH to WETH and sending to recipient /// @param swapData Encoded swap parameters [wrappedNative, destinationAddress] - /// @param tokenIn Should be INTERNAL_INPUT_SOURCE /// @param amountIn Amount of native ETH to wrap function wrapNative( bytes memory swapData, - address from, - address tokenIn, + address, // from is not used + address, // tokenIn is not used uint256 amountIn ) external payable { uint256 stream = LibPackedStream.createStream(swapData); + address wrappedNative = stream.readAddress(); address destinationAddress = stream.readAddress(); - if ( - wrappedNative == address(0) || - destinationAddress == address(0) || - from != address(this) || // opcode 3 invariant - tokenIn != INTERNAL_INPUT_SOURCE // opcode 3 invariant - ) { + if (wrappedNative == address(0) || destinationAddress == address(0)) { revert InvalidCallData(); } diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 4eab85365..4e8636126 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -351,7 +351,10 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { bytes memory route, bytes4 expectedRevert ) internal { - if (params.commandType != CommandType.DistributeSelfERC20) { + if ( + params.commandType != CommandType.DistributeSelfERC20 && + !LibAsset.isNativeAsset(params.tokenIn) + ) { IERC20(params.tokenIn).approve( address(ldaDiamond), params.amountIn diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol index bdcbde4b5..871f4b5e6 100644 --- a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol @@ -44,7 +44,7 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { function setUp() public override { // Set fork config for mainnet WETH testing customBlockNumberForForking = 23228012; - customRpcUrlForForking = vm.envString("ETH_NODE_URI_MAINNET"); + customRpcUrlForForking = "ETH_NODE_URI_MAINNET"; fork(); super.setUp(); @@ -316,24 +316,6 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { vm.stopPrank(); } - /// @notice Tests that unwrapNative reverts with invalid from address (INTERNAL_INPUT_SOURCE) - function testRevert_UnwrapNative_InvalidFromAddress() public { - uint256 amountIn = 1 ether; - - vm.startPrank(USER_SENDER); - - // Manually call the facet with INTERNAL_INPUT_SOURCE to trigger the error - vm.expectRevert(InvalidCallData.selector); - nativeWrapperFacet.unwrapNative( - abi.encodePacked(USER_RECEIVER), - 0x000000000000000000000000000000000000dEaD, // INTERNAL_INPUT_SOURCE - address(weth), - amountIn - ); - - vm.stopPrank(); - } - // ==== Helper Functions ==== /// @notice Builds swap data for unwrapNative operation From 465593485c995e6cd291d428fb076d16d73659c1 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 15:12:25 +0200 Subject: [PATCH 090/220] Refactor facet instance parameter naming in test contracts to use 'ldaDiamond' for clarity and consistency across BaseDEXFacetTest implementations. --- test/solidity/Periphery/Lda/BaseDexFacet.t.sol | 4 ++-- test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 8 +++----- test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 8 +++----- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 8 +++----- test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol | 6 ++---- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 6 ++---- test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 6 ++---- test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 6 ++---- 8 files changed, 19 insertions(+), 33 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index c1316bce9..984291e1d 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -100,8 +100,8 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { returns (address, bytes4[] memory); /// @notice Child must set its facet instance to the diamond proxy. - /// @param facetAddress Address of the diamond proxy that now exposes the facet. - function _setFacetInstance(address payable facetAddress) internal virtual; + /// @param ldaDiamond Address of the diamond proxy that now exposes the facet. + function _setFacetInstance(address payable ldaDiamond) internal virtual; /// @notice Child must configure tokens/pools for the selected fork. function _setupDexEnv() internal virtual; diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index b7d298eef..cdd5b31f3 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -58,11 +58,9 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { } /// @notice Sets `uniV2Facet` to the diamond proxy (post-cut). - /// @param facetAddress Diamond proxy address. - function _setFacetInstance( - address payable facetAddress - ) internal override { - uniV2Facet = UniV2StyleFacet(facetAddress); + /// @param ldaDiamond Diamond proxy address. + function _setFacetInstance(address payable ldaDiamond) internal override { + uniV2Facet = UniV2StyleFacet(ldaDiamond); } // ==== Helper Functions ==== diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 0fa4c4461..4399e2e28 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -57,11 +57,9 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { } /// @notice Sets `uniV3Facet` to the diamond proxy (post-cut). - /// @param facetAddress Diamond proxy address. - function _setFacetInstance( - address payable facetAddress - ) internal override { - uniV3Facet = UniV3StyleFacet(facetAddress); + /// @param ldaDiamond Diamond proxy address. + function _setFacetInstance(address payable ldaDiamond) internal override { + uniV3Facet = UniV3StyleFacet(ldaDiamond); } // ==== Helper Functions ==== diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 2ca66159e..f4ea33296 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -116,11 +116,9 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { } /// @notice Binds `algebraFacet` to the diamond proxy after diamondCut. - /// @param facetAddress Diamond proxy address. - function _setFacetInstance( - address payable facetAddress - ) internal override { - algebraFacet = AlgebraFacet(facetAddress); + /// @param ldaDiamond Diamond proxy address. + function _setFacetInstance(address payable ldaDiamond) internal override { + algebraFacet = AlgebraFacet(ldaDiamond); } /// @notice Sets fork-based token/pool addresses used by tests. diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 68c52f10e..88d302fb2 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -34,10 +34,8 @@ contract CurveFacetTest is BaseDEXFacetTest { } /// @notice Sets the facet instance to the diamond proxy after facet cut. - function _setFacetInstance( - address payable facetAddress - ) internal override { - curveFacet = CurveFacet(facetAddress); + function _setFacetInstance(address payable ldaDiamond) internal override { + curveFacet = CurveFacet(ldaDiamond); } /// @notice Defines tokens and pools used by tests (WETH/USDC/USDT). diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 88bead39d..9d1e79452 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -56,10 +56,8 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { } /// @notice Sets `izumiV3Facet` to the diamond proxy. - function _setFacetInstance( - address payable facetAddress - ) internal override { - izumiV3Facet = IzumiV3Facet(facetAddress); + function _setFacetInstance(address payable ldaDiamond) internal override { + izumiV3Facet = IzumiV3Facet(ldaDiamond); } /// @notice Defines a USDC/WETH/USDB_C path and pools on Base for tests. diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index a6cead1af..981049b4b 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -42,10 +42,8 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { } /// @notice Sets the facet instance to the diamond proxy after facet cut. - function _setFacetInstance( - address payable facetAddress - ) internal override { - syncSwapV2Facet = SyncSwapV2Facet(facetAddress); + function _setFacetInstance(address payable ldaDiamond) internal override { + syncSwapV2Facet = SyncSwapV2Facet(ldaDiamond); } /// @notice Defines tokens and pools used by tests (WETH/USDC/USDT). diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 4b0ff3a1e..34d8d2785 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -100,10 +100,8 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { } /// @notice Sets the facet instance to the diamond proxy. - function _setFacetInstance( - address payable facetAddress - ) internal override { - velodromeV2Facet = VelodromeV2Facet(facetAddress); + function _setFacetInstance(address payable ldaDiamond) internal override { + velodromeV2Facet = VelodromeV2Facet(ldaDiamond); } /// @notice Assigns tokens used in tests; pool addresses are resolved per-test from the router. From 3fe3b85fd4889e67aff9a05efe20b99841fb8669 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 15:14:20 +0200 Subject: [PATCH 091/220] Update SPDX license identifier in TestBase.sol from Unlicense to LGPL-3.0-only --- test/solidity/utils/TestBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 20e8ced95..c94d35607 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { Test } from "forge-std/Test.sol"; From 413c062fc2fcfb7583b5f5a94f08b2be8e9498cf Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 15:28:22 +0200 Subject: [PATCH 092/220] Update SPDX license identifier in IWETH.sol from MIT to LGPL-3.0-only --- src/Interfaces/IWETH.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interfaces/IWETH.sol b/src/Interfaces/IWETH.sol index 30548d693..ee6b0dc01 100644 --- a/src/Interfaces/IWETH.sol +++ b/src/Interfaces/IWETH.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; /// @title IWETH From af30e79579c67185f471663d5db91b7163cee3bf Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 15:44:08 +0200 Subject: [PATCH 093/220] Refactor BaseRouteConstants to rename INTERNAL_INPUT_SOURCE to FUNDS_IN_RECEIVER for clarity. Update references in CoreRouteFacet and VelodromeV2Facet to reflect this change, ensuring correct handling of token swaps when funds are already held by the receiving contract. --- src/Periphery/Lda/BaseRouteConstants.sol | 8 ++++++-- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 4 ++-- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Periphery/Lda/BaseRouteConstants.sol b/src/Periphery/Lda/BaseRouteConstants.sol index a67a16482..a2b9c70e2 100644 --- a/src/Periphery/Lda/BaseRouteConstants.sol +++ b/src/Periphery/Lda/BaseRouteConstants.sol @@ -9,6 +9,10 @@ pragma solidity ^0.8.17; abstract contract BaseRouteConstants { /// @dev Constant indicating swap direction from token0 to token1 uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; - /// @dev Used to indicate tokens are already in the pool/contract - address internal constant INTERNAL_INPUT_SOURCE = address(0); + /// @dev A sentinel address (0x0) used in the `from` parameter of a swap. + /// It signals that the input tokens for the swap are already held by the + /// receiving contract (e.g., from a previous swap in a multi-step route). + /// This tells the facet to use its current token balance instead of + /// pulling funds from an external address via `transferFrom`. + address internal constant FUNDS_IN_RECEIVER = address(0); } diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index c228d6335..64dd2064f 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -330,7 +330,7 @@ contract CoreRouteFacet is /// @return total The total amount of ETH to process function _distributeNative(uint256 cur) private returns (uint256 total) { total = address(this).balance; - _distributeAndSwap(cur, address(this), INTERNAL_INPUT_SOURCE, total); + _distributeAndSwap(cur, address(this), LibAsset.NULL_ADDRESS, total); } /// @notice Distributes ERC20 tokens already on this contract @@ -364,7 +364,7 @@ contract CoreRouteFacet is /// @param cur The current position in the byte stream function _dispatchSinglePoolSwap(uint256 cur) private { address token = cur.readAddress(); - _dispatchSwap(cur, INTERNAL_INPUT_SOURCE, token, 0); + _dispatchSwap(cur, FUNDS_IN_RECEIVER, token, 0); } /// @notice Distributes tokens across multiple pools based on share ratios diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 273b16e9e..6fc3ee1fd 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -49,7 +49,7 @@ contract VelodromeV2Facet is BaseRouteConstants { bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. // Will revert if contract (destinationAddress) does not implement IVelodromeV2PoolCallee. - if (from == INTERNAL_INPUT_SOURCE) { + if (from == FUNDS_IN_RECEIVER) { (uint256 reserve0, uint256 reserve1, ) = IVelodromeV2Pool(pool) .getReserves(); if (reserve0 == 0 || reserve1 == 0) revert WrongPoolReserves(); From e157a0de38d7177b07967673aef508876ec1a5af Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 15:54:16 +0200 Subject: [PATCH 094/220] Update functionSelectors array size in BaseUniV2StyleDexFacet --- test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index cdd5b31f3..73f125b0c 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -52,7 +52,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { returns (address, bytes4[] memory) { uniV2Facet = new UniV2StyleFacet(); - bytes4[] memory functionSelectors = new bytes4[](2); + bytes4[] memory functionSelectors = new bytes4[](1); functionSelectors[0] = uniV2Facet.swapUniV2.selector; return (address(uniV2Facet), functionSelectors); } From b2546ee3264f36765966eb8121cd29442d8cf39d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 16:12:12 +0200 Subject: [PATCH 095/220] Override setUp function in SwapperV2Test and PatcherTest to ensure proper initialization --- test/solidity/Helpers/SwapperV2.t.sol | 2 +- test/solidity/Periphery/Patcher.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/solidity/Helpers/SwapperV2.t.sol b/test/solidity/Helpers/SwapperV2.t.sol index dee047019..5fbd3280a 100644 --- a/test/solidity/Helpers/SwapperV2.t.sol +++ b/test/solidity/Helpers/SwapperV2.t.sol @@ -80,7 +80,7 @@ contract TestSwapperV2 is SwapperV2 { contract SwapperV2Test is TestBase { TestAMM internal amm; TestSwapperV2 internal swapper; - function setUp() public { + function setUp() public override { initTestBase(); amm = new TestAMM(); diff --git a/test/solidity/Periphery/Patcher.t.sol b/test/solidity/Periphery/Patcher.t.sol index 6fcdb9eee..61863239d 100644 --- a/test/solidity/Periphery/Patcher.t.sol +++ b/test/solidity/Periphery/Patcher.t.sol @@ -242,7 +242,7 @@ contract PatcherTest is TestBase, LiFiData { uint256 internal privateKey = 0x1234567890; address internal relaySolver; - function setUp() public { + function setUp() public override { initTestBase(); patcher = new Patcher(); valueSource = new MockValueSource(); From 842d77eaa62ac9621678bb79cfd5f4dd29573393 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 22:17:04 +0200 Subject: [PATCH 096/220] Update ICurve interface and CurveFacet to support payable exchanges, allowing native ETH handling in legacy pools. --- src/Interfaces/ICurve.sol | 7 ++++++- src/Periphery/Lda/Facets/CurveFacet.sol | 25 ++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Interfaces/ICurve.sol b/src/Interfaces/ICurve.sol index ac53de9ec..99ba0dc06 100644 --- a/src/Interfaces/ICurve.sol +++ b/src/Interfaces/ICurve.sol @@ -6,5 +6,10 @@ pragma solidity ^0.8.17; /// @notice Minimal Curve pool interface for exchange operations /// @custom:version 1.0.0 interface ICurve { - function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; + function exchange( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy + ) external payable; } diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index 1ec6c6009..3a8b242b0 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -56,7 +56,7 @@ contract CurveFacet { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); @@ -71,20 +71,17 @@ contract CurveFacet { uint256 amountOut; - if (from == msg.sender && amountIn > 0) { - LibAsset.transferFromERC20( - tokenIn, - msg.sender, - address(this), - amountIn - ); + if (from == msg.sender) { + LibAsset.depositAsset(tokenIn, amountIn); } LibAsset.maxApproveERC20(IERC20(tokenIn), pool, amountIn); // Track balances at the actual receiver for V2, otherwise at this contract address balAccount = isV2 ? destinationAddress : address(this); - uint256 balanceBefore = IERC20(tokenOut).balanceOf(balAccount); + uint256 balanceBefore = LibAsset.isNativeAsset(tokenOut) + ? balAccount.balance + : IERC20(tokenOut).balanceOf(balAccount); if (isV2) { if (from == address(0)) { @@ -98,6 +95,7 @@ contract CurveFacet { destinationAddress ); } else { + // Modern pools do not use pure native ETH path. They use WETH instead ICurveV2(pool).exchange( fromIndex, toIndex, @@ -107,10 +105,15 @@ contract CurveFacet { ); } } else { - ICurve(pool).exchange(fromIndex, toIndex, amountIn, 0); + // Legacy pools can accept/return native ETH + ICurve(pool).exchange{ + value: LibAsset.isNativeAsset(tokenIn) ? amountIn : 0 + }(fromIndex, toIndex, amountIn, 0); } - uint256 balanceAfter = IERC20(tokenOut).balanceOf(balAccount); + uint256 balanceAfter = LibAsset.isNativeAsset(tokenOut) + ? balAccount.balance + : IERC20(tokenOut).balanceOf(balAccount); amountOut = balanceAfter - balanceBefore; // Only transfer when legacy path kept tokens on this contract From b247edb0632af59f59df8307b9a93e8cf2fe885a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 27 Aug 2025 23:15:33 +0200 Subject: [PATCH 097/220] Enhance CurveFacet to support legacy ETH swaps with stETH, adding tests for ETH to stETH and stETH to ETH conversions. Update balance tracking for legacy pools and improve handling of native ETH in swap functions. --- src/Periphery/Lda/Facets/CurveFacet.sol | 50 ++++++---- .../Periphery/Lda/Facets/CurveFacet.t.sol | 92 ++++++++++++++++++- .../Periphery/Lda/utils/LdaDiamondTest.sol | 13 ++- 3 files changed, 133 insertions(+), 22 deletions(-) diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index 3a8b242b0..fd9b87f16 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -7,6 +7,7 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ICurve } from "lifi/Interfaces/ICurve.sol"; import { ICurveV2 } from "lifi/Interfaces/ICurveV2.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "lifi/Periphery/LDA/BaseRouteConstants.sol"; /// @title CurveFacet /// @author LI.FI (https://li.fi) @@ -14,10 +15,13 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @dev /// Pool Types & Interface Selection: /// 1. Legacy Pools (isV2 = false): -/// - Version <= 0.2.4 (like 3pool, compound, etc.) +/// - Version <= 0.2.4 (like 3pool, compound, ETH/stETH etc.) /// - Uses 4-arg exchange(i, j, dx, min_dy) /// - No receiver param, always sends to msg.sender -/// - We must transfer output tokens manually +/// - Native ETH Support: +/// * When selling ETH (i == 0): Accepts msg.value as input +/// * When buying ETH (j == 0): Returns native ETH via raw_call +/// - We must transfer output tokens manually to destinationAddress /// /// 2. Modern Pools (isV2 = true): /// - Factory pools and StableNG pools @@ -25,8 +29,9 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// - Direct transfer to specified receiver /// - For NG pools only: supports optimistic swap via exchange_received /// when from == address(0) signals tokens were pre-sent +/// - Does not support pure native ETH (uses wrapped versions) /// @custom:version 1.0.0 -contract CurveFacet { +contract CurveFacet is BaseRouteConstants { using LibPackedStream for uint256; using LibAsset for IERC20; @@ -45,11 +50,17 @@ contract CurveFacet { /// - if `from != msg.sender` - tokens are assumed to be already available (e.g., previous hop). /// Special case (NG optimistic): if `isV2 == 1` and `from == address(0)`, the facet calls /// `exchange_received` on NG pools (tokens must have been pre-sent to the pool). - /// - Indices (i,j) must match the pool’s coin ordering. + /// - Indices (i,j) must match the pool's coin ordering. + /// - Native ETH handling: + /// * For legacy pools (isV2 = false): + /// - When tokenIn is native: msg.value must equal amountIn + /// - When tokenOut is native: balance tracking uses address.balance + /// * For modern pools (isV2 = true): + /// - Native ETH not supported, use wrapped versions /// @param swapData Encoded swap parameters [pool, isV2, fromIndex, toIndex, destinationAddress, tokenOut] /// @param from Token source address; if equals msg.sender, tokens will be pulled; /// if set to address(0) with isV2==1, signals NG optimistic hop (tokens pre-sent) - /// @param tokenIn Input token address + /// @param tokenIn Input token address (address(0) for native ETH in legacy pools) /// @param amountIn Amount of input tokens (ignored for NG optimistic hop) function swapCurve( bytes memory swapData, @@ -69,22 +80,23 @@ contract CurveFacet { if (pool == address(0) || destinationAddress == address(0)) revert InvalidCallData(); - uint256 amountOut; - if (from == msg.sender) { LibAsset.depositAsset(tokenIn, amountIn); } LibAsset.maxApproveERC20(IERC20(tokenIn), pool, amountIn); - // Track balances at the actual receiver for V2, otherwise at this contract - address balAccount = isV2 ? destinationAddress : address(this); - uint256 balanceBefore = LibAsset.isNativeAsset(tokenOut) - ? balAccount.balance - : IERC20(tokenOut).balanceOf(balAccount); + // Only track balances for legacy path that needs manual transfer. Legacy pools doesn't have receiver param and always sends tokenOut to msg.sender + uint256 balanceBefore; + if (!isV2 && destinationAddress != address(this)) { + bool isNativeOut = LibAsset.isNativeAsset(tokenOut); + balanceBefore = isNativeOut + ? address(this).balance + : IERC20(tokenOut).balanceOf(address(this)); + } if (isV2) { - if (from == address(0)) { + if (from == FUNDS_IN_RECEIVER) { // Optimistic NG hop: tokens already sent to pool by previous hop. // NG requires _dx > 0 and asserts actual delta >= _dx. ICurveV2(pool).exchange_received( @@ -111,17 +123,17 @@ contract CurveFacet { }(fromIndex, toIndex, amountIn, 0); } - uint256 balanceAfter = LibAsset.isNativeAsset(tokenOut) - ? balAccount.balance - : IERC20(tokenOut).balanceOf(balAccount); - amountOut = balanceAfter - balanceBefore; - // Only transfer when legacy path kept tokens on this contract if (!isV2 && destinationAddress != address(this)) { + bool isNativeOut = LibAsset.isNativeAsset(tokenOut); + uint256 balanceAfter = isNativeOut + ? address(this).balance + : IERC20(tokenOut).balanceOf(address(this)); + LibAsset.transferAsset( tokenOut, payable(destinationAddress), - amountOut + balanceAfter - balanceBefore ); } } diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 88d302fb2..622e5b023 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -13,6 +13,11 @@ contract CurveFacetTest is BaseDEXFacetTest { /// @notice Facet proxy for swaps bound to the diamond after setup. CurveFacet internal curveFacet; + /// @notice Additional legacy curve pool for stETH/ETH + address internal poolStETHETH; + /// @notice stETH token for stETH/ETH pool + IERC20 internal stETH; + /// @notice Selects Linea fork and block height used by tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ @@ -45,6 +50,10 @@ contract CurveFacetTest is BaseDEXFacetTest { tokenOut = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); // USDT poolInMid = 0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E; // crvUSD-USDC poolMidOut = 0x4f493B7dE8aAC7d55F71853688b1F7C8F0243C85; // USDC-USDT + + // additional tokens for legacy curve pools + poolStETHETH = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022; // stETH/ETH pool + stETH = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); // stETH } /// @notice Single‐pool swap: USER sends crvUSD → receives USDC. @@ -147,7 +156,6 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - // Rest of the function remains the same SwapTestParams[] memory params = new SwapTestParams[](2); bytes[] memory swapData = new bytes[](2); @@ -342,6 +350,88 @@ contract CurveFacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Legacy stETH pool swap: USER sends native ETH → receives stETH via 4-arg exchange. + function test_CanSwap_LegacyEthPool_ETH_to_stETH() public { + // stETH/ETH pool on mainnet + uint256 amountIn = 1 ether; // 1 native ETH + + // Fund user with ETH + vm.deal(USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + // Build legacy swap data for ETH -> stETH + bytes memory swapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolStETHETH, + isV2: false, // legacy path + fromIndex: 0, // ETH index in stETH pool + toIndex: 1, // stETH index in stETH pool + destinationAddress: USER_SENDER, + tokenOut: address(stETH) + }) + ); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(0), // Native ETH + tokenOut: address(stETH), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeNative // Use native ETH distribution + }), + swapData + ); + + vm.stopPrank(); + } + + /// @notice Legacy stETH pool swap: USER sends stETH → receives native ETH via 4-arg exchange. + function test_CanSwap_LegacyEthPool_stETH_to_ETH() public { + // stETH/ETH pool on mainnet + uint256 amountIn = 1 ether; // 1 stETH + + address stETHWhaleAddress = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; + + // Fund user with stETH + vm.prank(stETHWhaleAddress); + stETH.transfer(USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + // Build legacy swap data for stETH -> ETH + bytes memory swapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolStETHETH, + isV2: false, // legacy path + fromIndex: 1, // stETH index + toIndex: 0, // ETH index + destinationAddress: USER_SENDER, + tokenOut: address(0) // Native ETH + }) + ); + + // Use the standard helper with isFeeOnTransferToken=true to handle stETH balance differences + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(stETH), + tokenOut: address(0), // Native ETH + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeUserERC20 + }), + swapData, + new ExpectedEvent[](0), + true // Allow for small balance differences due to stETH rebasing + ); + + vm.stopPrank(); + } + /// @notice Curve swap parameter shape used for `swapCurve`. struct CurveSwapParams { address pool; diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 281f3e929..a5f8632a4 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -98,7 +98,11 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { ); vm.expectRevert(LibDiamond.FunctionDoesNotExist.selector); - address(ldaDiamond).call(nonExistentCalldata); + (bool success, bytes memory returnData) = address(ldaDiamond).call( + nonExistentCalldata + ); + success; // silence unused variable warning + returnData; // silence unused variable warning } function testRevert_CannotCallUnregisteredSelector() public { @@ -111,6 +115,11 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { ); vm.expectRevert(LibDiamond.FunctionDoesNotExist.selector); - address(ldaDiamond).call(unregisteredCalldata); + // solhint-disable-next-line unused-return + (bool success, bytes memory returnData) = address(ldaDiamond).call( + unregisteredCalldata + ); + success; // silence unused variable warning + returnData; // silence unused variable warning } } From 01492346b5a2915436912abd25853ad7a7a181db Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 00:05:14 +0200 Subject: [PATCH 098/220] Enhance SyncSwapV2Facet tests to validate error handling for invalid withdraw modes and zero target addresses in V1 pools. Add scenarios for reverting on invalid inputs and ensure proper token approvals for swaps. --- .../Lda/Facets/SyncSwapV2Facet.t.sol | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 981049b4b..2a706d1d7 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -365,24 +365,30 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { /// @notice Only withdrawMode 0/1/2 are supported; invalid modes must revert. function testRevert_InvalidWithdrawMode() public { + // Transfer 1 000 WETH from whale to USER_SENDER + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + vm.startPrank(USER_SENDER); bytes memory swapDataWithInvalidWithdrawMode = _buildSyncSwapV2SwapData( SyncSwapV2SwapParams({ - pool: poolInOut, + pool: poolInMid, to: address(USER_SENDER), - withdrawMode: 3, + withdrawMode: 3, // Invalid withdraw mode (>2) isV1Pool: 1, vault: SYNC_SWAP_VAULT }) ); + // Approve tokens for the swap + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); + _buildRouteAndExecuteSwap( SwapTestParams({ tokenIn: address(tokenIn), - tokenOut: address(tokenOut), - amountIn: 1, + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), minOut: 0, sender: USER_SENDER, destinationAddress: USER_SENDER, @@ -395,6 +401,43 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice V1 pools with zero target address should revert + function testRevert_V1PoolZeroTarget() public { + // Transfer 1 000 WETH from whale to USER_SENDER + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + + // Swap data with isV1Pool=true but zero target (vault) address + bytes memory swapData = abi.encodePacked( + syncSwapV2Facet.swapSyncSwapV2.selector, + poolInMid, // pool address + USER_SENDER, // destination address + uint8(2), // withdrawMode + uint8(1), // isV1Pool = true + address(0) // target/vault address = zero address + ); + + // Approve tokens for the swap + tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); + + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenMid), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeUserERC20 + }), + swapData, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + /// @notice Empty test as SyncSwapV2 does not use callbacks /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification function testRevert_CallbackFromUnexpectedSender() public override { From 1b308ccb34c2f60b9e4ccec41b0df45d6f72e56a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 00:32:48 +0200 Subject: [PATCH 099/220] Refactor CurveFacet to conditionally approve ERC20 tokens based on asset type, improving native asset handling. Update import paths in test files for consistency with directory naming conventions. --- src/Periphery/Lda/Facets/CurveFacet.sol | 17 +++++++++++------ test/solidity/Periphery/GasZipPeriphery.t.sol | 8 ++++---- test/solidity/utils/TestBase.sol | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index fd9b87f16..25574a5e1 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -84,12 +84,15 @@ contract CurveFacet is BaseRouteConstants { LibAsset.depositAsset(tokenIn, amountIn); } - LibAsset.maxApproveERC20(IERC20(tokenIn), pool, amountIn); + bool isNativeOut = LibAsset.isNativeAsset(tokenOut); + bool isNativeIn = LibAsset.isNativeAsset(tokenIn); + if (!isNativeIn) { + LibAsset.maxApproveERC20(IERC20(tokenIn), pool, amountIn); + } // Only track balances for legacy path that needs manual transfer. Legacy pools doesn't have receiver param and always sends tokenOut to msg.sender uint256 balanceBefore; if (!isV2 && destinationAddress != address(this)) { - bool isNativeOut = LibAsset.isNativeAsset(tokenOut); balanceBefore = isNativeOut ? address(this).balance : IERC20(tokenOut).balanceOf(address(this)); @@ -118,14 +121,16 @@ contract CurveFacet is BaseRouteConstants { } } else { // Legacy pools can accept/return native ETH - ICurve(pool).exchange{ - value: LibAsset.isNativeAsset(tokenIn) ? amountIn : 0 - }(fromIndex, toIndex, amountIn, 0); + ICurve(pool).exchange{ value: isNativeIn ? amountIn : 0 }( + fromIndex, + toIndex, + amountIn, + 0 + ); } // Only transfer when legacy path kept tokens on this contract if (!isV2 && destinationAddress != address(this)) { - bool isNativeOut = LibAsset.isNativeAsset(tokenOut); uint256 balanceAfter = isNativeOut ? address(this).balance : IERC20(tokenOut).balanceOf(address(this)); diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index d61f66fae..37eec5736 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,10 +10,10 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LDADiamondTest } from "./Lda/utils/LdaDiamondTest.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; -import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; -import { NativeWrapperFacet } from "lifi/Periphery/Lda/Facets/NativeWrapperFacet.sol"; +import { LDADiamondTest } from "./LDA/utils/LdaDiamondTest.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; // Stub GenericSwapFacet Contract contract TestGasZipPeriphery is GasZipPeriphery { diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index c94d35607..dd8daee91 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -15,7 +15,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LDADiamondTest } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; +import { LDADiamondTest } from "../Periphery/LDA/utils/LdaDiamondTest.sol"; using stdJson for string; From 5cbf20a2bd22718d3e4bbcb7b17ebf25b7c84ba4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 01:01:23 +0200 Subject: [PATCH 100/220] Implement additional validation in AlgebraFacet and UniV3StyleFacet to check for amountIn exceeding int256 max value --- src/Periphery/Lda/Facets/AlgebraFacet.sol | 7 +++- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 6 ++- .../Lda/BaseUniV3StyleDexFacet.t.sol | 37 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 8b96cb73e..86e8a40a1 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -48,8 +48,11 @@ contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticated { address destinationAddress = stream.readAddress(); bool supportsFeeOnTransfer = stream.readUint8() > 0; - if (pool == address(0) || destinationAddress == address(0)) - revert InvalidCallData(); + if ( + pool == address(0) || + destinationAddress == address(0) || + amountIn > uint256(type(int256).max) + ) revert InvalidCallData(); if (from == msg.sender) { IERC20(tokenIn).safeTransferFrom( diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 8dd9a52e5..ba932a730 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -45,7 +45,11 @@ contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticated { bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; address destinationAddress = stream.readAddress(); - if (pool == address(0) || destinationAddress == address(0)) { + if ( + pool == address(0) || + destinationAddress == address(0) || + amountIn > uint256(type(int256).max) + ) { revert InvalidCallData(); } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 4399e2e28..284766297 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; /// @title BaseUniV3StyleDEXFacetTest @@ -238,4 +239,40 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { }) ); } + + /// @notice Tests that swaps with amountIn > type(int256).max revert + function testBase_Revert_SwapUniV3WithAmountOverInt256Max() public { + uint256 amountIn = uint256(type(int256).max) + 10; + + // Fund the sender + deal(address(tokenIn), USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + SwapDirection direction = _getDirection(poolInOut, address(tokenIn)); + bytes memory swapData = _buildUniV3SwapData( + UniV3SwapParams({ + pool: poolInOut, + direction: direction, + destinationAddress: USER_SENDER + }) + ); + + // Build route and execute with expected revert + _buildRouteAndExecuteSwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeUserERC20 + }), + swapData, + InvalidCallData.selector + ); + + vm.stopPrank(); + } } From cbebd52e22bc6bb1be7c6ca94f839da78919349b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 01:33:17 +0200 Subject: [PATCH 101/220] Remove LiFiDEXAggregator.sol file --- src/Periphery/LiFiDEXAggregator.sol | 1843 --------------------------- 1 file changed, 1843 deletions(-) delete mode 100644 src/Periphery/LiFiDEXAggregator.sol diff --git a/src/Periphery/LiFiDEXAggregator.sol b/src/Periphery/LiFiDEXAggregator.sol deleted file mode 100644 index 82db3be5f..000000000 --- a/src/Periphery/LiFiDEXAggregator.sol +++ /dev/null @@ -1,1843 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED - -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. -/// TODO: remove this file. - -pragma solidity ^0.8.17; - -import { SafeERC20, IERC20, IERC20Permit } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; -import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol"; -import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; -import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; -import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; -import { ISyncSwapVault } from "lifi/Interfaces/ISyncSwapVault.sol"; -import { ISyncSwapPool } from "lifi/Interfaces/ISyncSwapPool.sol"; -import { IKatanaV3Pool } from "lifi/Interfaces/KatanaV3/IKatanaV3Pool.sol"; -import { IKatanaV3Governance } from "lifi/Interfaces/KatanaV3/IKatanaV3Governance.sol"; -import { IKatanaV3AggregateRouter } from "lifi/Interfaces/KatanaV3/IKatanaV3AggregateRouter.sol"; -import { InvalidConfig, InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; - -address constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; -address constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001; -address constant INTERNAL_INPUT_SOURCE = 0x0000000000000000000000000000000000000000; - -uint8 constant LOCKED = 2; -uint8 constant NOT_LOCKED = 1; -uint8 constant PAUSED = 2; -uint8 constant NOT_PAUSED = 1; - -/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) -uint160 constant MIN_SQRT_RATIO = 4295128739; -/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) -uint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - -/// @dev iZiSwap pool price points boundaries -int24 constant IZUMI_LEFT_MOST_PT = -800000; -int24 constant IZUMI_RIGHT_MOST_PT = 800000; - -uint8 constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; -uint8 constant CALLBACK_ENABLED = 1; - -/// @dev Pool type identifiers used to determine which DEX protocol to interact with during swaps -uint8 constant POOL_TYPE_UNIV2 = 0; -uint8 constant POOL_TYPE_UNIV3 = 1; -uint8 constant POOL_TYPE_WRAP_NATIVE = 2; -uint8 constant POOL_TYPE_BENTO_BRIDGE = 3; -uint8 constant POOL_TYPE_TRIDENT = 4; -uint8 constant POOL_TYPE_CURVE = 5; -uint8 constant POOL_TYPE_VELODROME_V2 = 6; -uint8 constant POOL_TYPE_ALGEBRA = 7; -uint8 constant POOL_TYPE_IZUMI_V3 = 8; -uint8 constant POOL_TYPE_SYNCSWAP = 9; -uint8 constant POOL_TYPE_KATANA_V3 = 10; - -/// @dev command for V3_SWAP_EXACT_IN (0x00) for KatanaV3 -bytes constant KATANA_V3_SWAP_EXACT_IN = hex"00"; - -/// @title LiFiDEXAggregator -/// @author Ilya Lyalin (contract copied from: -/// https://github.com/sushiswap/sushiswap/blob/c8c80dec821003eb72eb77c7e0446ddde8ca9e1e/ -/// protocols/route-processor/contracts/RouteProcessor4.sol) -/// @notice Processes calldata to swap using various DEXs -/// @custom:version 1.12.0 -contract LiFiDEXAggregator is WithdrawablePeriphery { - using SafeERC20 for IERC20; - using Approve for IERC20; - using SafeERC20 for IERC20Permit; - using InputStream for uint256; - - event Route( - address indexed from, - address to, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOutMin, - uint256 amountOut - ); - - error MinimalOutputBalanceViolation(uint256 amountOut); - error RouteProcessorLocked(); - error RouteProcessorPaused(); - error CallerNotOwnerOrPriviledged(); - error UnknownCommandCode(); - error UnknownPoolType(); - error MinimalInputBalanceViolation(uint256 available, uint256 required); - error UniswapV3SwapUnexpected(); - error UniswapV3SwapCallbackUnknownSource(); - error UniswapV3SwapCallbackNotPositiveAmount(); - error WrongPoolReserves(); - error AlgebraSwapUnexpected(); - error IzumiV3SwapUnexpected(); - error IzumiV3SwapCallbackUnknownSource(); - error IzumiV3SwapCallbackNotPositiveAmount(); - - IBentoBoxMinimal public immutable BENTO_BOX; - mapping(address => bool) public priviledgedUsers; - address private lastCalledPool; - - uint8 private unlocked = NOT_LOCKED; - uint8 private paused = NOT_PAUSED; - modifier lock() { - if (unlocked != NOT_LOCKED) revert RouteProcessorLocked(); - if (paused != NOT_PAUSED) revert RouteProcessorPaused(); - unlocked = LOCKED; - _; - unlocked = NOT_LOCKED; - } - - modifier onlyOwnerOrPriviledgedUser() { - if (!(msg.sender == owner || priviledgedUsers[msg.sender])) - revert CallerNotOwnerOrPriviledged(); - _; - } - - constructor( - address _bentoBox, - address[] memory priviledgedUserList, - address _owner - ) WithdrawablePeriphery(_owner) { - if (_owner == address(0)) { - revert InvalidConfig(); - } - BENTO_BOX = IBentoBoxMinimal(_bentoBox); - lastCalledPool = IMPOSSIBLE_POOL_ADDRESS; - - for (uint256 i = 0; i < priviledgedUserList.length; i++) { - priviledgedUsers[priviledgedUserList[i]] = true; - } - } - - function setPriviledge(address user, bool priviledge) external onlyOwner { - priviledgedUsers[user] = priviledge; - } - - function pause() external onlyOwnerOrPriviledgedUser { - paused = PAUSED; - } - - function resume() external onlyOwnerOrPriviledgedUser { - paused = NOT_PAUSED; - } - - /// @notice For native unwrapping - receive() external payable {} - - /// @notice Processes the route generated off-chain. Has a lock - /// @param tokenIn Address of the input token - /// @param amountIn Amount of the input token - /// @param tokenOut Address of the output token - /// @param amountOutMin Minimum amount of the output token - /// @return amountOut Actual amount of the output token - function processRoute( - address tokenIn, - uint256 amountIn, - address tokenOut, - uint256 amountOutMin, - address to, - bytes memory route - ) external payable lock returns (uint256 amountOut) { - return - processRouteInternal( - tokenIn, - amountIn, - tokenOut, - amountOutMin, - to, - route - ); - } - - /// @notice Transfers some value to and then processes the route - /// @param transferValueTo Address where the value should be transferred - /// @param amountValueTransfer How much value to transfer - /// @param tokenIn Address of the input token - /// @param amountIn Amount of the input token - /// @param tokenOut Address of the output token - /// @param amountOutMin Minimum amount of the output token - /// @return amountOut Actual amount of the output token - function transferValueAndprocessRoute( - address payable transferValueTo, - uint256 amountValueTransfer, - address tokenIn, - uint256 amountIn, - address tokenOut, - uint256 amountOutMin, - address to, - bytes memory route - ) external payable lock returns (uint256 amountOut) { - SafeTransferLib.safeTransferETH(transferValueTo, amountValueTransfer); - return - processRouteInternal( - tokenIn, - amountIn, - tokenOut, - amountOutMin, - to, - route - ); - } - - /// @notice Processes the route generated off-chain - /// @param tokenIn Address of the input token - /// @param amountIn Amount of the input token - /// @param tokenOut Address of the output token - /// @param amountOutMin Minimum amount of the output token - /// @return amountOut Actual amount of the output token - function processRouteInternal( - address tokenIn, - uint256 amountIn, - address tokenOut, - uint256 amountOutMin, - address to, - bytes memory route - ) private returns (uint256 amountOut) { - uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS - ? 0 - : IERC20(tokenIn).balanceOf(msg.sender); - uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS - ? address(to).balance - : IERC20(tokenOut).balanceOf(to); - - uint256 realAmountIn = amountIn; - { - uint256 step = 0; - uint256 stream = InputStream.createStream(route); - while (stream.isNotEmpty()) { - uint8 commandCode = stream.readUint8(); - if (commandCode == 1) { - uint256 usedAmount = processMyERC20(stream); - if (step == 0) realAmountIn = usedAmount; - } else if (commandCode == 2) - processUserERC20(stream, amountIn); - else if (commandCode == 3) { - uint256 usedAmount = processNative(stream); - if (step == 0) realAmountIn = usedAmount; - } else if (commandCode == 4) processOnePool(stream); - else if (commandCode == 5) processInsideBento(stream); - else if (commandCode == 6) applyPermit(tokenIn, stream); - else revert UnknownCommandCode(); - ++step; - } - } - - uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS - ? 0 - : IERC20(tokenIn).balanceOf(msg.sender); - if (balanceInFinal + amountIn < balanceInInitial) - revert MinimalInputBalanceViolation( - balanceInFinal + amountIn, - balanceInInitial - ); - - uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS - ? address(to).balance - : IERC20(tokenOut).balanceOf(to); - if (balanceOutFinal < balanceOutInitial + amountOutMin) - revert MinimalOutputBalanceViolation( - balanceOutFinal - balanceOutInitial - ); - - amountOut = balanceOutFinal - balanceOutInitial; - - emit Route( - msg.sender, - to, - tokenIn, - tokenOut, - realAmountIn, - amountOutMin, - amountOut - ); - } - - /// @notice Applies ERC-2612 permit - /// @param tokenIn permitted token - /// @param stream Streamed program - function applyPermit(address tokenIn, uint256 stream) private { - uint256 value = stream.readUint(); - uint256 deadline = stream.readUint(); - uint8 v = stream.readUint8(); - bytes32 r = stream.readBytes32(); - bytes32 s = stream.readBytes32(); - IERC20Permit(tokenIn).safePermit( - msg.sender, - address(this), - value, - deadline, - v, - r, - s - ); - } - - /// @notice Processes native coin: call swap for all pools that swap from native coin - /// @param stream Streamed program - function processNative( - uint256 stream - ) private returns (uint256 amountTotal) { - amountTotal = address(this).balance; - distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal); - } - - /// @notice Processes ERC20 token from this contract balance: - /// @notice Call swap for all pools that swap from this token - /// @param stream Streamed program - function processMyERC20( - uint256 stream - ) private returns (uint256 amountTotal) { - address token = stream.readAddress(); - amountTotal = IERC20(token).balanceOf(address(this)); - unchecked { - if (amountTotal > 0) amountTotal -= 1; // slot undrain protection - } - distributeAndSwap(stream, address(this), token, amountTotal); - } - - /// @notice Processes ERC20 token from msg.sender balance: - /// @notice Call swap for all pools that swap from this token - /// @param stream Streamed program - /// @param amountTotal Amount of tokens to take from msg.sender - function processUserERC20(uint256 stream, uint256 amountTotal) private { - address token = stream.readAddress(); - distributeAndSwap(stream, msg.sender, token, amountTotal); - } - - /// @notice Processes ERC20 token for cases when the token has only one output pool - /// @notice In this case liquidity is already at pool balance. This is an optimization - /// @notice Call swap for all pools that swap from this token - /// @dev WARNING: This function passes amountIn as 0 which may not work with some UniswapV3 - /// @dev forks that require non-zero amounts for their pricing/slippage calculations. - /// @dev Use with caution for V3-style pools. - /// @param stream Streamed program - function processOnePool(uint256 stream) private { - address token = stream.readAddress(); - swap(stream, INTERNAL_INPUT_SOURCE, token, 0); - } - - /// @notice Processes Bento tokens - /// @notice Call swap for all pools that swap from this token - /// @param stream Streamed program - function processInsideBento(uint256 stream) private { - address token = stream.readAddress(); - uint256 amountTotal = BENTO_BOX.balanceOf(token, address(this)); - unchecked { - if (amountTotal > 0) amountTotal -= 1; // slot undrain protection - } - distributeAndSwap(stream, address(this), token, amountTotal); - } - - /// @notice Distributes amountTotal to several pools according to their shares and calls swap for each pool - /// @param stream Streamed program - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountTotal Total amount of tokenIn for swaps - function distributeAndSwap( - uint256 stream, - address from, - address tokenIn, - uint256 amountTotal - ) private { - uint8 num = stream.readUint8(); - unchecked { - for (uint256 i = 0; i < num; ++i) { - uint16 share = stream.readUint16(); - uint256 amount = (amountTotal * share) / - type(uint16).max /*65535*/; - amountTotal -= amount; - swap(stream, from, tokenIn, amount); - } - } - } - - /// @notice Makes swap - /// @param stream Streamed program - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swap( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - uint8 poolType = stream.readUint8(); - if (poolType == POOL_TYPE_UNIV2) - swapUniV2(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_UNIV3) - swapUniV3(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_WRAP_NATIVE) - wrapNative(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_BENTO_BRIDGE) - bentoBridge(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_TRIDENT) - swapTrident(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_CURVE) - swapCurve(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_VELODROME_V2) - swapVelodromeV2(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_ALGEBRA) - swapAlgebra(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_IZUMI_V3) - swapIzumiV3(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_SYNCSWAP) - swapSyncSwap(stream, from, tokenIn, amountIn); - else if (poolType == POOL_TYPE_KATANA_V3) - swapKatanaV3(stream, from, tokenIn, amountIn); - else revert UnknownPoolType(); - } - - /// @notice Wraps/unwraps native token - /// @param stream [direction & fake, recipient, wrapToken?] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function wrapNative( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - uint8 directionAndFake = stream.readUint8(); - address to = stream.readAddress(); - - if (directionAndFake & 1 == 1) { - // wrap native - address wrapToken = stream.readAddress(); - if (directionAndFake & 2 == 0) - IWETH(wrapToken).deposit{ value: amountIn }(); - if (to != address(this)) - IERC20(wrapToken).safeTransfer(to, amountIn); - } else { - // unwrap native - if (directionAndFake & 2 == 0) { - if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(this), - amountIn - ); - IWETH(tokenIn).withdraw(amountIn); - } - SafeTransferLib.safeTransferETH(to, amountIn); - } - } - - /// @notice Bridge/unbridge tokens to/from Bento - /// @param stream [direction, recipient] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function bentoBridge( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - uint8 direction = stream.readUint8(); - address to = stream.readAddress(); - - if (direction > 0) { - // outside to Bento - // deposit to arbitrary recipient is possible only from address(BENTO_BOX) - if (from == address(this)) - IERC20(tokenIn).safeTransfer(address(BENTO_BOX), amountIn); - else if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(BENTO_BOX), - amountIn - ); - else { - // tokens already are at address(BENTO_BOX) - amountIn = - IERC20(tokenIn).balanceOf(address(BENTO_BOX)) + - BENTO_BOX.strategyData(tokenIn).balance - - BENTO_BOX.totals(tokenIn).elastic; - } - BENTO_BOX.deposit(tokenIn, address(BENTO_BOX), to, amountIn, 0); - } else { - // Bento to outside - if (from != INTERNAL_INPUT_SOURCE) { - BENTO_BOX.transfer(tokenIn, from, address(this), amountIn); - } else amountIn = BENTO_BOX.balanceOf(tokenIn, address(this)); - BENTO_BOX.withdraw(tokenIn, address(this), to, 0, amountIn); - } - } - - /// @notice UniswapV2 pool swap - /// @param stream [pool, direction, recipient, fee] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapUniV2( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - uint8 direction = stream.readUint8(); - address to = stream.readAddress(); - uint24 fee = stream.readUint24(); // pool fee in 1/1_000_000 - - if (from == address(this)) - IERC20(tokenIn).safeTransfer(pool, amountIn); - else if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom(msg.sender, pool, amountIn); - - (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves(); - if (r0 == 0 || r1 == 0) revert WrongPoolReserves(); - (uint256 reserveIn, uint256 reserveOut) = direction == - DIRECTION_TOKEN0_TO_TOKEN1 - ? (r0, r1) - : (r1, r0); - amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; // tokens already were transferred - - uint256 amountInWithFee = amountIn * (1_000_000 - fee); - uint256 amountOut = (amountInWithFee * reserveOut) / - (reserveIn * 1_000_000 + amountInWithFee); - (uint256 amount0Out, uint256 amount1Out) = direction == - DIRECTION_TOKEN0_TO_TOKEN1 - ? (uint256(0), amountOut) - : (amountOut, uint256(0)); - IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0)); - } - - /// @notice Trident pool swap - /// @param stream [pool, swapData] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapTrident( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - bytes memory swapData = stream.readBytes(); - - if (from != INTERNAL_INPUT_SOURCE) { - BENTO_BOX.transfer(tokenIn, from, pool, amountIn); - } - - IPool(pool).swap(swapData); - } - - /// @notice UniswapV3 pool swap - /// @param stream [pool, direction, recipient] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapUniV3( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - bool direction = stream.readUint8() > 0; - address recipient = stream.readAddress(); - - if ( - pool == address(0) || - pool == IMPOSSIBLE_POOL_ADDRESS || - recipient == address(0) - ) revert InvalidCallData(); - - if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(this), - uint256(amountIn) - ); - - lastCalledPool = pool; - IUniswapV3Pool(pool).swap( - recipient, - direction, - int256(amountIn), - direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, - abi.encode(tokenIn) - ); - if (lastCalledPool != IMPOSSIBLE_POOL_ADDRESS) - revert UniswapV3SwapUnexpected(); // Just to be sure - } - - /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. - /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call - function uniswapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) public { - if (msg.sender != lastCalledPool) - revert UniswapV3SwapCallbackUnknownSource(); - int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta; - if (amount <= 0) revert UniswapV3SwapCallbackNotPositiveAmount(); - - lastCalledPool = IMPOSSIBLE_POOL_ADDRESS; - address tokenIn = abi.decode(data, (address)); - IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount)); - } - - /// @notice Called to `msg.sender` after executing a swap via IAlgebraPool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method _must_ be checked to be a AlgebraPool deployed by the canonical AlgebraFactory. - /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the IAlgebraPoolActions#swap call - function algebraSwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via PancakeV3Pool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the PancakeV3Pool#swap call - function pancakeV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via RaExchangeV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the RaExchangeV3#swap call - function ramsesV2SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via XeiV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the XeiV3#swap call - function xeiV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via DragonSwapV2#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the DragonSwapV2#swap call - function dragonswapV2SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via AgniV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) - /// by the pool by the end of the swap. If positive, the callback must send that amount of - /// token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) - /// by the pool by the end of the swap. If positive, the callback must send that amount of - /// token1 to the pool. - /// @param data Any data passed through by the caller via the AgniV3#swap call - function agniSwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via FusionXV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the FusionXV3#swap call - function fusionXV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via VVS FinanceV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the VVS Finance V3#swap call - function vvsV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via SupSwapV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the SupSwapV3#swap call - function supV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via ZebraV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the ZebraV3#swap call - function zebraV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Performs a swap through iZiSwap V3 pools - /// @dev This function handles both X to Y and Y to X swaps through iZiSwap V3 pools - /// @param stream [pool, direction, recipient] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapIzumiV3( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - uint8 direction = stream.readUint8(); // 0 = Y2X, 1 = X2Y - address recipient = stream.readAddress(); - if ( - pool == address(0) || - pool == IMPOSSIBLE_POOL_ADDRESS || - recipient == address(0) || - amountIn > type(uint128).max - ) revert InvalidCallData(); - - if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(this), - amountIn - ); - } - - lastCalledPool = pool; - - if (direction == DIRECTION_TOKEN0_TO_TOKEN1) { - IiZiSwapPool(pool).swapX2Y( - recipient, - uint128(amountIn), - IZUMI_LEFT_MOST_PT + 1, - abi.encode(tokenIn) - ); - } else { - IiZiSwapPool(pool).swapY2X( - recipient, - uint128(amountIn), - IZUMI_RIGHT_MOST_PT - 1, - abi.encode(tokenIn) - ); - } - - // After the swapX2Y or swapY2X call, the callback should reset lastCalledPool to IMPOSSIBLE_POOL_ADDRESS. - // If it hasn't, it means the callback either didn't happen, was incorrect, or the pool misbehaved so we revert - // to protect against misuse or faulty integrations - if (lastCalledPool != IMPOSSIBLE_POOL_ADDRESS) { - revert IzumiV3SwapUnexpected(); - } - } - - /// @notice Performs a swap through SyncSwap pools - /// @dev This function handles both X to Y and Y to X swaps through SyncSwap pools. - /// See [SyncSwap API documentation](https://docs.syncswap.xyz/api-documentation) for protocol details. - /// @param stream [pool, to, withdrawMode, isV1Pool, vault] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapSyncSwap( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - address to = stream.readAddress(); - - if (pool == address(0) || to == address(0)) revert InvalidCallData(); - - // withdrawMode meaning for SyncSwap via vault: - // 1: Withdraw raw ETH (native) - // 2: Withdraw WETH (wrapped) - // 0: Let the vault decide (ETH for native, WETH for wrapped) - // For ERC-20 tokens the vault just withdraws the ERC-20 - // and this mode byte is read and ignored by the ERC-20 branch. - uint8 withdrawMode = stream.readUint8(); - - if (withdrawMode > 2) revert InvalidCallData(); - - bool isV1Pool = stream.readUint8() == 1; - - address target = isV1Pool ? stream.readAddress() : pool; // target is the vault for V1 pools, the pool for V2 pools - if (isV1Pool && target == address(0)) revert InvalidCallData(); - - if (from == msg.sender) { - IERC20(tokenIn).safeTransferFrom(msg.sender, target, amountIn); - } else if (from == address(this)) { - IERC20(tokenIn).safeTransfer(target, amountIn); - } - // if from is not msg.sender or address(this), it must be INTERNAL_INPUT_SOURCE - // which means tokens are already in the vault/pool, no transfer needed - - if (isV1Pool) { - ISyncSwapVault(target).deposit(tokenIn, pool); - } - - bytes memory data = abi.encode(tokenIn, to, withdrawMode); - - ISyncSwapPool(pool).swap(data, from, address(0), new bytes(0)); - } - - /// @notice Performs a swap through KatanaV3 pools - /// @dev This function handles swaps through KatanaV3 pools. - /// @param stream [pool, direction, recipient] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapKatanaV3( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; - address recipient = stream.readAddress(); - - if ( - pool == address(0) || - pool == IMPOSSIBLE_POOL_ADDRESS || - recipient == address(0) - ) revert InvalidCallData(); - - // get router address from pool governance - address governance = IKatanaV3Pool(pool).governance(); - address router = IKatanaV3Governance(governance).getRouter(); - - // get pool info for constructing the path - uint24 fee = IKatanaV3Pool(pool).fee(); - - // determine tokenOut based on swap direction - address tokenOut = direction - ? IKatanaV3Pool(pool).token1() - : IKatanaV3Pool(pool).token0(); - - // transfer tokens to the router - if (from == msg.sender) { - LibAsset.transferFromERC20(tokenIn, msg.sender, router, amountIn); - } else if (from == address(this)) { - LibAsset.transferERC20(tokenIn, router, amountIn); - } - - // construct the path for V3 swap (tokenIn -> tokenOut with fee) - bytes memory path = abi.encodePacked(tokenIn, fee, tokenOut); - - // encode the inputs for V3_SWAP_EXACT_IN - // set payerIsUser to false since we already transferred tokens to the router - bytes[] memory inputs = new bytes[](1); - inputs[0] = abi.encode( - recipient, // recipient - amountIn, // amountIn - 0, // amountOutMin (0, as we handle slippage at higher level) - path, // path - false // payerIsUser (false since tokens are already in router) - ); - - // call the router's execute function - // first parameter for execute is the command for V3_SWAP_EXACT_IN (0x00) - IKatanaV3AggregateRouter(router).execute( - KATANA_V3_SWAP_EXACT_IN, - inputs - ); - - // katanaV3SwapCallback implementation is in the router contract itself - } - - /// @dev Common logic for iZiSwap callbacks - /// @param amountToPay The amount of tokens to be sent to the pool - /// @param data The data passed through by the caller - function _handleIzumiV3SwapCallback( - uint256 amountToPay, - bytes calldata data - ) private { - if (msg.sender != lastCalledPool) { - revert IzumiV3SwapCallbackUnknownSource(); - } - - if (amountToPay == 0) { - revert IzumiV3SwapCallbackNotPositiveAmount(); - } - - address tokenIn = abi.decode(data, (address)); - - // After a successful callback, we reset lastCalledPool to an impossible address. - // This prevents any subsequent callback from erroneously succeeding. - // It's a security measure to avoid reentrancy or misuse of stale state in future callbacks - lastCalledPool = IMPOSSIBLE_POOL_ADDRESS; - - IERC20(tokenIn).safeTransfer(msg.sender, amountToPay); - } - - /// @notice Called to `msg.sender` after executing a swap via IiZiSwapPool#swapX2Y - /// @dev In the implementation you must pay the pool tokens owed for the swap - /// @dev The caller of this method is checked against the `lastCalledPool` variable set during the swap call - /// This protects against unauthorized callbacks - /// @param amountX The amount of tokenX that must be sent to the pool by the end of the swap - /// @param {unused} The amount of tokenY that was sent by the pool in the swap - /// @param data Any data passed through by the caller via the IiZiSwapPool#swapX2Y call - function swapX2YCallback( - uint256 amountX, - uint256, - bytes calldata data - ) external { - _handleIzumiV3SwapCallback(amountX, data); - } - - /// @notice Called to `msg.sender` after executing a swap via IiZiSwapPool#swapY2X - /// @dev In the implementation you must pay the pool tokens owed for the swap - /// @dev The caller of this method is checked against the `lastCalledPool` variable set during the swap call - /// This protects against unauthorized callbacks - /// @param {unused} The amount of tokenX that was sent by the pool in the swap - /// @param amountY The amount of tokenY that must be sent to the pool by the end of the swap - /// @param data Any data passed through by the caller via the IiZiSwapPool#swapY2X call - function swapY2XCallback( - uint256, - uint256 amountY, - bytes calldata data - ) external { - // In swapY2X, we're swapping from tokenY to tokenX - // The pool will expect us to transfer the tokenY amount - _handleIzumiV3SwapCallback(amountY, data); - } - - /// @notice Called to `msg.sender` after executing a swap via HyperswapV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the HyperswapV3#swap call - function hyperswapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via LaminarV3#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the LaminarV3#swap call - function laminarV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via IXSwapPool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method must be checked to be a XSwapPool deployed by the canonical XSwapFactory. - /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the IXSwapPoolActions#swap call - function xswapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via IRabbitSwapV3Pool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method must be checked to be a RabbitSwapV3Pool deployed by the canonical RabbitSwapV3Factory. - /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the IRabbitSwapV3PoolActions#swap call - function rabbitSwapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Called to `msg.sender` after executing a swap via IEnosysDexV3Pool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method must be checked to be a EnosysDexV3Pool deployed by the canonical EnosysDexV3Factory. - /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the IEnosysDexV3PoolActions#swap call - function enosysdexV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata data - ) external { - uniswapV3SwapCallback(amount0Delta, amount1Delta, data); - } - - /// @notice Curve pool swap. Legacy pools that don't return amountOut and have native coins are not supported - /// @param stream [pool, poolType, fromIndex, toIndex, recipient, output token] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapCurve( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - uint8 poolType = stream.readUint8(); - int128 fromIndex = int8(stream.readUint8()); - int128 toIndex = int8(stream.readUint8()); - address to = stream.readAddress(); - address tokenOut = stream.readAddress(); - - uint256 amountOut; - if (tokenIn == NATIVE_ADDRESS) { - amountOut = ICurve(pool).exchange{ value: amountIn }( - fromIndex, - toIndex, - amountIn, - 0 - ); - } else { - if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(this), - amountIn - ); - IERC20(tokenIn).approveSafe(pool, amountIn); - if (poolType == 0) - amountOut = ICurve(pool).exchange( - fromIndex, - toIndex, - amountIn, - 0 - ); - else { - uint256 balanceBefore = IERC20(tokenOut).balanceOf( - address(this) - ); - ICurveLegacy(pool).exchange(fromIndex, toIndex, amountIn, 0); - uint256 balanceAfter = IERC20(tokenOut).balanceOf( - address(this) - ); - amountOut = balanceAfter - balanceBefore; - } - } - - if (to != address(this)) { - if (tokenOut == NATIVE_ADDRESS) { - SafeTransferLib.safeTransferETH(to, amountOut); - } else { - IERC20(tokenOut).safeTransfer(to, amountOut); - } - } - } - - /// @notice Performs a swap through VelodromeV2 pools - /// @dev This function does not handle native token swaps directly, so processNative command cannot be used - /// @param stream [pool, direction, to, callback] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - function swapVelodromeV2( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - uint8 direction = stream.readUint8(); - address to = stream.readAddress(); - if (pool == address(0) || to == address(0)) revert InvalidCallData(); - // solhint-disable-next-line max-line-length - bool callback = stream.readUint8() == CALLBACK_ENABLED; // if true then run callback after swap with tokenIn as flashloan data. Will revert if contract (to) does not implement IVelodromeV2PoolCallee - - if (from == INTERNAL_INPUT_SOURCE) { - (uint256 reserve0, uint256 reserve1, ) = IVelodromeV2Pool(pool) - .getReserves(); - if (reserve0 == 0 || reserve1 == 0) revert WrongPoolReserves(); - uint256 reserveIn = direction == DIRECTION_TOKEN0_TO_TOKEN1 - ? reserve0 - : reserve1; - - amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; - } else { - if (from == address(this)) - IERC20(tokenIn).safeTransfer(pool, amountIn); - else if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom(msg.sender, pool, amountIn); - } - - // calculate the expected output amount using the pool's getAmountOut function - uint256 amountOut = IVelodromeV2Pool(pool).getAmountOut( - amountIn, - tokenIn - ); - - // set the appropriate output amount based on which token is being swapped - // determine output amounts based on direction - uint256 amount0Out = direction == DIRECTION_TOKEN0_TO_TOKEN1 - ? 0 - : amountOut; - uint256 amount1Out = direction == DIRECTION_TOKEN0_TO_TOKEN1 - ? amountOut - : 0; - - // 'swap' function from IVelodromeV2Pool should be called from a contract which performs important safety checks. - // Safety Checks Covered: - // - Reentrancy: LDA has a custom lock() modifier - // - Token transfer safety: SafeERC20 is used to ensure token transfers revert on failure - // - Expected output verification: The contract calls getAmountOut (including fees) before executing the swap - // - Flashloan trigger: A flashloan flag is used to determine if the callback should be triggered - // - Post-swap verification: In processRouteInternal, it verifies that the recipient receives at least minAmountOut - // and that the sender's final balance is not less than the initial balance - // - Immutable interaction: Velodrome V2 pools and the router are not upgradable, - // so we can rely on the behavior of getAmountOut and swap - - // ATTENTION FOR CALLBACKS / HOOKS: - // - recipient contracts should validate that msg.sender is the Velodrome pool contract who is calling the hook - // - recipient contracts must not manipulate their own tokenOut balance - // (as this may bypass/invalidate the built-in slippage protection) - // - @developers: never trust balance-based slippage protection for callback recipients - // - @integrators: do not use slippage guarantees when recipient is a contract with side-effects - IVelodromeV2Pool(pool).swap( - amount0Out, - amount1Out, - to, - callback ? abi.encode(tokenIn) : bytes("") - ); - } - - /// @notice Algebra pool swap - /// @param stream [pool, direction, recipient, supportsFeeOnTransfer] - /// @param from Where to take liquidity for swap - /// @param tokenIn Input token - /// @param amountIn Amount of tokenIn to take for swap - /// @dev The supportsFeeOnTransfer flag accepts any non-zero value (1-255) to enable fee-on-transfer handling. - /// When enabled, the swap will first attempt to use swapSupportingFeeOnInputTokens(), and if that fails, - /// it will fall back to the regular swap() function. A value of 0 disables fee-on-transfer handling. - function swapAlgebra( - uint256 stream, - address from, - address tokenIn, - uint256 amountIn - ) private { - address pool = stream.readAddress(); - // solhint-disable-next-line max-line-length - bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; // direction indicates the swap direction: true for token0 -> token1, false for token1 -> token0 - address recipient = stream.readAddress(); - bool supportsFeeOnTransfer = stream.readUint8() > 0; // Any non-zero value enables fee-on-transfer handling - - if ( - pool == address(0) || - pool == IMPOSSIBLE_POOL_ADDRESS || - recipient == address(0) - ) revert InvalidCallData(); - - if (from == msg.sender) - IERC20(tokenIn).safeTransferFrom( - msg.sender, - address(this), - uint256(amountIn) - ); - - lastCalledPool = pool; - - // Handle fee-on-transfer tokens with special care: - // - These tokens modify balances during transfer (fees, rebasing, etc.) - // - newest pool of Algebra versions has built-in support via swapSupportingFeeOnInputTokens() - // - Unlike UniswapV3, Algebra can safely handle these non-standard tokens. - if (supportsFeeOnTransfer) { - // If the pool is not using a version of Algebra that supports this feature, the swap will revert - // when attempting to use swapSupportingFeeOnInputTokens(), indicating the token was incorrectly - // flagged as fee-on-transfer or the pool doesn't support such tokens. - IAlgebraPool(pool).swapSupportingFeeOnInputTokens( - address(this), - recipient, - direction, - int256(amountIn), - direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, - abi.encode(tokenIn) - ); - } else { - IAlgebraPool(pool).swap( - recipient, - direction, - int256(amountIn), - direction ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, - abi.encode(tokenIn) - ); - } - - if (lastCalledPool != IMPOSSIBLE_POOL_ADDRESS) - revert AlgebraSwapUnexpected(); - } -} - -/// @notice Minimal BentoBox vault interface. -/// @dev `token` is aliased as `address` from `IERC20` for simplicity. -interface IBentoBoxMinimal { - /// @notice Balance per ERC-20 token per account in shares. - function balanceOf(address, address) external view returns (uint256); - - /// @dev Helper function to represent an `amount` of `token` in shares. - /// @param token The ERC-20 token. - /// @param amount The `token` amount. - /// @param roundUp If the result `share` should be rounded up. - /// @return share The token amount represented in shares. - function toShare( - address token, - uint256 amount, - bool roundUp - ) external view returns (uint256 share); - - /// @dev Helper function to represent shares back into the `token` amount. - /// @param token The ERC-20 token. - /// @param share The amount of shares. - /// @param roundUp If the result should be rounded up. - /// @return amount The share amount back into native representation. - function toAmount( - address token, - uint256 share, - bool roundUp - ) external view returns (uint256 amount); - - /// @notice Registers this contract so that users can approve it for BentoBox. - function registerProtocol() external; - - /// @notice Deposit an amount of `token` represented in either `amount` or `share`. - /// @param token The ERC-20 token to deposit. - /// @param from which account to pull the tokens. - /// @param to which account to push the tokens. - /// @param amount Token amount in native representation to deposit. - /// @param share Token amount represented in shares to deposit. Takes precedence over `amount`. - /// @return amountOut The amount deposited. - /// @return shareOut The deposited amount represented in shares. - function deposit( - address token, - address from, - address to, - uint256 amount, - uint256 share - ) external payable returns (uint256 amountOut, uint256 shareOut); - - /// @notice Withdraws an amount of `token` from a user account. - /// @param token_ The ERC-20 token to withdraw. - /// @param from which user to pull the tokens. - /// @param to which user to push the tokens. - /// @param amount of tokens. Either one of `amount` or `share` needs to be supplied. - /// @param share Like above, but `share` takes precedence over `amount`. - function withdraw( - address token_, - address from, - address to, - uint256 amount, - uint256 share - ) external returns (uint256 amountOut, uint256 shareOut); - - /// @notice Transfer shares from a user account to another one. - /// @param token The ERC-20 token to transfer. - /// @param from which user to pull the tokens. - /// @param to which user to push the tokens. - /// @param share The amount of `token` in shares. - function transfer( - address token, - address from, - address to, - uint256 share - ) external; - - /// @dev Reads the Rebase `totals`from storage for a given token - function totals(address token) external view returns (Rebase memory total); - - function strategyData( - address token - ) external view returns (StrategyData memory total); - - /// @dev Approves users' BentoBox assets to a "master" contract. - function setMasterContractApproval( - address user, - address masterContract, - bool approved, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function harvest( - address token, - bool balance, - uint256 maxChangeAmount - ) external; -} - -interface ICurve { - function exchange( - int128 i, - int128 j, - uint256 dx, - // solhint-disable-next-line var-name-mixedcase - uint256 min_dy - ) external payable returns (uint256); -} - -interface ICurveLegacy { - function exchange( - int128 i, - int128 j, - uint256 dx, - // solhint-disable-next-line var-name-mixedcase - uint256 min_dy - ) external payable; -} - -/// @notice Trident pool interface. -interface IPool { - /// @notice Executes a swap from one token to another. - /// @dev The input tokens must've already been sent to the pool. - /// @param data ABI-encoded params that the pool requires. - /// @return finalAmountOut The amount of output tokens that were sent to the user. - function swap( - bytes calldata data - ) external returns (uint256 finalAmountOut); - - /// @notice Executes a swap from one token to another with a callback. - /// @dev This function allows borrowing the output tokens and sending the input tokens in the callback. - /// @param data ABI-encoded params that the pool requires. - /// @return finalAmountOut The amount of output tokens that were sent to the user. - function flashSwap( - bytes calldata data - ) external returns (uint256 finalAmountOut); - - /// @notice Mints liquidity tokens. - /// @param data ABI-encoded params that the pool requires. - /// @return liquidity The amount of liquidity tokens that were minted for the user. - function mint(bytes calldata data) external returns (uint256 liquidity); - - /// @notice Burns liquidity tokens. - /// @dev The input LP tokens must've already been sent to the pool. - /// @param data ABI-encoded params that the pool requires. - /// @return withdrawnAmounts The amount of various output tokens that were sent to the user. - function burn( - bytes calldata data - ) external returns (TokenAmount[] memory withdrawnAmounts); - - /// @notice Burns liquidity tokens for a single output token. - /// @dev The input LP tokens must've already been sent to the pool. - /// @param data ABI-encoded params that the pool requires. - /// @return amountOut The amount of output tokens that were sent to the user. - function burnSingle( - bytes calldata data - ) external returns (uint256 amountOut); - - /// @return A unique identifier for the pool type. - function poolIdentifier() external pure returns (bytes32); - - /// @return An array of tokens supported by the pool. - function getAssets() external view returns (address[] memory); - - /// @notice Simulates a trade and returns the expected output. - /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library. - /// @param data ABI-encoded params that the pool requires. - /// @return finalAmountOut The amount of output tokens that will be sent to the user if the trade is executed. - function getAmountOut( - bytes calldata data - ) external view returns (uint256 finalAmountOut); - - /// @notice Simulates a trade and returns the expected output. - /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library. - /// @param data ABI-encoded params that the pool requires. - /// @return finalAmountIn The amount of input tokens that are required from the user if the trade is executed. - function getAmountIn( - bytes calldata data - ) external view returns (uint256 finalAmountIn); - - /// @dev This event must be emitted on all swaps. - event Swap( - address indexed recipient, - address indexed tokenIn, - address indexed tokenOut, - uint256 amountIn, - uint256 amountOut - ); - - /// @dev This struct frames output tokens for burns. - struct TokenAmount { - address token; - uint256 amount; - } -} - -interface ITridentCLPool { - function token0() external returns (address); - - function token1() external returns (address); - - function swap( - address recipient, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bool unwrapBento, - bytes calldata data - ) external returns (int256 amount0, int256 amount1); -} - -interface IUniswapV2Pair { - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - event Transfer(address indexed from, address indexed to, uint256 value); - - function name() external pure returns (string memory); - - function symbol() external pure returns (string memory); - - function decimals() external pure returns (uint8); - - function totalSupply() external view returns (uint256); - - function balanceOf(address owner) external view returns (uint256); - - function allowance( - address owner, - address spender - ) external view returns (uint256); - - function approve(address spender, uint256 value) external returns (bool); - - function transfer(address to, uint256 value) external returns (bool); - - function transferFrom( - address from, - address to, - uint256 value - ) external returns (bool); - - function DOMAIN_SEPARATOR() external view returns (bytes32); - - function PERMIT_TYPEHASH() external pure returns (bytes32); - - function nonces(address owner) external view returns (uint256); - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - event Mint(address indexed sender, uint256 amount0, uint256 amount1); - event Burn( - address indexed sender, - uint256 amount0, - uint256 amount1, - address indexed to - ); - event Swap( - address indexed sender, - uint256 amount0In, - uint256 amount1In, - uint256 amount0Out, - uint256 amount1Out, - address indexed to - ); - event Sync(uint112 reserve0, uint112 reserve1); - - function MINIMUM_LIQUIDITY() external pure returns (uint256); - - function factory() external view returns (address); - - function token0() external view returns (address); - - function token1() external view returns (address); - - function getReserves() - external - view - returns ( - uint112 reserve0, - uint112 reserve1, - uint32 blockTimestampLast - ); - - function price0CumulativeLast() external view returns (uint256); - - function price1CumulativeLast() external view returns (uint256); - - function kLast() external view returns (uint256); - - function mint(address to) external returns (uint256 liquidity); - - function burn( - address to - ) external returns (uint256 amount0, uint256 amount1); - - function swap( - uint256 amount0Out, - uint256 amount1Out, - address to, - bytes calldata data - ) external; - - function skim(address to) external; - - function sync() external; - - function initialize(address, address) external; -} - -interface IUniswapV3Pool { - function token0() external returns (address); - - function token1() external returns (address); - - function swap( - address recipient, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bytes calldata data - ) external returns (int256 amount0, int256 amount1); -} - -interface IWETH { - function deposit() external payable; - - function transfer(address to, uint256 value) external returns (bool); - - function withdraw(uint256) external; -} - -/** @notice Simple read stream */ -library InputStream { - /** @notice Creates stream from data - * @param data data - */ - function createStream( - bytes memory data - ) internal pure returns (uint256 stream) { - assembly { - stream := mload(0x40) - mstore(0x40, add(stream, 64)) - mstore(stream, data) - let length := mload(data) - mstore(add(stream, 32), add(data, length)) - } - } - - /** @notice Checks if stream is not empty - * @param stream stream - */ - function isNotEmpty(uint256 stream) internal pure returns (bool) { - uint256 pos; - uint256 finish; - assembly { - pos := mload(stream) - finish := mload(add(stream, 32)) - } - return pos < finish; - } - - /** @notice Reads uint8 from the stream - * @param stream stream - */ - function readUint8(uint256 stream) internal pure returns (uint8 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 1) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint16 from the stream - * @param stream stream - */ - function readUint16(uint256 stream) internal pure returns (uint16 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 2) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint24 from the stream - * @param stream stream - */ - function readUint24(uint256 stream) internal pure returns (uint24 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 3) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint32 from the stream - * @param stream stream - */ - function readUint32(uint256 stream) internal pure returns (uint32 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 4) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads uint256 from the stream - * @param stream stream - */ - function readUint(uint256 stream) internal pure returns (uint256 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 32) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads bytes32 from the stream - * @param stream stream - */ - function readBytes32(uint256 stream) internal pure returns (bytes32 res) { - assembly { - let pos := mload(stream) - pos := add(pos, 32) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads address from the stream - * @param stream stream - */ - function readAddress(uint256 stream) internal pure returns (address res) { - assembly { - let pos := mload(stream) - pos := add(pos, 20) - res := mload(pos) - mstore(stream, pos) - } - } - - /** @notice Reads bytes from the stream - * @param stream stream - */ - function readBytes( - uint256 stream - ) internal pure returns (bytes memory res) { - assembly { - let pos := mload(stream) - res := add(pos, 32) - let length := mload(res) - mstore(stream, add(res, length)) - } - } -} - -library Approve { - /** - * @dev ERC20 approve that correct works with token.approve which returns bool or nothing (USDT for example) - * @param token The token targeted by the call. - * @param spender token spender - * @param amount token amount - */ - function approveStable( - IERC20 token, - address spender, - uint256 amount - ) internal returns (bool) { - (bool success, bytes memory data) = address(token).call( - abi.encodeWithSelector(token.approve.selector, spender, amount) - ); - return success && (data.length == 0 || abi.decode(data, (bool))); - } - - /** - * @dev ERC20 approve that correct works with token.approve which reverts if amount and - * current allowance are not zero simultaniously (USDT for example). - * In second case it tries to set allowance to 0, and then back to amount. - * @param token The token targeted by the call. - * @param spender token spender - * @param amount token amount - */ - function approveSafe( - IERC20 token, - address spender, - uint256 amount - ) internal returns (bool) { - return - approveStable(token, spender, amount) || - (approveStable(token, spender, 0) && - approveStable(token, spender, amount)); - } -} - -struct Rebase { - uint128 elastic; - uint128 base; -} - -struct StrategyData { - uint64 strategyStartDate; - uint64 targetPercentage; - uint128 balance; // the balance of the strategy that BentoBox thinks is in there -} - -/// @notice A rebasing library -library RebaseLibrary { - /// @notice Calculates the base value in relationship to `elastic` and `total`. - function toBase( - Rebase memory total, - uint256 elastic - ) internal pure returns (uint256 base) { - if (total.elastic == 0) { - base = elastic; - } else { - base = (elastic * total.base) / total.elastic; - } - } - - /// @notice Calculates the elastic value in relationship to `base` and `total`. - function toElastic( - Rebase memory total, - uint256 base - ) internal pure returns (uint256 elastic) { - if (total.base == 0) { - elastic = base; - } else { - elastic = (base * total.elastic) / total.base; - } - } -} From 289b8ce024ade9b8e0cfb714e0141bb0bc210423 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 01:33:57 +0200 Subject: [PATCH 102/220] FUNDS_IN_RECEIVER to address(1) --- src/Periphery/Lda/BaseRouteConstants.sol | 2 +- src/Periphery/Lda/Facets/CoreRouteFacet.sol | 4 ++-- src/Periphery/Lda/Facets/NativeWrapperFacet.sol | 2 +- src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 4 ++-- src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 2 +- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 2 +- test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Periphery/Lda/BaseRouteConstants.sol b/src/Periphery/Lda/BaseRouteConstants.sol index a2b9c70e2..a457825db 100644 --- a/src/Periphery/Lda/BaseRouteConstants.sol +++ b/src/Periphery/Lda/BaseRouteConstants.sol @@ -14,5 +14,5 @@ abstract contract BaseRouteConstants { /// receiving contract (e.g., from a previous swap in a multi-step route). /// This tells the facet to use its current token balance instead of /// pulling funds from an external address via `transferFrom`. - address internal constant FUNDS_IN_RECEIVER = address(0); + address internal constant FUNDS_IN_RECEIVER = address(1); } diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 64dd2064f..07d6e7f06 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -218,11 +218,11 @@ contract CoreRouteFacet is /// [3][n: uint8] then n legs, each: /// [share: uint16][len: uint16][data: bytes] /// total = address(this).balance (includes msg.value and any residual ETH) - /// from = address(this), tokenIn = INTERNAL_INPUT_SOURCE + /// from = address(this), tokenIn is native ETH (address(0)) /// /// 4. DispatchSinglePoolSwap: /// [4][token: address][len: uint16][data: bytes] - /// amountIn = 0 (pool sources tokens internally), from = INTERNAL_INPUT_SOURCE + /// amountIn = 0 (pool sources tokens internally), from = FUNDS_IN_RECEIVER /// /// 5. ApplyPermit: /// [5][value: uint256][deadline: uint256][v: uint8][r: bytes32][s: bytes32] diff --git a/src/Periphery/Lda/Facets/NativeWrapperFacet.sol b/src/Periphery/Lda/Facets/NativeWrapperFacet.sol index 299cf9ab5..fdf7b800c 100644 --- a/src/Periphery/Lda/Facets/NativeWrapperFacet.sol +++ b/src/Periphery/Lda/Facets/NativeWrapperFacet.sol @@ -19,7 +19,7 @@ contract NativeWrapperFacet is BaseRouteConstants { /// @dev Handles unwrapping WETH and sending native ETH to recipient /// @param swapData Encoded swap parameters [destinationAddress] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; - /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE + /// otherwise assumes tokens are at receiver address (FUNDS_IN_RECEIVER) /// @param tokenIn WETH token address /// @param amountIn Amount of WETH to unwrap function unwrapNative( diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index b5300586f..51a90ca98 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -19,7 +19,7 @@ contract SyncSwapV2Facet { /// @dev Handles both V1 (vault-based) and V2 (direct) pool swaps /// @param swapData Encoded swap parameters [pool, destinationAddress, withdrawMode, isV1Pool, vault] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; - /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE + /// otherwise assumes tokens are at receiver address /// @param tokenIn Input token address /// @param amountIn Amount of input tokens function swapSyncSwapV2( @@ -56,7 +56,7 @@ contract SyncSwapV2Facet { } else if (from == address(this)) { LibAsset.transferERC20(tokenIn, target, amountIn); } - // if from is not msg.sender or address(this), it must be INTERNAL_INPUT_SOURCE + // if from is not msg.sender or address(this), it must be FUNDS_IN_RECEIVER // which means tokens are already in the vault/pool, no transfer needed // SyncSwap V1 pools require tokens to be deposited into their vault first diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index f08f505fc..43d588ef3 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -28,7 +28,7 @@ contract UniV2StyleFacet is BaseRouteConstants { /// @dev Handles token transfers and calculates output amounts based on pool reserves /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, fee] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; - /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE + /// otherwise assumes tokens are at receiver address (FUNDS_IN_RECEIVER) /// @param tokenIn Input token address /// @param amountIn Amount of input tokens function swapUniV2( diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index 6fc3ee1fd..fa446c433 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -28,7 +28,7 @@ contract VelodromeV2Facet is BaseRouteConstants { /// @dev Handles token transfers and optional callbacks, with comprehensive safety checks /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, callback] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; - /// otherwise assumes tokens are at INTERNAL_INPUT_SOURCE + /// otherwise assumes tokens are at receiver address (FUNDS_IN_RECEIVER) /// @param tokenIn Input token address /// @param amountIn Amount of input tokens function swapVelodromeV2( diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 34d8d2785..47d056de5 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -650,7 +650,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { VELODROME_V2_FACTORY_REGISTRY ); - // if tokens come from the aggregator (address(liFiDEXAggregator)), use command code 1; otherwise, use 2. + // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. CommandType commandCode = params.from == address(ldaDiamond) ? CommandType.DistributeSelfERC20 : CommandType.DistributeUserERC20; From 7ebfdaeddc4b7357ddde57ddfd6c9372820bf57d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 13:08:42 +0200 Subject: [PATCH 103/220] Update EmergencyPauseFacet to include InvalidConfig error handling and increment version to 1.0.2 --- src/Security/EmergencyPauseFacet.sol | 5 +++-- .../EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Security/EmergencyPauseFacet.sol b/src/Security/EmergencyPauseFacet.sol index 90172422a..7d134100b 100644 --- a/src/Security/EmergencyPauseFacet.sol +++ b/src/Security/EmergencyPauseFacet.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { LibDiamond } from "../Libraries/LibDiamond.sol"; import { LibDiamondLoupe } from "../Libraries/LibDiamondLoupe.sol"; -import { UnAuthorized, InvalidCallData, DiamondIsPaused } from "../Errors/GenericErrors.sol"; +import { UnAuthorized, InvalidCallData, DiamondIsPaused, InvalidConfig } from "../Errors/GenericErrors.sol"; import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; @@ -11,7 +11,7 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; /// @author LI.FI (https://li.fi) /// @notice Allows a LI.FI-owned and -controlled, non-multisig "PauserWallet" to remove a facet /// or pause the diamond in case of emergency -/// @custom:version 1.0.1 +/// @custom:version 1.0.2 /// @dev Admin-Facet for emergency purposes only contract EmergencyPauseFacet { /// Events /// @@ -51,6 +51,7 @@ contract EmergencyPauseFacet { /// Constructor /// /// @param _pauserWallet The address of the wallet that can execute emergency facet removal actions constructor(address _pauserWallet) { + if (_pauserWallet == address(0)) revert InvalidConfig(); pauserWallet = _pauserWallet; _emergencyPauseFacetAddress = address(this); } diff --git a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol index 4afc8faf5..14e0df41b 100644 --- a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { TestBase } from "../../utils/TestBase.sol"; -import { OnlyContractOwner, InvalidCallData, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist } from "lifi/Errors/GenericErrors.sol"; +import { OnlyContractOwner, InvalidCallData, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist, InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; @@ -49,6 +49,11 @@ contract EmergencyPauseFacetLOCALTest is TestBase { ); } + function testRevert_WhenPauserWalletIsZeroAddress() public { + vm.expectRevert(InvalidConfig.selector); + new EmergencyPauseFacet(address(0)); + } + function test_PauserWalletCanPauseDiamond() public { vm.startPrank(USER_PAUSER); From 15c94b6f334bb538720ed78f9a3414d1b825431a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 21:02:16 +0200 Subject: [PATCH 104/220] updated paths --- conventions.md | 2 +- script/deploy/facets/LDA/DeployAlgebraFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployCurveFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol | 2 +- script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol | 2 +- script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol | 2 +- src/Periphery/Lda/Facets/AlgebraFacet.sol | 4 ++-- src/Periphery/Lda/Facets/CurveFacet.sol | 2 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 4 ++-- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 4 ++-- test/solidity/Periphery/GasZipPeriphery.t.sol | 8 ++++---- test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol | 2 +- .../solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol | 2 +- test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 2 +- test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/NativeWrapperFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol | 2 +- test/solidity/utils/TestBase.sol | 2 +- 32 files changed, 39 insertions(+), 39 deletions(-) diff --git a/conventions.md b/conventions.md index 4a1b5ee11..1e490e60d 100644 --- a/conventions.md +++ b/conventions.md @@ -154,7 +154,7 @@ We use Foundry as our primary development and testing framework. Foundry provide - **Error Handling:** - **Generic errors** must be defined in `src/Errors/GenericErrors.sol` - - LDA-specific errors should be defined in `src/Periphery/LDA/Errors/Errors.sol` + - LDA-specific errors should be defined in `src/Periphery/Lda/Errors/Errors.sol` - Use for common validation errors that apply across multiple contracts - When adding new generic errors, increment the version in `@custom:version` comment - Examples: `InvalidAmount()`, `InvalidCallData()`, `UnAuthorized()` diff --git a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol index 40fedd9f1..ff51b4a91 100644 --- a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol +++ b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; +import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("AlgebraFacet") {} diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index 352d08355..f28a906fd 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CoreRouteFacet") {} diff --git a/script/deploy/facets/LDA/DeployCurveFacet.s.sol b/script/deploy/facets/LDA/DeployCurveFacet.s.sol index 18b3c43ce..1e5dead12 100644 --- a/script/deploy/facets/LDA/DeployCurveFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCurveFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; +import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CurveFacet") {} diff --git a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol index 6a295772b..55055a52d 100644 --- a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol +++ b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; +import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("IzumiV3Facet") {} diff --git a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol index 6d10e3c4b..328ea8162 100644 --- a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("SyncSwapV2Facet") {} diff --git a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol index b08a859c7..6347240bc 100644 --- a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("UniV2StyleFacet") {} diff --git a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol index fc825770f..b69085833 100644 --- a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("UniV3StyleFacet") {} diff --git a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol index 1c1df461e..af8c3817f 100644 --- a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("VelodromeV2Facet") {} diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 86e8a40a1..752e2a388 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -8,8 +8,8 @@ import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticato import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/Lda/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title AlgebraFacet diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index 25574a5e1..583796303 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -7,7 +7,7 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ICurve } from "lifi/Interfaces/ICurve.sol"; import { ICurveV2 } from "lifi/Interfaces/ICurveV2.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "lifi/Periphery/LDA/BaseRouteConstants.sol"; +import { BaseRouteConstants } from "lifi/Periphery/Lda/BaseRouteConstants.sol"; /// @title CurveFacet /// @author LI.FI (https://li.fi) diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index a74694b46..44f840319 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -6,8 +6,8 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/Lda/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title IzumiV3Facet diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index ba932a730..426f7684c 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -7,8 +7,8 @@ import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticato import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/Lda/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title UniV3StyleFacet diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 37eec5736..d61f66fae 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,10 +10,10 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LDADiamondTest } from "./LDA/utils/LdaDiamondTest.sol"; -import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; -import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; -import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; +import { LDADiamondTest } from "./Lda/utils/LdaDiamondTest.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/Lda/Facets/NativeWrapperFacet.sol"; // Stub GenericSwapFacet Contract contract TestGasZipPeriphery is GasZipPeriphery { diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index 4e8636126..c9435fc4a 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; import { LDADiamondTest } from "./utils/LDADiamondTest.sol"; diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index 104953506..79aae6efe 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index 73f125b0c..74bf64029 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 284766297..d7c133b6e 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index f4ea33296..bbb86f683 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -6,9 +6,9 @@ import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; +import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 779a00e51..544f47a0a 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; -import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 622e5b023..402edc751 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; -import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; +import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title CurveFacetTest diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 6f713b4ec..635487e9e 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title EnosysDEXV3FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index a5d1847ac..212a025c2 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title HyperswapV3FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 9d1e79452..59064654b 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; +import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 08c577352..2c42ebb65 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title LaminarV3FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol index 871f4b5e6..8389e617c 100644 --- a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IWETH } from "lifi/Interfaces/IWETH.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; -import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/Lda/Facets/NativeWrapperFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title NativeWrapperFacetTest diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 2ac1c634b..541cacbd0 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 2a706d1d7..126b8d5fb 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; -import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title SyncSwapV2FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 47d056de5..b542e259f 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -6,7 +6,7 @@ import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index c48a749bc..d105893d3 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title XSwapV3FacetTest diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index a5f8632a4..b74d3cd6e 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; +import { LDADiamond } from "lifi/Periphery/Lda/LDADiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index dd8daee91..c94d35607 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -15,7 +15,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LDADiamondTest } from "../Periphery/LDA/utils/LdaDiamondTest.sol"; +import { LDADiamondTest } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; using stdJson for string; From 61c0c977a3996b2fdb353780615f34f36cf7be9b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 21:54:59 +0200 Subject: [PATCH 105/220] Update LDA diamond test setup with new owner and pauser addresses, and add PeripheryRegistryFacet integration --- .../Periphery/Lda/Facets/CoreRouteFacet.t.sol | 2 +- .../Periphery/Lda/utils/LdaDiamondTest.sol | 24 +++++++++++++++---- .../utils/TestBaseRandomConstants.sol | 2 ++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 544f47a0a..97eb3418e 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -100,7 +100,7 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { // Test that owner is set correctly assertEq( coreRouteFacet.owner(), - USER_DIAMOND_OWNER, + USER_LDA_DIAMOND_OWNER, "owner not set correctly" ); } diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index b74d3cd6e..db7a97827 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -5,6 +5,7 @@ import { LDADiamond } from "lifi/Periphery/Lda/LDADiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; @@ -21,7 +22,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. function setUp() public virtual { - ldaDiamond = createLDADiamond(USER_DIAMOND_OWNER, USER_PAUSER); + ldaDiamond = createLDADiamond(USER_LDA_DIAMOND_OWNER, USER_LDA_PAUSER); } /// @notice Creates an LDA diamond and wires up Loupe, Ownership and EmergencyPause facets. @@ -43,6 +44,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { _diamondOwner, address(diamondCut) ); + PeripheryRegistryFacet periphery = new PeripheryRegistryFacet(); // Add Diamond Loupe _addDiamondLoupeSelectors(address(diamondLoupe)); @@ -50,10 +52,24 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { // Add Ownership _addOwnershipSelectors(address(ownership)); - // Add PeripheryRegistry TODO?!?!? + // Add PeripheryRegistry + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = PeripheryRegistryFacet + .registerPeripheryContract + .selector; + functionSelectors[1] = PeripheryRegistryFacet + .getPeripheryContract + .selector; + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(periphery), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); - // Add EmergencyPause (removeFacet, pause/unpause) - bytes4[] memory functionSelectors = new bytes4[](3); + // Add EmergencyPause + functionSelectors = new bytes4[](3); functionSelectors[0] = emergencyPause.removeFacet.selector; functionSelectors[1] = emergencyPause.pauseDiamond.selector; functionSelectors[2] = emergencyPause.unpauseDiamond.selector; diff --git a/test/solidity/utils/TestBaseRandomConstants.sol b/test/solidity/utils/TestBaseRandomConstants.sol index 4fad2acdd..1599fc789 100644 --- a/test/solidity/utils/TestBaseRandomConstants.sol +++ b/test/solidity/utils/TestBaseRandomConstants.sol @@ -8,4 +8,6 @@ abstract contract TestBaseRandomConstants { address internal constant USER_PAUSER = address(0xdeadbeef); address internal constant USER_DIAMOND_OWNER = 0x5042255A3F3FD7727e419CeA387cAFDfad3C3aF8; + address internal constant USER_LDA_DIAMOND_OWNER = address(0x123457); + address internal constant USER_LDA_PAUSER = address(0x123458); } From 38c6a387bbfe095b07dbab140d5de4e52249f235 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 22:13:03 +0200 Subject: [PATCH 106/220] Refactor swap amount calculation in BaseUniV2StyleDexFacet and BaseUniV3StyleDexFacet tests to use params.amountIn directly, removing unnecessary increment logic. --- test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 7 ++----- test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 9 +++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index 74bf64029..84d537a01 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -141,10 +141,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { function _executeUniV2StyleSwapAuto( UniV2AutoSwapParams memory params ) internal { - uint256 amountIn = params.commandType == - CommandType.DistributeSelfERC20 - ? params.amountIn + 1 - : params.amountIn; + uint256 amountIn = params.amountIn; // Fund the appropriate account if (params.commandType == CommandType.DistributeSelfERC20) { @@ -324,7 +321,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { _executeUniV2StyleSwapAuto( UniV2AutoSwapParams({ commandType: CommandType.DistributeSelfERC20, - amountIn: _getDefaultAmountForTokenIn() - 1 + amountIn: _getDefaultAmountForTokenIn() }) ); } diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index d7c133b6e..a14f14a2e 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/lda/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; @@ -135,10 +135,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { function _executeUniV3StyleSwapAuto( UniV3AutoSwapParams memory params ) internal { - uint256 amountIn = params.commandType == - CommandType.DistributeSelfERC20 - ? params.amountIn + 1 - : params.amountIn; + uint256 amountIn = params.amountIn; // Fund the appropriate account if (params.commandType == CommandType.DistributeSelfERC20) { @@ -235,7 +232,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { _executeUniV3StyleSwapAuto( UniV3AutoSwapParams({ commandType: CommandType.DistributeSelfERC20, - amountIn: _getDefaultAmountForTokenIn() - 1 + amountIn: _getDefaultAmountForTokenIn() }) ); } From 9035ee0458883236c00501cd5760660d70984154 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 22:23:35 +0200 Subject: [PATCH 107/220] Update AlgebraFacet test to reflect changes in aggregator address from `coreRouteFacet` to `ldaDiamond` for swap functionality. --- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index bbb86f683..c47d168ae 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -142,12 +142,12 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { // ==== Test Cases ==== /// @notice Aggregator-funded swap on Algebra using funds transferred from a whale. - /// @dev Transfers APE_ETH to `coreRouteFacet` (aggregator) and executes swap to `USER_SENDER`. + /// @dev Transfers APE_ETH to `ldaDiamond` (aggregator) and executes swap to `USER_SENDER`. function test_CanSwap_FromDexAggregator() public override { // Fund LDA from whale address vm.prank(RANDOM_APE_ETH_HOLDER_APECHAIN); IERC20(tokenIn).transfer( - address(coreRouteFacet), + address(ldaDiamond), _getDefaultAmountForTokenIn() ); @@ -155,7 +155,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { _testSwap( AlgebraSwapTestParams({ - from: address(coreRouteFacet), + from: address(ldaDiamond), destinationAddress: address(USER_SENDER), tokenIn: address(tokenIn), amountIn: _getDefaultAmountForTokenIn() - 1, From 0cf2654d64cc5b2eb293bccecdfde850921d9544 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 22:23:48 +0200 Subject: [PATCH 108/220] Update sender logic in BaseUniV2StyleDexFacet and BaseUniV3StyleDexFacet tests to conditionally use ldaDiamond address for DistributeSelfERC20 command type. --- test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 8 ++++++-- test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index 84d537a01..f92f4562d 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -169,7 +169,9 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { tokenOut: address(tokenOut), amountIn: amountIn, minOut: 0, - sender: USER_SENDER, + sender: params.commandType == CommandType.DistributeSelfERC20 + ? address(ldaDiamond) + : USER_SENDER, destinationAddress: USER_SENDER, commandType: params.commandType }), @@ -182,7 +184,9 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { tokenOut: address(tokenOut), amountIn: amountIn, minOut: 0, - sender: USER_SENDER, + sender: params.commandType == CommandType.DistributeSelfERC20 + ? address(ldaDiamond) + : USER_SENDER, destinationAddress: USER_SENDER, commandType: params.commandType }), diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index a14f14a2e..6c70d4c51 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -162,7 +162,9 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { tokenOut: address(tokenOut), amountIn: amountIn, minOut: 0, - sender: USER_SENDER, + sender: params.commandType == CommandType.DistributeSelfERC20 + ? address(ldaDiamond) + : USER_SENDER, destinationAddress: USER_SENDER, commandType: params.commandType }), @@ -175,7 +177,9 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { tokenOut: address(tokenOut), amountIn: amountIn, minOut: 0, - sender: USER_SENDER, + sender: params.commandType == CommandType.DistributeSelfERC20 + ? address(ldaDiamond) + : USER_SENDER, destinationAddress: USER_SENDER, commandType: params.commandType }), From 067e2524feb4e86af6e88c030d6f9fa4c688f7e0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 22:39:25 +0200 Subject: [PATCH 109/220] Update sender address in CurveFacet, IzumiV3Facet, NativeWrapperFacet, and SyncSwapV2Facet tests to use ldaDiamond for DistributeSelfERC20 command type. --- test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 402edc751..076fab183 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -117,7 +117,7 @@ contract CurveFacetTest is BaseDEXFacetTest { tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain minOut: 0, - sender: USER_SENDER, + sender: address(ldaDiamond), destinationAddress: USER_SENDER, commandType: CommandType.DistributeSelfERC20 }), diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 59064654b..dd45a8fb4 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -164,7 +164,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // -1 for undrain protection minOut: 0, - sender: address(coreRouteFacet), + sender: address(ldaDiamond), destinationAddress: USER_SENDER, commandType: CommandType.DistributeSelfERC20 }), diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol index 8389e617c..db8a0e709 100644 --- a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol @@ -126,7 +126,7 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { tokenOut: address(0), // Native ETH amountIn: amountIn, minOut: amountIn, // Expect 1:1 unwrapping - sender: USER_SENDER, + sender: address(ldaDiamond), destinationAddress: USER_RECEIVER, commandType: CommandType.DistributeSelfERC20 }); diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 126b8d5fb..5a5090c5b 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -148,7 +148,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain minOut: 0, - sender: USER_SENDER, + sender: address(ldaDiamond), destinationAddress: USER_SENDER, commandType: CommandType.DistributeSelfERC20 }), @@ -185,7 +185,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { tokenOut: address(tokenMid), amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain minOut: 0, - sender: USER_SENDER, + sender: address(ldaDiamond), destinationAddress: USER_SENDER, commandType: CommandType.DistributeSelfERC20 }), @@ -245,7 +245,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { tokenOut: address(tokenOut), amountIn: 0, // Not used in DispatchSinglePoolSwap minOut: 0, - sender: USER_SENDER, + sender: address(ldaDiamond), destinationAddress: USER_SENDER, commandType: CommandType.DistributeSelfERC20 }); From 706224762ee3ec5eda28c5d1a427ef61d9508b35 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 22:40:14 +0200 Subject: [PATCH 110/220] Update minOutput calculation in AlgebraFacet test to prevent underflow by ensuring it does not go below zero. --- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index c47d168ae..35d81c079 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -702,7 +702,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { ); // Add 1 wei slippage buffer - uint256 minOutput = expectedOutput - 1; + uint256 minOutput = expectedOutput > 0 ? expectedOutput - 1 : 0; // if tokens come from the aggregator (address(ldaDiamond)), use command code 1; otherwise, use 2. CommandType commandCode = params.from == address(ldaDiamond) From 486f651d216aea23a525a4f5edc57cb71f2ed10a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 23:09:19 +0200 Subject: [PATCH 111/220] Increment version number in LibAsset library from 2.1.2 to 2.1.3 --- src/Libraries/LibAsset.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/LibAsset.sol b/src/Libraries/LibAsset.sol index 4754bb2a7..f50553313 100644 --- a/src/Libraries/LibAsset.sol +++ b/src/Libraries/LibAsset.sol @@ -8,7 +8,7 @@ import { InvalidReceiver, NullAddrIsNotAValidSpender, InvalidAmount, NullAddrIsN /// @title LibAsset /// @author LI.FI (https://li.fi) -/// @custom:version 2.1.2 +/// @custom:version 2.1.3 /// @notice This library contains helpers for dealing with onchain transfers /// of assets, including accounting for the native asset `assetId` /// conventions and any noncompliant ERC20 transfers From 642b0e6b0499cb9d9d395e7e4d72e1e1146f7ebe Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 28 Aug 2025 23:12:16 +0200 Subject: [PATCH 112/220] Update sender address in CurveFacet and IzumiV3Facet tests to use ldaDiamond for DistributeSelfERC20 command type. --- test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 076fab183..e2f6ef2e6 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -277,7 +277,7 @@ contract CurveFacetTest is BaseDEXFacetTest { tokenOut: address(tokenOut), // USDT amountIn: amountIn - 1, // follow undrain convention minOut: 0, - sender: USER_SENDER, + sender: address(ldaDiamond), destinationAddress: USER_SENDER, commandType: CommandType.DistributeSelfERC20 }), diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index dd45a8fb4..62f6a695a 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -220,7 +220,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { tokenOut: address(tokenOut), amountIn: 0, // Will be determined by first swap minOut: 0, - sender: USER_SENDER, + sender: address(ldaDiamond), destinationAddress: USER_SENDER, commandType: CommandType.DistributeSelfERC20 }); From 61405740ecd1c13fb39a98e7b1f624a7e9f275bd Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 00:11:07 +0200 Subject: [PATCH 113/220] Refactor interface documentation comments in IAlgebraFactory, IAlgebraPool, and IAlgebraRouter for consistency; add new error type WrongPoolReserves in Errors.sol; update various tests to use _buildRouteAndExecuteAndVerifySwap for enhanced verification. --- src/Interfaces/IAlgebraFactory.sol | 12 +- src/Interfaces/IAlgebraPool.sol | 14 +- src/Interfaces/IAlgebraRouter.sol | 12 +- src/Periphery/Lda/Errors/Errors.sol | 1 + src/Periphery/Lda/Facets/CoreRouteFacet.sol | 57 ++++--- src/Periphery/Lda/Facets/SyncSwapV2Facet.sol | 11 +- src/Periphery/Lda/Facets/UniV2StyleFacet.sol | 5 +- src/Periphery/Lda/Facets/VelodromeV2Facet.sol | 5 +- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 152 ++++++++++++------ .../solidity/Periphery/Lda/BaseDexFacet.t.sol | 25 ++- .../Lda/BaseUniV2StyleDexFacet.t.sol | 6 +- .../Lda/BaseUniV3StyleDexFacet.t.sol | 2 +- .../Periphery/Lda/Facets/AlgebraFacet.t.sol | 12 +- .../Periphery/Lda/Facets/CurveFacet.t.sol | 28 ++-- .../Periphery/Lda/Facets/IzumiV3Facet.t.sol | 8 +- .../Lda/Facets/NativeWrapperFacet.t.sol | 4 +- .../Periphery/Lda/Facets/PancakeV2.t.sol | 3 +- .../Periphery/Lda/Facets/RabbitSwapV3.t.sol | 4 +- .../Lda/Facets/SyncSwapV2Facet.t.sol | 20 +-- .../Lda/Facets/VelodromeV2Facet.t.sol | 13 +- 20 files changed, 224 insertions(+), 170 deletions(-) diff --git a/src/Interfaces/IAlgebraFactory.sol b/src/Interfaces/IAlgebraFactory.sol index 3ea12d70f..4a2f53a07 100644 --- a/src/Interfaces/IAlgebraFactory.sol +++ b/src/Interfaces/IAlgebraFactory.sol @@ -6,13 +6,11 @@ pragma solidity ^0.8.17; /// @author LI.FI (https://li.fi) /// @custom:version 1.0.0 interface IAlgebraFactory { - /** - * @notice Creates a pool for the given two tokens - * @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0 - * @param tokenA The contract address of either token0 or token1 - * @param tokenB The contract address of the other token - * @return pool The address of the newly created pool - */ + /// @notice Creates a pool for the given two tokens + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0 + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @return pool The address of the newly created pool function createPool( address tokenA, address tokenB diff --git a/src/Interfaces/IAlgebraPool.sol b/src/Interfaces/IAlgebraPool.sol index 7cd38a7a0..25d1cc39c 100644 --- a/src/Interfaces/IAlgebraPool.sol +++ b/src/Interfaces/IAlgebraPool.sol @@ -6,17 +6,13 @@ pragma solidity ^0.8.17; /// @author LI.FI (https://li.fi) /// @custom:version 1.0.0 interface IAlgebraPool { - /** - * @notice The first of the two tokens of the pool, sorted by address - * @return The token contract address - */ + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address function token0() external view returns (address); - /** - * @notice Sets the initial price for the pool - * @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value - * @param price the initial sqrt price of the pool as a Q64.96 - */ + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param price the initial sqrt price of the pool as a Q64.96 function initialize(uint160 price) external; /// @notice Swaps tokens supporting fee on input tokens diff --git a/src/Interfaces/IAlgebraRouter.sol b/src/Interfaces/IAlgebraRouter.sol index 415017df7..237167d2d 100644 --- a/src/Interfaces/IAlgebraRouter.sol +++ b/src/Interfaces/IAlgebraRouter.sol @@ -5,13 +5,11 @@ pragma solidity ^0.8.17; /// @author LI.FI (https://li.fi) /// @custom:version 1.0.0 interface IAlgebraRouter { - /** - * @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist - * @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order - * @param tokenA The contract address of either token0 or token1 - * @param tokenB The contract address of the other token - * @return pool The pool address - */ + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @return pool The pool address function poolByPair( address tokenA, address tokenB diff --git a/src/Periphery/Lda/Errors/Errors.sol b/src/Periphery/Lda/Errors/Errors.sol index 058f279fc..409873ec4 100644 --- a/src/Periphery/Lda/Errors/Errors.sol +++ b/src/Periphery/Lda/Errors/Errors.sol @@ -6,3 +6,4 @@ pragma solidity ^0.8.17; error SwapCallbackNotExecuted(); +error WrongPoolReserves(); diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/Lda/Facets/CoreRouteFacet.sol index 07d6e7f06..2e3a56dd3 100644 --- a/src/Periphery/Lda/Facets/CoreRouteFacet.sol +++ b/src/Periphery/Lda/Facets/CoreRouteFacet.sol @@ -226,7 +226,7 @@ contract CoreRouteFacet is /// /// 5. ApplyPermit: /// [5][value: uint256][deadline: uint256][v: uint8][r: bytes32][s: bytes32] - /// Calls permit on tokenIn for msg.sender → address(this). No swap occurs. + /// Calls permit on tokenIn for msg.sender -> address(this). No swap occurs. /// /// Leg data encoding: /// Each leg's data field contains [selector (4 bytes) | payload (bytes)]. @@ -264,8 +264,7 @@ contract CoreRouteFacet is /// @param tokenIn The input token address /// @param declaredAmountIn The declared input amount /// @param route The encoded route data - /// @return realAmountIn The actual amount used in the first hop. For opcode 1: contract balance minus 1, - /// for opcode 3: contract's ETH balance, for opcode 2: equals declaredAmountIn + /// @return realAmountIn The actual amount used in the first hop function _runRoute( address tokenIn, uint256 declaredAmountIn, @@ -368,37 +367,50 @@ contract CoreRouteFacet is } /// @notice Distributes tokens across multiple pools based on share ratios + /// @dev This function implements proportional distribution where: + /// - Each leg gets a percentage of the total based on its share value + /// - Shares are encoded as uint16 where 65535 (type(uint16).max) = 100% + /// - The last leg gets all remaining tokens to handle rounding errors + /// - Example: 60/40 split would use shares [39321, 26214] since: + /// 39321/65535 ≈ 0.6 and 26214/65535 ≈ 0.4 /// @param cur The current position in the byte stream /// @param from The source address for tokens /// @param tokenIn The token being distributed - /// @param total The total amount to distribute + /// @param total The total amount to distribute across all legs function _distributeAndSwap( uint256 cur, address from, address tokenIn, uint256 total ) private { + // Read number of swap legs from the stream uint8 n = cur.readUint8(); unchecked { uint256 remaining = total; for (uint256 i = 0; i < n; ++i) { + // Read the proportional share for this leg (0-65535 scale) uint16 share = cur.readUint16(); - uint256 amt = i == n - 1 - ? remaining - : (total * share) / type(uint16).max; // compute vs original total - if (amt > remaining) amt = remaining; - remaining -= amt; - _dispatchSwap(cur, from, tokenIn, amt); + + // Calculate amount for this leg: + // - For intermediate legs: proportional amount based on share + // - For last leg: all remaining tokens (handles rounding dust) + uint256 legAmount = i == n - 1 + ? remaining // Last leg gets all remaining to avoid dust + : (total * share) / type(uint16).max; // Proportional calculation + + // Safety check: never exceed what's left to distribute + if (legAmount > remaining) legAmount = remaining; + + // Update remaining balance for next iteration + remaining -= legAmount; + + // Execute the swap for this leg with calculated amount + _dispatchSwap(cur, from, tokenIn, legAmount); } } } /// @notice Dispatches a swap call to the appropriate DEX facet - /// @dev Uses direct selector dispatch with optimized calldata construction - /// Assembly is used to: - /// - Build calldata for delegatecall without extra memory copies/abi.encode overhead - /// - Reuse the payload already read from the stream without re-encoding - /// - Keep memory usage predictable and cheap across arbitrary payload sizes /// @param cur The current position in the byte stream /// @param from The source address for tokens /// @param tokenIn The input token address @@ -415,8 +427,9 @@ contract CoreRouteFacet is // Extract function selector (first 4 bytes of data) bytes4 selector = _readSelector(data); - // Compute payload length directly (data = [len | selector(4) | payload]) - + // Calculate payload length by subtracting selector size from total data length + // data memory layout: [length][selector(4 bytes)][payload...] + // mload(data) reads the length field, then we subtract 4 bytes for the selector uint256 payloadLen; assembly { payloadLen := sub(mload(data), 4) @@ -535,19 +548,17 @@ contract CoreRouteFacet is } /// @notice Creates a new bytes view that aliases blob without the first 4 bytes (selector) - /// @dev Assembly used to: - /// - Point into the original bytes (no allocation/copy) - /// - Rewrite the length to exclude the 4-byte selector - /// This is safe here because we treat the result as a read-only slice. /// @param blob The original calldata bytes /// @return payload The calldata without selector function _payloadFrom( bytes memory blob ) private pure returns (bytes memory payload) { assembly { - // payload points at blob + 4, sharing the same underlying buffer + // Point payload 4 bytes into blob's data section (skipping selector) + // Memory layout: [length][data...] -> payload points to [data+4...] payload := add(blob, 4) - // set payload.length = blob.length - 4 + // Update length field: original_length - 4 (selector size) + // This creates a valid bytes object that references blob's memory mstore(payload, sub(mload(blob), 4)) } } diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol index 51a90ca98..8ced77642 100644 --- a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol @@ -65,12 +65,11 @@ contract SyncSwapV2Facet { ISyncSwapVault(target).deposit(tokenIn, pool); } - bytes memory data = abi.encode( - tokenIn, - destinationAddress, - withdrawMode + ISyncSwapPool(pool).swap( + abi.encode(tokenIn, destinationAddress, withdrawMode), + from, + address(0), + new bytes(0) ); - - ISyncSwapPool(pool).swap(data, from, address(0), new bytes(0)); } } diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol index 43d588ef3..1ab5403c0 100644 --- a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV2StyleFacet.sol @@ -7,6 +7,7 @@ import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; +import { WrongPoolReserves } from "../Errors/Errors.sol"; /// @title UniV2StyleFacet /// @author LI.FI (https://li.fi) @@ -19,10 +20,6 @@ contract UniV2StyleFacet is BaseRouteConstants { /// @dev Fee denominator for UniV2-style pools (100% = 1_000_000) uint256 private constant FEE_DENOMINATOR = 1_000_000; - // ==== Errors ==== - /// @dev Thrown when pool reserves are zero, indicating an invalid pool state - error WrongPoolReserves(); - // ==== External Functions ==== /// @notice Executes a UniswapV2-style swap /// @dev Handles token transfers and calculates output amounts based on pool reserves diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol index fa446c433..45839ce05 100644 --- a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/Lda/Facets/VelodromeV2Facet.sol @@ -7,6 +7,7 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; +import { WrongPoolReserves } from "../Errors/Errors.sol"; /// @title VelodromeV2Facet /// @author LI.FI (https://li.fi) @@ -19,10 +20,6 @@ contract VelodromeV2Facet is BaseRouteConstants { /// @dev Flag to enable post-swap callback with flashloan data uint8 internal constant CALLBACK_ENABLED = 1; - // ==== Errors ==== - /// @dev Thrown when pool reserves are zero, indicating an invalid pool state - error WrongPoolReserves(); - // ==== External Functions ==== /// @notice Performs a swap through VelodromeV2 pools /// @dev Handles token transfers and optional callbacks, with comprehensive safety checks diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index c9435fc4a..e9dcfbcd2 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -46,7 +46,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { bool checkData; bytes32 eventSelector; // The event selector (keccak256 hash of the event signature) bytes[] eventParams; // The event parameters, each encoded separately - uint8[] indexedParamIndices; // indices of params that are indexed (→ topics 1..3) + uint8[] indexedParamIndices; // indices of params that are indexed (topics 1..3) } /// @notice Tuning for verifying the core `Route` event. @@ -181,13 +181,19 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { } } - /// @notice Executes a built route and verifies balances and events. - /// @param params Swap params; if DistributeSelfERC20, measures in/out at the diamond. - /// @param route Pre-built route bytes (single or multi-hop). - /// @param additionalEvents Additional external events to expect. - /// @param isFeeOnTransferToken Whether tokenIn is fee-on-transfer (tolerates off-by-1 spent). - /// @param routeEventVerification Route event check configuration (exact out optional). - /// @dev Approves tokenIn if not aggregator-funded. Emits and verifies Route event. + /// @notice Executes a built route and verifies balances and events with full verification options + /// @param params Swap parameters including token addresses, amounts, and command type + /// @param route Pre-built route bytes (single or multi-hop) + /// @param additionalEvents Additional external events to expect during execution + /// @param isFeeOnTransferToken Whether tokenIn is fee-on-transfer token (tolerates off-by-1 spent) + /// @param routeEventVerification Route event check configuration for exact output and data validation + /// @dev Handles the following: + /// - Approves tokenIn if not aggregator-funded + /// - Tracks balances before/after for both input and output tokens + /// - Emits and verifies Route event with specified verification options + /// - Supports native token transfers via msg.value + /// - Validates token spent matches expected amount (with fee-on-transfer tolerance) + /// - Ensures positive output amount received function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -294,7 +300,15 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { assertGt(outAfter - outBefore, 0, "Should receive tokens"); } - /// @notice Convenience overload for `_executeAndVerifySwap` without exact-out check. + /// @notice Executes a built route with basic verification and no exact output check + /// @param params Swap parameters including token addresses, amounts, and command type + /// @param route Pre-built route bytes (single or multi-hop) + /// @param additionalEvents Additional external events to expect during execution + /// @param isFeeOnTransferToken Whether tokenIn is fee-on-transfer token (tolerates off-by-1 spent) + /// @dev Convenience overload that: + /// - Sets expectedExactOut to 0 (no exact output verification) + /// - Disables Route event data validation + /// - Maintains all other verification steps from the full version function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -310,7 +324,15 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { ); } - /// @notice Convenience overload for `_executeAndVerifySwap` with only params and route. + /// @notice Executes a built route with minimal verification + /// @param params Swap parameters including token addresses, amounts, and command type + /// @param route Pre-built route bytes (single or multi-hop) + /// @dev Simplest overload that: + /// - Assumes non-fee-on-transfer token + /// - Expects no additional events + /// - Disables exact output verification + /// - Disables Route event data validation + /// - Useful for basic swap tests without complex verification needs function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route @@ -324,7 +346,16 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { ); } - /// @notice Convenience overload for `_executeAndVerifySwap` with fee-on-transfer toggle. + /// @notice Executes a built route with fee-on-transfer support + /// @param params Swap parameters including token addresses, amounts, and command type + /// @param route Pre-built route bytes (single or multi-hop) + /// @param isFeeOnTransferToken Whether tokenIn is fee-on-transfer token (tolerates off-by-1 spent) + /// @dev Convenience overload that: + /// - Supports fee-on-transfer tokens via tolerance parameter + /// - Expects no additional events + /// - Disables exact output verification + /// - Disables Route event data validation + /// - Useful for testing fee-on-transfer tokens without complex event verification function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -339,13 +370,15 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { ); } - /// @notice Executes route expecting a specific revert error selector. - /// @param params Swap params; for aggregator funds, the helper deliberately uses amountIn-1 to trigger errors. - /// @param route Pre-built route bytes. - /// @param expectedRevert Error selector expected from `processRoute`. - /// @dev Example: - /// vm.expectRevert(Errors.SwapCallbackNotExecuted.selector); - /// _executeAndVerifySwap(params, route, Errors.SwapCallbackNotExecuted.selector); + /// @notice Executes a route expecting a specific revert error + /// @param params Swap parameters including token addresses, amounts, and command type + /// @param route Pre-built route bytes (single or multi-hop) + /// @param expectedRevert Error selector that should be thrown by processRoute + /// @dev Special overload for testing failure cases: + /// - For aggregator funds (DistributeSelfERC20), uses amountIn-1 to trigger errors + /// - For user funds, approves full amountIn but sends amountIn-1 + /// - Sets minOut to 0 for testing focus + /// - Verifies exact error selector match function _executeAndVerifySwap( SwapTestParams memory params, bytes memory route, @@ -374,14 +407,18 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { ); } - /// @notice Helper that builds route and executes swap in one call, with extended verification options. - /// @param params SwapTestParams for building and executing. - /// @param swapData DEX-specific swap data to pack. - /// @param expectedEvents Additional events to expect. - /// @param expectRevert Treats token as fee-on-transfer to adjust spent checking if true. - /// @param verification Route event verification configuration. - /// @dev Primarily used by complex tests to keep scenario assembly terse. - function _buildRouteAndExecuteSwap( + /// @notice Builds route and executes swap with full verification options in a single call + /// @param params SwapTestParams for building and executing the swap + /// @param swapData DEX-specific swap data to pack into the route + /// @param expectedEvents Additional events to expect during execution + /// @param expectRevert Whether to treat tokenIn as fee-on-transfer for spent checking + /// @param verification Route event verification configuration + /// @dev Comprehensive helper that: + /// - Builds route using _buildBaseRoute + /// - Executes swap with full verification options + /// - Supports all verification features: events, fee-on-transfer, exact output + /// - Primarily used by complex test scenarios to keep code concise + function _buildRouteAndExecuteAndVerifySwap( SwapTestParams memory params, bytes memory swapData, ExpectedEvent[] memory expectedEvents, @@ -398,12 +435,22 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { ); } - /// @notice Overload: builds route and runs default execution checks. - function _buildRouteAndExecuteSwap( + /// @notice Builds route and executes swap with default verification settings + /// @param params SwapTestParams for building and executing the swap + /// @param swapData DEX-specific swap data to pack into the route + /// @dev Simple helper that: + /// - Builds route using _buildBaseRoute + /// - Executes with default settings: + /// - No additional events + /// - No fee-on-transfer handling + /// - No exact output verification + /// - No Route event data validation + /// - Useful for basic swap test scenarios + function _buildRouteAndExecuteAndVerifySwap( SwapTestParams memory params, bytes memory swapData ) internal { - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( params, swapData, new ExpectedEvent[](0), @@ -412,8 +459,16 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { ); } - /// @notice Overload: builds route and expects a revert. - function _buildRouteAndExecuteSwap( + /// @notice Builds route and executes swap expecting a specific revert error + /// @param params SwapTestParams for building and executing the swap + /// @param swapData DEX-specific swap data to pack into the route + /// @param expectedRevert Error selector that should be thrown by processRoute + /// @dev Revert testing helper that: + /// - Builds route using _buildBaseRoute + /// - Delegates to _executeAndVerifySwap's revert testing logic + /// - For aggregator funds, uses amountIn-1 to trigger errors + /// - Sets minOut to 0 to focus on specific error cases + function _buildRouteAndExecuteAndVerifySwap( SwapTestParams memory params, bytes memory swapData, bytes4 expectedRevert @@ -422,8 +477,19 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { _executeAndVerifySwap(params, route, expectedRevert); } - /// @notice Overload: builds route and runs with fee-on-transfer toggle and extra events. - function _buildRouteAndExecuteSwap( + /// @notice Builds route and executes swap with fee-on-transfer support and event verification + /// @param params SwapTestParams for building and executing the swap + /// @param swapData DEX-specific swap data to pack into the route + /// @param additionalEvents Additional events to expect during execution + /// @param isFeeOnTransferToken Whether tokenIn is fee-on-transfer token (tolerates off-by-1 spent) + /// @dev Extended helper that: + /// - Builds route using _buildBaseRoute + /// - Supports fee-on-transfer tokens via tolerance parameter + /// - Allows verification of additional protocol events + /// - Disables exact output verification + /// - Disables Route event data validation + /// - Useful for testing complex scenarios with fee-on-transfer tokens + function _buildRouteAndExecuteAndVerifySwap( SwapTestParams memory params, bytes memory swapData, ExpectedEvent[] memory additionalEvents, @@ -448,25 +514,19 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { } } - /** - * @notice Sets up event expectations for a list of events - * @param events Array of events to expect - * @dev Each `ExpectedEvent` can independently toggle checking indexed topics and data. - */ + /// @notice Sets up event expectations for a list of events + /// @param events Array of events to expect + /// @dev Each `ExpectedEvent` can independently toggle checking indexed topics and data. function _expectEvents(ExpectedEvent[] memory events) internal { for (uint256 i = 0; i < events.length; i++) { _expectEvent(events[i]); } } - /** - * @notice Sets up expectation for a single event - * @param evt The event to expect with its check parameters and data - * @dev Builds the right number of topics based on `indexedParamIndices`, and an ABI-packed data - * payload of non-indexed params (static only). - * @custom:error TooManyIndexedParams if more than 3 indexed params are specified. - * @custom:error DynamicParamsNotSupported if any non-indexed param is not 32 bytes. - */ + /// @notice Sets up expectation for a single event + /// @param evt The event to expect with its check parameters and data + /// @dev Builds the right number of topics based on `indexedParamIndices`, and an ABI-packed data + /// payload of non-indexed params (static only). function _expectEvent(ExpectedEvent memory evt) internal { vm.expectEmit( evt.checkTopic1, diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 984291e1d..cc24948ff 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -6,19 +6,16 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { BaseCoreRouteTest } from "./BaseCoreRouteTest.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; -/** - * @title BaseDEXFacetTest - * @notice Base test contract with common functionality and abstractions for DEX-specific tests. - * @dev Child tests implement the virtual hooks to: - * - choose fork/network - * - set pool/token addresses - * - deploy and register their DEX facet + callback (if applicable) - * - * Usage: - * - Inherit and implement `_setupForkConfig`, `_setupDexEnv`, - * and facet creation hooks. - * - Call core helpers like `_buildMultiHopRoute` and `_addDexFacet`. - */ +/// @title BaseDEXFacetTest +/// @notice Base test contract with common functionality and abstractions for DEX-specific tests. +/// @dev Child tests implement the virtual hooks to: +/// - choose fork/network +/// - set pool/token addresses +/// - deploy and register their DEX facet + callback (if applicable)/// +/// Usage: +/// - Inherit and implement `_setupForkConfig`, `_setupDexEnv`, +/// and facet creation hooks. +/// - Call core helpers like `_buildMultiHopRoute` and `_addDexFacet`. abstract contract BaseDEXFacetTest is BaseCoreRouteTest { using SafeERC20 for IERC20; @@ -43,8 +40,6 @@ abstract contract BaseDEXFacetTest is BaseCoreRouteTest { // ==== Errors ==== - /// @notice Thrown when an expected pool reserve shape is not met in setup. - error WrongPoolReserves(); /// @notice Thrown when a required on-chain pool does not exist. error PoolDoesNotExist(); /// @notice Thrown when hopParams and hopData arrays differ in length. diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index f92f4562d..87a5d58b0 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -211,7 +211,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -241,7 +241,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -271,7 +271,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 6c70d4c51..6535938a4 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -260,7 +260,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { ); // Build route and execute with expected revert - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 35d81c079..9956f972c 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -184,7 +184,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -297,7 +297,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { bytes memory swapData = _buildCallbackSwapData(mockPool, USER_SENDER); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -340,7 +340,7 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -419,7 +419,11 @@ contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { commandType: CommandType.DistributeUserERC20 }); - _buildRouteAndExecuteSwap(params, swapData, InvalidCallData.selector); + _buildRouteAndExecuteAndVerifySwap( + params, + swapData, + InvalidCallData.selector + ); vm.stopPrank(); vm.clearMockedCalls(); diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index e2f6ef2e6..26aa7ad1c 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -56,7 +56,7 @@ contract CurveFacetTest is BaseDEXFacetTest { stETH = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); // stETH } - /// @notice Single‐pool swap: USER sends crvUSD → receives USDC. + /// @notice Single‐pool swap: USER sends crvUSD -> receives USDC. function test_CanSwap() public override { // Transfer 1 000 crvUSD from whale to USER_SENDER deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -74,7 +74,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -111,7 +111,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -211,7 +211,7 @@ contract CurveFacetTest is BaseDEXFacetTest { // Curve does not use callbacks - test intentionally empty } - /// @notice Legacy 3pool swap: USER sends USDC → receives USDT via 4-arg exchange (isV2=false). + /// @notice Legacy 3pool swap: USER sends USDC -> receives USDT via 4-arg exchange (isV2=false). function test_CanSwap_Legacy3Pool_USDC_to_USDT() public { // 3pool (DAI,USDC,USDT) mainnet address poolLegacy = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; @@ -222,7 +222,7 @@ contract CurveFacetTest is BaseDEXFacetTest { vm.startPrank(USER_SENDER); - // Build legacy swap data (isV2=false → 4-arg exchange) + // Build legacy swap data (isV2=false -> 4-arg exchange) bytes memory swapData = _buildCurveSwapData( CurveSwapParams({ pool: poolLegacy, @@ -234,7 +234,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenMid), // USDC tokenOut: address(tokenOut), // USDT @@ -250,7 +250,7 @@ contract CurveFacetTest is BaseDEXFacetTest { vm.stopPrank(); } - /// @notice Legacy 3pool swap funded by aggregator: USDC → USDT via 4-arg exchange (isV2=false). + /// @notice Legacy 3pool swap funded by aggregator: USDC -> USDT via 4-arg exchange (isV2=false). function test_CanSwap_FromDexAggregator_Legacy3Pool_USDC_to_USDT() public { address poolLegacy = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; uint256 amountIn = 1_000 * 1e6; @@ -271,7 +271,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenMid), // USDC tokenOut: address(tokenOut), // USDT @@ -307,7 +307,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -333,7 +333,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -350,7 +350,7 @@ contract CurveFacetTest is BaseDEXFacetTest { vm.stopPrank(); } - /// @notice Legacy stETH pool swap: USER sends native ETH → receives stETH via 4-arg exchange. + /// @notice Legacy stETH pool swap: USER sends native ETH -> receives stETH via 4-arg exchange. function test_CanSwap_LegacyEthPool_ETH_to_stETH() public { // stETH/ETH pool on mainnet uint256 amountIn = 1 ether; // 1 native ETH @@ -372,7 +372,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(0), // Native ETH tokenOut: address(stETH), @@ -388,7 +388,7 @@ contract CurveFacetTest is BaseDEXFacetTest { vm.stopPrank(); } - /// @notice Legacy stETH pool swap: USER sends stETH → receives native ETH via 4-arg exchange. + /// @notice Legacy stETH pool swap: USER sends stETH -> receives native ETH via 4-arg exchange. function test_CanSwap_LegacyEthPool_stETH_to_ETH() public { // stETH/ETH pool on mainnet uint256 amountIn = 1 ether; // 1 stETH @@ -414,7 +414,7 @@ contract CurveFacetTest is BaseDEXFacetTest { ); // Use the standard helper with isFeeOnTransferToken=true to handle stETH balance differences - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(stETH), tokenOut: address(0), // Native ETH diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 62f6a695a..6ac419864 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -123,7 +123,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -158,7 +158,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -174,7 +174,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { vm.stopPrank(); } - /// @notice Multi-hop user→aggregator USDC->WETH->USDB_C flow. + /// @notice Multi-hop user->aggregator USDC->WETH->USDB_C flow. function test_CanSwap_MultiHop() public override { // Fund the sender with tokens uint256 amountIn = _getDefaultAmountForTokenIn(); @@ -275,7 +275,7 @@ contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenMid), tokenOut: address(tokenIn), diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol index db8a0e709..3138b83df 100644 --- a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol @@ -68,7 +68,7 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { weth = IWETH(ADDRESS_WRAPPED_NATIVE); // Use constant from TestBase } - // ==== Positive Test Cases ==== + // ==== Test Cases ==== /// @notice Tests unwrapping WETH to ETH from user funds function test_CanUnwrap() public { @@ -219,8 +219,6 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { vm.stopPrank(); } - // ==== Negative Test Cases ==== - /// @notice Tests that unwrapNative reverts with zero destination address function testRevert_UnwrapNative_ZeroDestinationAddress() public { uint256 amountIn = 1 ether; diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol index 9a30042d3..a1ce15e35 100644 --- a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol +++ b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; import { BaseUniV2StyleDEXFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; +import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; /// @title PancakeV2FacetTest /// @notice Fork-based UniV2-style tests for PancakeV2 integration. @@ -55,7 +56,7 @@ contract PancakeV2FacetTest is BaseUniV2StyleDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 541cacbd0..070ddacdf 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -45,7 +45,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -77,7 +77,7 @@ contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 5a5090c5b..f16f971d3 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -55,7 +55,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { poolMidOut = 0x258d5f860B11ec73Ee200eB14f1b60A3B7A536a2; // USDC-USDT V1 } - /// @notice Single‐pool swap: USER sends WETH → receives USDC. + /// @notice Single‐pool swap: USER sends WETH -> receives USDC. function test_CanSwap() public override { // Transfer 1 000 WETH from whale to USER_SENDER deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); @@ -72,7 +72,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -105,7 +105,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -142,7 +142,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -179,7 +179,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -287,7 +287,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -321,7 +321,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -346,7 +346,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenOut), @@ -384,7 +384,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { // Approve tokens for the swap tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -421,7 +421,7 @@ contract SyncSwapV2FacetTest is BaseDEXFacetTest { // Approve tokens for the swap tokenIn.approve(address(ldaDiamond), _getDefaultAmountForTokenIn()); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index b542e259f..5e6380489 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -8,6 +8,7 @@ import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; /// @title VelodromeV2FacetTest @@ -477,7 +478,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -502,7 +503,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { }) ); - _buildRouteAndExecuteSwap( + _buildRouteAndExecuteAndVerifySwap( SwapTestParams({ tokenIn: address(tokenIn), tokenOut: address(tokenMid), @@ -622,11 +623,9 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { // ==== Helper Functions ==== - /** - * @notice Helper to execute a VelodromeV2 swap with optional callback expectation and strict event checking. - * @param params The swap request including direction and whether callback is enabled. - * @dev Computes expected outputs via router, builds payload, and asserts Route + optional HookCalled event. - */ + /// @notice Helper to execute a VelodromeV2 swap with optional callback expectation and strict event checking. + /// @param params The swap request including direction and whether callback is enabled. + /// @dev Computes expected outputs via router, builds payload, and asserts Route + optional HookCalled event. function _testSwap(VelodromeV2SwapTestParams memory params) internal { // get expected output amounts from the router. IVelodromeV2Router.Route[] From d2e69f3afaf8990beb007457b9ddfd2c50c47fc1 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 00:26:29 +0200 Subject: [PATCH 114/220] Refactor isIndexed array in BaseCoreRouteTest to use dynamic sizing based on event parameters, improving flexibility for future enhancements. --- test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index e9dcfbcd2..d5635cafe 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -562,7 +562,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { if (evt.checkData) { // Only support static params for now (each abi.encode(param) must be 32 bytes) uint256 total = evt.eventParams.length; - bool[8] memory isIndexed; // up to 8 params; expand if needed + bool[] memory isIndexed = new bool[](total); for (uint256 k = 0; k < topicsCount; k++) { uint8 pos = idx[k]; if (pos >= evt.eventParams.length) { @@ -571,7 +571,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { evt.eventParams.length ); } - if (pos < isIndexed.length) isIndexed[pos] = true; + isIndexed[pos] = true; } for (uint256 p = 0; p < total; p++) { From 169361b282b767399eb90af3b8d9137262779772 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 00:46:51 +0200 Subject: [PATCH 115/220] improve error handling in NativeWrapperFacetTest --- .../Periphery/Lda/BaseCoreRouteTest.t.sol | 41 ++++++++++++++----- .../Lda/Facets/NativeWrapperFacet.t.sol | 24 +++++------ 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index d5635cafe..bae748b8a 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -233,7 +233,13 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { _expectEvents(additionalEvents); - vm.expectEmit(true, true, true, routeEventVerification.checkData); + vm.expectEmit( + true, + true, + true, + routeEventVerification.checkData, + address(ldaDiamond) + ); emit Route( fromAddress, params.destinationAddress, @@ -395,16 +401,31 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { } vm.expectRevert(expectedRevert); - coreRouteFacet.processRoute( - params.tokenIn, - params.commandType == CommandType.DistributeSelfERC20 + { + uint256 sendAmount = params.commandType == + CommandType.DistributeSelfERC20 ? params.amountIn - : params.amountIn - 1, - params.tokenOut, - 0, // minOut = 0 for tests - params.destinationAddress, - route - ); + : params.amountIn - 1; + if (LibAsset.isNativeAsset(params.tokenIn)) { + coreRouteFacet.processRoute{ value: sendAmount }( + params.tokenIn, + sendAmount, + params.tokenOut, + 0, // minOut = 0 for tests + params.destinationAddress, + route + ); + } else { + coreRouteFacet.processRoute( + params.tokenIn, + sendAmount, + params.tokenOut, + 0, // minOut = 0 for tests + params.destinationAddress, + route + ); + } + } } /// @notice Builds route and executes swap with full verification options in a single call diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol index 3138b83df..a2c92b62f 100644 --- a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol @@ -250,19 +250,19 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { vm.stopPrank(); } - /// @notice Tests that wrapNative reverts with zero wrapped native address - function testRevert_WrapNative_ZeroWrappedNative() public { + /// @notice Tests that wrapNative reverts with zero destination address + function testRevert_WrapNative_ZeroDestinationAddress() public { uint256 amountIn = 1 ether; - // Fund aggregator with ETH - vm.deal(address(ldaDiamond), amountIn); + // Fund user with ETH (not aggregator, since this is DistributeNative) + vm.deal(USER_SENDER, amountIn); vm.startPrank(USER_SENDER); bytes memory swapData = _buildWrapSwapData( WrapParams({ - wrappedNative: address(0), // Invalid wrapped native - destinationAddress: USER_RECEIVER + wrappedNative: address(weth), + destinationAddress: address(0) // Invalid destination }) ); @@ -282,19 +282,19 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { vm.stopPrank(); } - /// @notice Tests that wrapNative reverts with zero destination address - function testRevert_WrapNative_ZeroDestinationAddress() public { + /// @notice Tests that wrapNative reverts with zero wrapped native address + function testRevert_WrapNative_ZeroWrappedNative() public { uint256 amountIn = 1 ether; - // Fund aggregator with ETH - vm.deal(address(ldaDiamond), amountIn); + // Fund user with ETH (not aggregator, since this is DistributeNative) + vm.deal(USER_SENDER, amountIn); vm.startPrank(USER_SENDER); bytes memory swapData = _buildWrapSwapData( WrapParams({ - wrappedNative: address(weth), - destinationAddress: address(0) // Invalid destination + wrappedNative: address(0), // Invalid wrapped native + destinationAddress: USER_RECEIVER }) ); From d05f15a3799e26371e148f3309dde442a60514e0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 00:56:26 +0200 Subject: [PATCH 116/220] changed paths --- test/solidity/Periphery/GasZipPeriphery.t.sol | 4 ++-- test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol | 6 +++--- test/solidity/Periphery/Lda/BaseDexFacet.t.sol | 4 ++-- .../Periphery/Lda/BaseDexFacetWithCallback.t.sol | 6 +++--- .../Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 8 ++++---- .../Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 12 ++++++------ .../solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol | 4 ++-- .../Periphery/Lda/Facets/EnosysDexV3Facet.t.sol | 4 ++-- .../Periphery/Lda/Facets/HyperswapV3Facet.t.sol | 4 ++-- .../solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 4 ++-- .../Periphery/Lda/Facets/LaminarV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol | 4 ++-- .../solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol | 4 ++-- .../Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 4 ++-- .../Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 4 ++-- .../solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol | 6 +++--- test/solidity/utils/TestBase.sol | 6 +++--- 19 files changed, 48 insertions(+), 48 deletions(-) diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index d61f66fae..2b5dc285b 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,7 +10,7 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LDADiamondTest } from "./Lda/utils/LdaDiamondTest.sol"; +import { LdaDiamondTest } from "./Lda/utils/LdaDiamondTest.sol"; import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; import { NativeWrapperFacet } from "lifi/Periphery/Lda/Facets/NativeWrapperFacet.sol"; @@ -61,7 +61,7 @@ contract GasZipPeripheryTest is TestBase { function setUp() public override { customBlockNumberForForking = 22566858; initTestBase(); - LDADiamondTest.setUp(); + LdaDiamondTest.setUp(); // deploy contracts gasZipPeriphery = new TestGasZipPeriphery( diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index bae748b8a..ad19dabb5 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LDADiamondTest } from "./utils/LDADiamondTest.sol"; +import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; /// @title BaseCoreRouteTest /// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. @@ -15,7 +15,7 @@ import { LDADiamondTest } from "./utils/LDADiamondTest.sol"; /// - Event expectations helpers /// - Overloads of `_executeAndVerifySwap` including revert path /// Concrete tests compose these helpers to succinctly define swap scenarios. -abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { +abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { using SafeERC20 for IERC20; // ==== Types ==== @@ -120,7 +120,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @notice Deploys and attaches `CoreRouteFacet` to the diamond under test. /// @dev Invoked from `setUp` of child tests via inheritance chain. function setUp() public virtual override { - LDADiamondTest.setUp(); + LdaDiamondTest.setUp(); _addCoreRouteFacet(); } diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index cc24948ff..5e60433f9 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { BaseCoreRouteTest } from "./BaseCoreRouteTest.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; -/// @title BaseDEXFacetTest +/// @title BaseDexFacetTest /// @notice Base test contract with common functionality and abstractions for DEX-specific tests. /// @dev Child tests implement the virtual hooks to: /// - choose fork/network @@ -16,7 +16,7 @@ import { stdJson } from "forge-std/StdJson.sol"; /// - Inherit and implement `_setupForkConfig`, `_setupDexEnv`, /// and facet creation hooks. /// - Call core helpers like `_buildMultiHopRoute` and `_addDexFacet`. -abstract contract BaseDEXFacetTest is BaseCoreRouteTest { +abstract contract BaseDexFacetTest is BaseCoreRouteTest { using SafeERC20 for IERC20; // ==== Types ==== diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index 79aae6efe..e96d94ce6 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -3,15 +3,15 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; -import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; +import { BaseDexFacetTest } from "./BaseDEXFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; -/// @title BaseDEXFacetWithCallbackTest +/// @title BaseDexFacetWithCallbackTest /// @notice Base harness for testing DEX facets that rely on swap callbacks. /// @dev Provides callback selector/data hooks and two negative tests: /// - unexpected callback sender /// - swap path where pool never calls back (should revert) -abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { +abstract contract BaseDexFacetWithCallbackTest is BaseDexFacetTest { /// @notice Returns the callback selector used by the DEX under test. /// @return selector Function selector for the DEX's swap callback. function _getCallbackSelector() internal virtual returns (bytes4); diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index 87a5d58b0..d97cf4549 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.17; import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; -import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; +import { BaseDexFacetTest } from "./BaseDEXFacet.t.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -/// @title BaseUniV2StyleDEXFacetTest -/// @notice Shared UniV2-style testing helpers built atop BaseDEXFacetTest. +/// @title BaseUniV2StyleDexFacetTest +/// @notice Shared UniV2-style testing helpers built atop BaseDexFacetTest. /// @dev Handles selector wiring, pool direction inference (token0/token1), and auto-execution flows. -abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { +abstract contract BaseUniV2StyleDexFacetTest is BaseDexFacetTest { /// @notice UniV2-style facet proxy handle (points to diamond after setup). UniV2StyleFacet internal uniV2Facet; diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 6535938a4..082b2d1b4 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UniV3StyleFacet } from "lifi/Periphery/lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; +import { BaseDexFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; -/// @title BaseUniV3StyleDEXFacetTest -/// @notice Shared UniV3-style testing helpers built atop BaseDEXFacetWithCallbackTest. +/// @title BaseUniV3StyleDexFacetTest +/// @notice Shared UniV3-style testing helpers built atop BaseDexFacetWithCallbackTest. /// @dev Handles selector wiring, pool direction inference (token0/token1), and auto-execution flows. -abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { +abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetWithCallbackTest { /// @notice UniV3-style facet proxy handle (points to diamond after setup). UniV3StyleFacet internal uniV3Facet; @@ -191,7 +191,7 @@ abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { // ==== Overrides ==== - /// @notice Builds callback-arming swap data for `BaseDEXFacetWithCallbackTest` harness. + /// @notice Builds callback-arming swap data for `BaseDexFacetWithCallbackTest` harness. /// @param pool Pool to invoke against in callback tests. /// @param destinationAddress Destination address for proceeds in these tests. /// @return Packed swap payload. diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 9956f972c..abbe31029 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -11,7 +11,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; -import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; +import { BaseDexFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; /// @title AlgebraFacetTest /// @notice Forked tests for Algebra pools integrated via LDA CoreRoute. @@ -20,7 +20,7 @@ import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol" /// - fee-on-transfer compatibility /// - multi-hop routes combining user and aggregator steps /// - callback protection and revert scenarios -contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { +contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { /// @notice Facet proxy handle exposed on the diamond. AlgebraFacet internal algebraFacet; diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 26aa7ad1c..11cde83e4 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; +import { BaseDexFacetTest } from "../BaseDEXFacet.t.sol"; import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title CurveFacetTest /// @notice Linea Curve tests via LDA route. /// @dev Verifies single-hop, aggregator flow, and revert paths. -contract CurveFacetTest is BaseDEXFacetTest { +contract CurveFacetTest is BaseDexFacetTest { /// @notice Facet proxy for swaps bound to the diamond after setup. CurveFacet internal curveFacet; diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 635487e9e..c9f92f8d0 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title EnosysDEXV3FacetTest /// @notice Forked UniV3-style tests for Enosys DEX V3 pools via LDA route. /// @dev Configures Flare network and a concrete pool pair; inherits execution helpers from the base. -contract EnosysDEXV3FacetTest is BaseUniV3StyleDEXFacetTest { +contract EnosysDEXV3FacetTest is BaseUniV3StyleDexFacetTest { // ==== Setup Functions ==== /// @notice Selects Flare fork and block height used for deterministic tests. diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 212a025c2..01b8258b9 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title HyperswapV3FacetTest /// @notice Fork-based UniV3-style tests for HyperswapV3 integration. /// @dev Selects Hyperevm fork, sets pool/token addresses, and delegates logic to base UniV3 test helpers. -contract HyperswapV3FacetTest is BaseUniV3StyleDEXFacetTest { +contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { // ==== Setup Functions ==== /// @notice Selects `hyperevm` network and block for fork tests. diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 6ac419864..e1388c3fc 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -4,13 +4,13 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; +import { BaseDexFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; /// @title IzumiV3FacetTest /// @notice Forked + local tests for Izumi V3 pools routed through LDA. /// @dev Validates swap paths, aggregator/user flows, multi-hop, callback auth, and revert cases. -contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { +contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { /// @notice Facet proxy handle bound to diamond after facet cut. IzumiV3Facet internal izumiV3Facet; diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 2c42ebb65..7f0371dbc 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title LaminarV3FacetTest /// @notice Hyperevm UniV3-style tests for Laminar pools via LDA. /// @dev Minimal setup; inherits all execution helpers from the base. -contract LaminarV3FacetTest is BaseUniV3StyleDEXFacetTest { +contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { /// @notice Selects Hyperevm fork and block used by tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol index a1ce15e35..70fe73b28 100644 --- a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol +++ b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; -import { BaseUniV2StyleDEXFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; +import { BaseUniV2StyleDexFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; /// @title PancakeV2FacetTest /// @notice Fork-based UniV2-style tests for PancakeV2 integration. /// @dev Selects BSC fork, sets pool/token addresses, and delegates logic to base UniV2 test helpers. -contract PancakeV2FacetTest is BaseUniV2StyleDEXFacetTest { +contract PancakeV2FacetTest is BaseUniV2StyleDexFacetTest { // ==== Setup Functions ==== /// @notice Selects `bsc` network and block for fork tests. diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 070ddacdf..94dc9caa2 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title RabbitSwapV3FacetTest /// @notice Viction UniV3-style tests for RabbitSwap V3 pools. /// @dev Covers invalid pool/destinationAddress edge cases plus standard setup. -contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { +contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { /// @notice Selects Viction fork and block height used in tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index f16f971d3..678638e6c 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; +import { BaseDexFacetTest } from "../BaseDEXFacet.t.sol"; import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title SyncSwapV2FacetTest /// @notice Linea SyncSwap V2 tests via LDA route; includes both v1 and v2 pool wiring. /// @dev Verifies single-hop, aggregator flow, multi-hop (with DispatchSinglePoolSwap), and revert paths. -contract SyncSwapV2FacetTest is BaseDEXFacetTest { +contract SyncSwapV2FacetTest is BaseDexFacetTest { /// @notice Facet proxy for swaps bound to the diamond after setup. SyncSwapV2Facet internal syncSwapV2Facet; diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 5e6380489..7700f48dc 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -9,12 +9,12 @@ import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; -import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; +import { BaseDexFacetTest } from "../BaseDEXFacet.t.sol"; /// @title VelodromeV2FacetTest /// @notice Optimism Velodrome V2 tests covering stable/volatile pools, aggregator/user flows, multi-hop, and precise reserve accounting. /// @dev Includes a flashloan callback path to assert event expectations and reserve deltas. -contract VelodromeV2FacetTest is BaseDEXFacetTest { +contract VelodromeV2FacetTest is BaseDexFacetTest { /// @notice Facet proxy bound to the diamond after setup. VelodromeV2Facet internal velodromeV2Facet; diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index d105893d3..dfb584e6b 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title XSwapV3FacetTest /// @notice XDC chain UniV3-style tests for XSwap V3 integration via LDA. /// @dev Minimal setup; inherits execution logic from base UniV3-style test harness. -contract XSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { +contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { // ==== Setup Functions ==== /// @notice Selects XDC fork and block height used by the tests. diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index db7a97827..7afc271df 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -12,15 +12,15 @@ import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; -/// @title LDADiamondTest +/// @title LdaDiamondTest /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. /// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. -contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { +contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice The diamond proxy under test. LDADiamond internal ldaDiamond; /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. - /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. + /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDexFacetTest. function setUp() public virtual { ldaDiamond = createLDADiamond(USER_LDA_DIAMOND_OWNER, USER_LDA_PAUSER); } diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index c94d35607..3add13ab2 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -15,7 +15,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LDADiamondTest } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; +import { LdaDiamondTest } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; using stdJson for string; @@ -99,7 +99,7 @@ abstract contract TestBase is TestBaseRandomConstants, TestHelpers, DiamondTest, - LDADiamondTest, + LdaDiamondTest, ILiFi { address internal _facetTestContractAddress; @@ -244,7 +244,7 @@ abstract contract TestBase is // deploy & configure diamond diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); // deploy & configure ldaDiamond - LDADiamondTest.setUp(); + LdaDiamondTest.setUp(); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From 337bc149987fefc7f3ada737077133d80ac79149 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:10:22 +0200 Subject: [PATCH 117/220] updates --- test/solidity/Periphery/Lda/BaseDexFacet.t.sol | 4 ++-- .../Periphery/Lda/BaseDexFacetWithCallback.t.sol | 6 +++--- .../Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 8 ++++---- .../Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 10 +++++----- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol | 4 ++-- .../Periphery/Lda/Facets/EnosysDexV3Facet.t.sol | 4 ++-- .../Periphery/Lda/Facets/HyperswapV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 4 ++-- .../solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol | 4 ++-- .../Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 4 ++-- .../Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol | 2 +- 16 files changed, 37 insertions(+), 37 deletions(-) diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol index 5e60433f9..cc24948ff 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacet.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { BaseCoreRouteTest } from "./BaseCoreRouteTest.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; -/// @title BaseDexFacetTest +/// @title BaseDEXFacetTest /// @notice Base test contract with common functionality and abstractions for DEX-specific tests. /// @dev Child tests implement the virtual hooks to: /// - choose fork/network @@ -16,7 +16,7 @@ import { stdJson } from "forge-std/StdJson.sol"; /// - Inherit and implement `_setupForkConfig`, `_setupDexEnv`, /// and facet creation hooks. /// - Call core helpers like `_buildMultiHopRoute` and `_addDexFacet`. -abstract contract BaseDexFacetTest is BaseCoreRouteTest { +abstract contract BaseDEXFacetTest is BaseCoreRouteTest { using SafeERC20 for IERC20; // ==== Types ==== diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index e96d94ce6..79aae6efe 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -3,15 +3,15 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; -import { BaseDexFacetTest } from "./BaseDEXFacet.t.sol"; +import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; -/// @title BaseDexFacetWithCallbackTest +/// @title BaseDEXFacetWithCallbackTest /// @notice Base harness for testing DEX facets that rely on swap callbacks. /// @dev Provides callback selector/data hooks and two negative tests: /// - unexpected callback sender /// - swap path where pool never calls back (should revert) -abstract contract BaseDexFacetWithCallbackTest is BaseDexFacetTest { +abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { /// @notice Returns the callback selector used by the DEX under test. /// @return selector Function selector for the DEX's swap callback. function _getCallbackSelector() internal virtual returns (bytes4); diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index d97cf4549..87a5d58b0 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.17; import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; -import { BaseDexFacetTest } from "./BaseDEXFacet.t.sol"; +import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -/// @title BaseUniV2StyleDexFacetTest -/// @notice Shared UniV2-style testing helpers built atop BaseDexFacetTest. +/// @title BaseUniV2StyleDEXFacetTest +/// @notice Shared UniV2-style testing helpers built atop BaseDEXFacetTest. /// @dev Handles selector wiring, pool direction inference (token0/token1), and auto-execution flows. -abstract contract BaseUniV2StyleDexFacetTest is BaseDexFacetTest { +abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { /// @notice UniV2-style facet proxy handle (points to diamond after setup). UniV2StyleFacet internal uniV2Facet; diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index 082b2d1b4..d253bc1ab 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.17; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseDexFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; +import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; -/// @title BaseUniV3StyleDexFacetTest -/// @notice Shared UniV3-style testing helpers built atop BaseDexFacetWithCallbackTest. +/// @title BaseUniV3StyleDEXFacetTest +/// @notice Shared UniV3-style testing helpers built atop BaseDEXFacetWithCallbackTest. /// @dev Handles selector wiring, pool direction inference (token0/token1), and auto-execution flows. -abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetWithCallbackTest { +abstract contract BaseUniV3StyleDEXFacetTest is BaseDEXFacetWithCallbackTest { /// @notice UniV3-style facet proxy handle (points to diamond after setup). UniV3StyleFacet internal uniV3Facet; @@ -191,7 +191,7 @@ abstract contract BaseUniV3StyleDexFacetTest is BaseDexFacetWithCallbackTest { // ==== Overrides ==== - /// @notice Builds callback-arming swap data for `BaseDexFacetWithCallbackTest` harness. + /// @notice Builds callback-arming swap data for `BaseDEXFacetWithCallbackTest` harness. /// @param pool Pool to invoke against in callback tests. /// @param destinationAddress Destination address for proceeds in these tests. /// @return Packed swap payload. diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index abbe31029..9956f972c 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -11,7 +11,7 @@ import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; -import { BaseDexFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; +import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; /// @title AlgebraFacetTest /// @notice Forked tests for Algebra pools integrated via LDA CoreRoute. @@ -20,7 +20,7 @@ import { BaseDexFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol" /// - fee-on-transfer compatibility /// - multi-hop routes combining user and aggregator steps /// - callback protection and revert scenarios -contract AlgebraFacetTest is BaseDexFacetWithCallbackTest { +contract AlgebraFacetTest is BaseDEXFacetWithCallbackTest { /// @notice Facet proxy handle exposed on the diamond. AlgebraFacet internal algebraFacet; diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 11cde83e4..26aa7ad1c 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { BaseDexFacetTest } from "../BaseDEXFacet.t.sol"; +import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title CurveFacetTest /// @notice Linea Curve tests via LDA route. /// @dev Verifies single-hop, aggregator flow, and revert paths. -contract CurveFacetTest is BaseDexFacetTest { +contract CurveFacetTest is BaseDEXFacetTest { /// @notice Facet proxy for swaps bound to the diamond after setup. CurveFacet internal curveFacet; diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index c9f92f8d0..635487e9e 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title EnosysDEXV3FacetTest /// @notice Forked UniV3-style tests for Enosys DEX V3 pools via LDA route. /// @dev Configures Flare network and a concrete pool pair; inherits execution helpers from the base. -contract EnosysDEXV3FacetTest is BaseUniV3StyleDexFacetTest { +contract EnosysDEXV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== /// @notice Selects Flare fork and block height used for deterministic tests. diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 01b8258b9..212a025c2 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title HyperswapV3FacetTest /// @notice Fork-based UniV3-style tests for HyperswapV3 integration. /// @dev Selects Hyperevm fork, sets pool/token addresses, and delegates logic to base UniV3 test helpers. -contract HyperswapV3FacetTest is BaseUniV3StyleDexFacetTest { +contract HyperswapV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== /// @notice Selects `hyperevm` network and block for fork tests. diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index e1388c3fc..6ac419864 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -4,13 +4,13 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseDexFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; +import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; /// @title IzumiV3FacetTest /// @notice Forked + local tests for Izumi V3 pools routed through LDA. /// @dev Validates swap paths, aggregator/user flows, multi-hop, callback auth, and revert cases. -contract IzumiV3FacetTest is BaseDexFacetWithCallbackTest { +contract IzumiV3FacetTest is BaseDEXFacetWithCallbackTest { /// @notice Facet proxy handle bound to diamond after facet cut. IzumiV3Facet internal izumiV3Facet; diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 7f0371dbc..2c42ebb65 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title LaminarV3FacetTest /// @notice Hyperevm UniV3-style tests for Laminar pools via LDA. /// @dev Minimal setup; inherits all execution helpers from the base. -contract LaminarV3FacetTest is BaseUniV3StyleDexFacetTest { +contract LaminarV3FacetTest is BaseUniV3StyleDEXFacetTest { /// @notice Selects Hyperevm fork and block used by tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol index 70fe73b28..a1ce15e35 100644 --- a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol +++ b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; -import { BaseUniV2StyleDexFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; +import { BaseUniV2StyleDEXFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; /// @title PancakeV2FacetTest /// @notice Fork-based UniV2-style tests for PancakeV2 integration. /// @dev Selects BSC fork, sets pool/token addresses, and delegates logic to base UniV2 test helpers. -contract PancakeV2FacetTest is BaseUniV2StyleDexFacetTest { +contract PancakeV2FacetTest is BaseUniV2StyleDEXFacetTest { // ==== Setup Functions ==== /// @notice Selects `bsc` network and block for fork tests. diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 94dc9caa2..070ddacdf 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title RabbitSwapV3FacetTest /// @notice Viction UniV3-style tests for RabbitSwap V3 pools. /// @dev Covers invalid pool/destinationAddress edge cases plus standard setup. -contract RabbitSwapV3FacetTest is BaseUniV3StyleDexFacetTest { +contract RabbitSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { /// @notice Selects Viction fork and block height used in tests. function _setupForkConfig() internal override { forkConfig = ForkConfig({ diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index 678638e6c..f16f971d3 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { BaseDexFacetTest } from "../BaseDEXFacet.t.sol"; +import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title SyncSwapV2FacetTest /// @notice Linea SyncSwap V2 tests via LDA route; includes both v1 and v2 pool wiring. /// @dev Verifies single-hop, aggregator flow, multi-hop (with DispatchSinglePoolSwap), and revert paths. -contract SyncSwapV2FacetTest is BaseDexFacetTest { +contract SyncSwapV2FacetTest is BaseDEXFacetTest { /// @notice Facet proxy for swaps bound to the diamond after setup. SyncSwapV2Facet internal syncSwapV2Facet; diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 7700f48dc..5e6380489 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -9,12 +9,12 @@ import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; -import { BaseDexFacetTest } from "../BaseDEXFacet.t.sol"; +import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; /// @title VelodromeV2FacetTest /// @notice Optimism Velodrome V2 tests covering stable/volatile pools, aggregator/user flows, multi-hop, and precise reserve accounting. /// @dev Includes a flashloan callback path to assert event expectations and reserve deltas. -contract VelodromeV2FacetTest is BaseDexFacetTest { +contract VelodromeV2FacetTest is BaseDEXFacetTest { /// @notice Facet proxy bound to the diamond after setup. VelodromeV2Facet internal velodromeV2Facet; diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index dfb584e6b..d105893d3 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; -import { BaseUniV3StyleDexFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; +import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title XSwapV3FacetTest /// @notice XDC chain UniV3-style tests for XSwap V3 integration via LDA. /// @dev Minimal setup; inherits execution logic from base UniV3-style test harness. -contract XSwapV3FacetTest is BaseUniV3StyleDexFacetTest { +contract XSwapV3FacetTest is BaseUniV3StyleDEXFacetTest { // ==== Setup Functions ==== /// @notice Selects XDC fork and block height used by the tests. diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 7afc271df..20662471d 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -20,7 +20,7 @@ contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { LDADiamond internal ldaDiamond; /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. - /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDexFacetTest. + /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. function setUp() public virtual { ldaDiamond = createLDADiamond(USER_LDA_DIAMOND_OWNER, USER_LDA_PAUSER); } From f3de73067f88f50140f49dc4c68f5d73763aec43 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:14:37 +0200 Subject: [PATCH 118/220] update paths --- conventions.md | 2 +- script/deploy/facets/LDA/DeployAlgebraFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployCurveFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol | 2 +- script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol | 2 +- script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol | 2 +- script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol | 2 +- src/Periphery/Lda/Facets/AlgebraFacet.sol | 4 ++-- src/Periphery/Lda/Facets/CurveFacet.sol | 2 +- src/Periphery/Lda/Facets/IzumiV3Facet.sol | 4 ++-- src/Periphery/Lda/Facets/UniV3StyleFacet.sol | 4 ++-- test/solidity/Periphery/GasZipPeriphery.t.sol | 10 +++++----- test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol | 8 ++++---- .../Periphery/Lda/BaseDexFacetWithCallback.t.sol | 2 +- .../Periphery/Lda/BaseUniV2StyleDexFacet.t.sol | 2 +- .../Periphery/Lda/BaseUniV3StyleDexFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol | 4 ++-- .../solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol | 2 +- .../Periphery/Lda/Facets/EnosysDexV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/HyperswapV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol | 2 +- .../solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol | 2 +- .../Periphery/Lda/Facets/NativeWrapperFacet.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol | 2 +- test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol | 2 +- .../Periphery/Lda/Facets/SyncSwapV2Facet.t.sol | 2 +- .../Periphery/Lda/Facets/VelodromeV2Facet.t.sol | 4 ++-- test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol | 2 +- test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol | 6 +++--- test/solidity/utils/TestBase.sol | 6 +++--- 33 files changed, 49 insertions(+), 49 deletions(-) diff --git a/conventions.md b/conventions.md index 1e490e60d..4a1b5ee11 100644 --- a/conventions.md +++ b/conventions.md @@ -154,7 +154,7 @@ We use Foundry as our primary development and testing framework. Foundry provide - **Error Handling:** - **Generic errors** must be defined in `src/Errors/GenericErrors.sol` - - LDA-specific errors should be defined in `src/Periphery/Lda/Errors/Errors.sol` + - LDA-specific errors should be defined in `src/Periphery/LDA/Errors/Errors.sol` - Use for common validation errors that apply across multiple contracts - When adding new generic errors, increment the version in `@custom:version` comment - Examples: `InvalidAmount()`, `InvalidCallData()`, `UnAuthorized()` diff --git a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol index ff51b4a91..40fedd9f1 100644 --- a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol +++ b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; +import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("AlgebraFacet") {} diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index f28a906fd..352d08355 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CoreRouteFacet") {} diff --git a/script/deploy/facets/LDA/DeployCurveFacet.s.sol b/script/deploy/facets/LDA/DeployCurveFacet.s.sol index 1e5dead12..18b3c43ce 100644 --- a/script/deploy/facets/LDA/DeployCurveFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCurveFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; +import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CurveFacet") {} diff --git a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol index 55055a52d..6a295772b 100644 --- a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol +++ b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; +import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("IzumiV3Facet") {} diff --git a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol index 328ea8162..6d10e3c4b 100644 --- a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("SyncSwapV2Facet") {} diff --git a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol index 6347240bc..b08a859c7 100644 --- a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("UniV2StyleFacet") {} diff --git a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol index b69085833..fc825770f 100644 --- a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("UniV3StyleFacet") {} diff --git a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol index af8c3817f..1c1df461e 100644 --- a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("VelodromeV2Facet") {} diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/Lda/Facets/AlgebraFacet.sol index 752e2a388..86e8a40a1 100644 --- a/src/Periphery/Lda/Facets/AlgebraFacet.sol +++ b/src/Periphery/Lda/Facets/AlgebraFacet.sol @@ -8,8 +8,8 @@ import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticato import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/Lda/PoolCallbackAuthenticated.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title AlgebraFacet diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/Lda/Facets/CurveFacet.sol index 583796303..25574a5e1 100644 --- a/src/Periphery/Lda/Facets/CurveFacet.sol +++ b/src/Periphery/Lda/Facets/CurveFacet.sol @@ -7,7 +7,7 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ICurve } from "lifi/Interfaces/ICurve.sol"; import { ICurveV2 } from "lifi/Interfaces/ICurveV2.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { BaseRouteConstants } from "lifi/Periphery/Lda/BaseRouteConstants.sol"; +import { BaseRouteConstants } from "lifi/Periphery/LDA/BaseRouteConstants.sol"; /// @title CurveFacet /// @author LI.FI (https://li.fi) diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/Lda/Facets/IzumiV3Facet.sol index 44f840319..a74694b46 100644 --- a/src/Periphery/Lda/Facets/IzumiV3Facet.sol +++ b/src/Periphery/Lda/Facets/IzumiV3Facet.sol @@ -6,8 +6,8 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/Lda/PoolCallbackAuthenticated.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title IzumiV3Facet diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol index 426f7684c..ba932a730 100644 --- a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/Lda/Facets/UniV3StyleFacet.sol @@ -7,8 +7,8 @@ import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticato import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/Lda/PoolCallbackAuthenticated.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title UniV3StyleFacet diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 2b5dc285b..c2403db60 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,10 +10,10 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LdaDiamondTest } from "./Lda/utils/LdaDiamondTest.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; -import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; -import { NativeWrapperFacet } from "lifi/Periphery/Lda/Facets/NativeWrapperFacet.sol"; +import { LDADiamondTest } from "./LDA/utils/LDADiamondTest.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; // Stub GenericSwapFacet Contract contract TestGasZipPeriphery is GasZipPeriphery { @@ -61,7 +61,7 @@ contract GasZipPeripheryTest is TestBase { function setUp() public override { customBlockNumberForForking = 22566858; initTestBase(); - LdaDiamondTest.setUp(); + LDADiamondTest.setUp(); // deploy contracts gasZipPeriphery = new TestGasZipPeriphery( diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol index ad19dabb5..553a61bef 100644 --- a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; +import { LDADiamondTest } from "./utils/LDADiamondTest.sol"; /// @title BaseCoreRouteTest /// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. @@ -15,7 +15,7 @@ import { LdaDiamondTest } from "./utils/LdaDiamondTest.sol"; /// - Event expectations helpers /// - Overloads of `_executeAndVerifySwap` including revert path /// Concrete tests compose these helpers to succinctly define swap scenarios. -abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { +abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { using SafeERC20 for IERC20; // ==== Types ==== @@ -120,7 +120,7 @@ abstract contract BaseCoreRouteTest is LdaDiamondTest, TestHelpers { /// @notice Deploys and attaches `CoreRouteFacet` to the diamond under test. /// @dev Invoked from `setUp` of child tests via inheritance chain. function setUp() public virtual override { - LdaDiamondTest.setUp(); + LDADiamondTest.setUp(); _addCoreRouteFacet(); } diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol index 79aae6efe..104953506 100644 --- a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol +++ b/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol index 87a5d58b0..a0c8dbd77 100644 --- a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UniV2StyleFacet } from "lifi/Periphery/Lda/Facets/UniV2StyleFacet.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol index d253bc1ab..f19373acd 100644 --- a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol +++ b/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetWithCallbackTest } from "./BaseDEXFacetWithCallback.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol index 9956f972c..4f33355e9 100644 --- a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol @@ -6,9 +6,9 @@ import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { IAlgebraRouter } from "lifi/Interfaces/IAlgebraRouter.sol"; import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; -import { AlgebraFacet } from "lifi/Periphery/Lda/Facets/AlgebraFacet.sol"; +import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol index 97eb3418e..131567c02 100644 --- a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; -import { CoreRouteFacet } from "lifi/Periphery/Lda/Facets/CoreRouteFacet.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol index 26aa7ad1c..50c7a84f3 100644 --- a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; -import { CurveFacet } from "lifi/Periphery/Lda/Facets/CurveFacet.sol"; +import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title CurveFacetTest diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol index 635487e9e..6f713b4ec 100644 --- a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title EnosysDEXV3FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol index 212a025c2..a5d1847ac 100644 --- a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IHyperswapV3Factory } from "lifi/Interfaces/IHyperswapV3Factory.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title HyperswapV3FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol index 6ac419864..2384d5639 100644 --- a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IzumiV3Facet } from "lifi/Periphery/Lda/Facets/IzumiV3Facet.sol"; +import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; import { MockNoCallbackPool } from "../../../utils/MockNoCallbackPool.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol index 2c42ebb65..08c577352 100644 --- a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title LaminarV3FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol index a2c92b62f..d1d95674e 100644 --- a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IWETH } from "lifi/Interfaces/IWETH.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; -import { NativeWrapperFacet } from "lifi/Periphery/Lda/Facets/NativeWrapperFacet.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title NativeWrapperFacetTest diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol index a1ce15e35..31c96fe57 100644 --- a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol +++ b/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; import { BaseUniV2StyleDEXFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; -import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { WrongPoolReserves } from "lifi/Periphery/LDA/Errors/Errors.sol"; /// @title PancakeV2FacetTest /// @notice Fork-based UniV2-style tests for PancakeV2 integration. diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol index 070ddacdf..d1c9caa2b 100644 --- a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol +++ b/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol index f16f971d3..102b96035 100644 --- a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; -import { SyncSwapV2Facet } from "lifi/Periphery/Lda/Facets/SyncSwapV2Facet.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; /// @title SyncSwapV2FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol index 5e6380489..45cc47150 100644 --- a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol @@ -6,9 +6,9 @@ import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { IVelodromeV2PoolCallee } from "lifi/Interfaces/IVelodromeV2PoolCallee.sol"; import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory.sol"; import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; -import { VelodromeV2Facet } from "lifi/Periphery/Lda/Facets/VelodromeV2Facet.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { WrongPoolReserves } from "lifi/Periphery/Lda/Errors/Errors.sol"; +import { WrongPoolReserves } from "lifi/Periphery/LDA/Errors/Errors.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; /// @title VelodromeV2FacetTest diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol index d105893d3..c48a749bc 100644 --- a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol +++ b/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { UniV3StyleFacet } from "lifi/Periphery/Lda/Facets/UniV3StyleFacet.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; import { BaseUniV3StyleDEXFacetTest } from "../BaseUniV3StyleDEXFacet.t.sol"; /// @title XSwapV3FacetTest diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol index 20662471d..230898010 100644 --- a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol +++ b/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LDADiamond } from "lifi/Periphery/Lda/LDADiamond.sol"; +import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; @@ -12,10 +12,10 @@ import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; -/// @title LdaDiamondTest +/// @title LDADiamondTest /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. /// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. -contract LdaDiamondTest is BaseDiamondTest, TestBaseRandomConstants { +contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice The diamond proxy under test. LDADiamond internal ldaDiamond; diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 3add13ab2..817a22424 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -15,7 +15,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LdaDiamondTest } from "../Periphery/Lda/utils/LdaDiamondTest.sol"; +import { LDADiamondTest } from "../Periphery/LDA/utils/LDADiamondTest.sol"; using stdJson for string; @@ -99,7 +99,7 @@ abstract contract TestBase is TestBaseRandomConstants, TestHelpers, DiamondTest, - LdaDiamondTest, + LDADiamondTest, ILiFi { address internal _facetTestContractAddress; @@ -244,7 +244,7 @@ abstract contract TestBase is // deploy & configure diamond diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); // deploy & configure ldaDiamond - LdaDiamondTest.setUp(); + LDADiamondTest.setUp(); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From 3131cd6c54c801fc5f6ad0fdda4df7a7cb87e60d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:22:24 +0200 Subject: [PATCH 119/220] changed folder name --- src/Periphery/{Lda => LDAa}/BaseRouteConstants.sol | 0 src/Periphery/{Lda => LDAa}/Errors/Errors.sol | 0 src/Periphery/{Lda => LDAa}/Facets/AlgebraFacet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/CoreRouteFacet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/CurveFacet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/IzumiV3Facet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/NativeWrapperFacet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/SyncSwapV2Facet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/UniV2StyleFacet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/UniV3StyleFacet.sol | 0 src/Periphery/{Lda => LDAa}/Facets/VelodromeV2Facet.sol | 0 src/Periphery/{Lda => LDAa}/LDADiamond.sol | 0 src/Periphery/{Lda => LDAa}/PoolCallbackAuthenticated.sol | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename src/Periphery/{Lda => LDAa}/BaseRouteConstants.sol (100%) rename src/Periphery/{Lda => LDAa}/Errors/Errors.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/AlgebraFacet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/CoreRouteFacet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/CurveFacet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/IzumiV3Facet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/NativeWrapperFacet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/SyncSwapV2Facet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/UniV2StyleFacet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/UniV3StyleFacet.sol (100%) rename src/Periphery/{Lda => LDAa}/Facets/VelodromeV2Facet.sol (100%) rename src/Periphery/{Lda => LDAa}/LDADiamond.sol (100%) rename src/Periphery/{Lda => LDAa}/PoolCallbackAuthenticated.sol (100%) diff --git a/src/Periphery/Lda/BaseRouteConstants.sol b/src/Periphery/LDAa/BaseRouteConstants.sol similarity index 100% rename from src/Periphery/Lda/BaseRouteConstants.sol rename to src/Periphery/LDAa/BaseRouteConstants.sol diff --git a/src/Periphery/Lda/Errors/Errors.sol b/src/Periphery/LDAa/Errors/Errors.sol similarity index 100% rename from src/Periphery/Lda/Errors/Errors.sol rename to src/Periphery/LDAa/Errors/Errors.sol diff --git a/src/Periphery/Lda/Facets/AlgebraFacet.sol b/src/Periphery/LDAa/Facets/AlgebraFacet.sol similarity index 100% rename from src/Periphery/Lda/Facets/AlgebraFacet.sol rename to src/Periphery/LDAa/Facets/AlgebraFacet.sol diff --git a/src/Periphery/Lda/Facets/CoreRouteFacet.sol b/src/Periphery/LDAa/Facets/CoreRouteFacet.sol similarity index 100% rename from src/Periphery/Lda/Facets/CoreRouteFacet.sol rename to src/Periphery/LDAa/Facets/CoreRouteFacet.sol diff --git a/src/Periphery/Lda/Facets/CurveFacet.sol b/src/Periphery/LDAa/Facets/CurveFacet.sol similarity index 100% rename from src/Periphery/Lda/Facets/CurveFacet.sol rename to src/Periphery/LDAa/Facets/CurveFacet.sol diff --git a/src/Periphery/Lda/Facets/IzumiV3Facet.sol b/src/Periphery/LDAa/Facets/IzumiV3Facet.sol similarity index 100% rename from src/Periphery/Lda/Facets/IzumiV3Facet.sol rename to src/Periphery/LDAa/Facets/IzumiV3Facet.sol diff --git a/src/Periphery/Lda/Facets/NativeWrapperFacet.sol b/src/Periphery/LDAa/Facets/NativeWrapperFacet.sol similarity index 100% rename from src/Periphery/Lda/Facets/NativeWrapperFacet.sol rename to src/Periphery/LDAa/Facets/NativeWrapperFacet.sol diff --git a/src/Periphery/Lda/Facets/SyncSwapV2Facet.sol b/src/Periphery/LDAa/Facets/SyncSwapV2Facet.sol similarity index 100% rename from src/Periphery/Lda/Facets/SyncSwapV2Facet.sol rename to src/Periphery/LDAa/Facets/SyncSwapV2Facet.sol diff --git a/src/Periphery/Lda/Facets/UniV2StyleFacet.sol b/src/Periphery/LDAa/Facets/UniV2StyleFacet.sol similarity index 100% rename from src/Periphery/Lda/Facets/UniV2StyleFacet.sol rename to src/Periphery/LDAa/Facets/UniV2StyleFacet.sol diff --git a/src/Periphery/Lda/Facets/UniV3StyleFacet.sol b/src/Periphery/LDAa/Facets/UniV3StyleFacet.sol similarity index 100% rename from src/Periphery/Lda/Facets/UniV3StyleFacet.sol rename to src/Periphery/LDAa/Facets/UniV3StyleFacet.sol diff --git a/src/Periphery/Lda/Facets/VelodromeV2Facet.sol b/src/Periphery/LDAa/Facets/VelodromeV2Facet.sol similarity index 100% rename from src/Periphery/Lda/Facets/VelodromeV2Facet.sol rename to src/Periphery/LDAa/Facets/VelodromeV2Facet.sol diff --git a/src/Periphery/Lda/LDADiamond.sol b/src/Periphery/LDAa/LDADiamond.sol similarity index 100% rename from src/Periphery/Lda/LDADiamond.sol rename to src/Periphery/LDAa/LDADiamond.sol diff --git a/src/Periphery/Lda/PoolCallbackAuthenticated.sol b/src/Periphery/LDAa/PoolCallbackAuthenticated.sol similarity index 100% rename from src/Periphery/Lda/PoolCallbackAuthenticated.sol rename to src/Periphery/LDAa/PoolCallbackAuthenticated.sol From eb7b1961cb214eaed18ef954eaf00421f1ee9b28 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:23:28 +0200 Subject: [PATCH 120/220] changed folder name --- src/Periphery/{LDAa => LDA}/BaseRouteConstants.sol | 0 src/Periphery/{LDAa => LDA}/Errors/Errors.sol | 0 src/Periphery/{LDAa => LDA}/Facets/AlgebraFacet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/CoreRouteFacet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/CurveFacet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/IzumiV3Facet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/NativeWrapperFacet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/SyncSwapV2Facet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/UniV2StyleFacet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/UniV3StyleFacet.sol | 0 src/Periphery/{LDAa => LDA}/Facets/VelodromeV2Facet.sol | 0 src/Periphery/{LDAa => LDA}/LDADiamond.sol | 0 src/Periphery/{LDAa => LDA}/PoolCallbackAuthenticated.sol | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename src/Periphery/{LDAa => LDA}/BaseRouteConstants.sol (100%) rename src/Periphery/{LDAa => LDA}/Errors/Errors.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/AlgebraFacet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/CoreRouteFacet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/CurveFacet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/IzumiV3Facet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/NativeWrapperFacet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/SyncSwapV2Facet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/UniV2StyleFacet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/UniV3StyleFacet.sol (100%) rename src/Periphery/{LDAa => LDA}/Facets/VelodromeV2Facet.sol (100%) rename src/Periphery/{LDAa => LDA}/LDADiamond.sol (100%) rename src/Periphery/{LDAa => LDA}/PoolCallbackAuthenticated.sol (100%) diff --git a/src/Periphery/LDAa/BaseRouteConstants.sol b/src/Periphery/LDA/BaseRouteConstants.sol similarity index 100% rename from src/Periphery/LDAa/BaseRouteConstants.sol rename to src/Periphery/LDA/BaseRouteConstants.sol diff --git a/src/Periphery/LDAa/Errors/Errors.sol b/src/Periphery/LDA/Errors/Errors.sol similarity index 100% rename from src/Periphery/LDAa/Errors/Errors.sol rename to src/Periphery/LDA/Errors/Errors.sol diff --git a/src/Periphery/LDAa/Facets/AlgebraFacet.sol b/src/Periphery/LDA/Facets/AlgebraFacet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/AlgebraFacet.sol rename to src/Periphery/LDA/Facets/AlgebraFacet.sol diff --git a/src/Periphery/LDAa/Facets/CoreRouteFacet.sol b/src/Periphery/LDA/Facets/CoreRouteFacet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/CoreRouteFacet.sol rename to src/Periphery/LDA/Facets/CoreRouteFacet.sol diff --git a/src/Periphery/LDAa/Facets/CurveFacet.sol b/src/Periphery/LDA/Facets/CurveFacet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/CurveFacet.sol rename to src/Periphery/LDA/Facets/CurveFacet.sol diff --git a/src/Periphery/LDAa/Facets/IzumiV3Facet.sol b/src/Periphery/LDA/Facets/IzumiV3Facet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/IzumiV3Facet.sol rename to src/Periphery/LDA/Facets/IzumiV3Facet.sol diff --git a/src/Periphery/LDAa/Facets/NativeWrapperFacet.sol b/src/Periphery/LDA/Facets/NativeWrapperFacet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/NativeWrapperFacet.sol rename to src/Periphery/LDA/Facets/NativeWrapperFacet.sol diff --git a/src/Periphery/LDAa/Facets/SyncSwapV2Facet.sol b/src/Periphery/LDA/Facets/SyncSwapV2Facet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/SyncSwapV2Facet.sol rename to src/Periphery/LDA/Facets/SyncSwapV2Facet.sol diff --git a/src/Periphery/LDAa/Facets/UniV2StyleFacet.sol b/src/Periphery/LDA/Facets/UniV2StyleFacet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/UniV2StyleFacet.sol rename to src/Periphery/LDA/Facets/UniV2StyleFacet.sol diff --git a/src/Periphery/LDAa/Facets/UniV3StyleFacet.sol b/src/Periphery/LDA/Facets/UniV3StyleFacet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/UniV3StyleFacet.sol rename to src/Periphery/LDA/Facets/UniV3StyleFacet.sol diff --git a/src/Periphery/LDAa/Facets/VelodromeV2Facet.sol b/src/Periphery/LDA/Facets/VelodromeV2Facet.sol similarity index 100% rename from src/Periphery/LDAa/Facets/VelodromeV2Facet.sol rename to src/Periphery/LDA/Facets/VelodromeV2Facet.sol diff --git a/src/Periphery/LDAa/LDADiamond.sol b/src/Periphery/LDA/LDADiamond.sol similarity index 100% rename from src/Periphery/LDAa/LDADiamond.sol rename to src/Periphery/LDA/LDADiamond.sol diff --git a/src/Periphery/LDAa/PoolCallbackAuthenticated.sol b/src/Periphery/LDA/PoolCallbackAuthenticated.sol similarity index 100% rename from src/Periphery/LDAa/PoolCallbackAuthenticated.sol rename to src/Periphery/LDA/PoolCallbackAuthenticated.sol From fbad9a01bdca8bf354e34bdd315afbe123f68c43 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:24:39 +0200 Subject: [PATCH 121/220] changed folder name --- test/solidity/Periphery/{Lda => LDAa}/BaseCoreRouteTest.t.sol | 0 .../Periphery/{Lda/BaseDexFacet.t.sol => LDAa/BaseDEXFacet.t.sol} | 0 .../BaseDEXFacetWithCallback.t.sol} | 0 .../BaseUniV2StyleDEXFacet.t.sol} | 0 .../BaseUniV3StyleDEXFacet.t.sol} | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/AlgebraFacet.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/CoreRouteFacet.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/CurveFacet.t.sol | 0 .../Periphery/{Lda => LDAa}/Facets/EnosysDexV3Facet.t.sol | 0 .../Periphery/{Lda => LDAa}/Facets/HyperswapV3Facet.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/IzumiV3Facet.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/LaminarV3Facet.t.sol | 0 .../Periphery/{Lda => LDAa}/Facets/NativeWrapperFacet.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/PancakeV2.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/RabbitSwapV3.t.sol | 0 .../solidity/Periphery/{Lda => LDAa}/Facets/SyncSwapV2Facet.t.sol | 0 .../Periphery/{Lda => LDAa}/Facets/VelodromeV2Facet.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/Facets/XSwapV3Facet.t.sol | 0 test/solidity/Periphery/{Lda => LDAa}/utils/LdaDiamondTest.sol | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename test/solidity/Periphery/{Lda => LDAa}/BaseCoreRouteTest.t.sol (100%) rename test/solidity/Periphery/{Lda/BaseDexFacet.t.sol => LDAa/BaseDEXFacet.t.sol} (100%) rename test/solidity/Periphery/{Lda/BaseDexFacetWithCallback.t.sol => LDAa/BaseDEXFacetWithCallback.t.sol} (100%) rename test/solidity/Periphery/{Lda/BaseUniV2StyleDexFacet.t.sol => LDAa/BaseUniV2StyleDEXFacet.t.sol} (100%) rename test/solidity/Periphery/{Lda/BaseUniV3StyleDexFacet.t.sol => LDAa/BaseUniV3StyleDEXFacet.t.sol} (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/AlgebraFacet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/CoreRouteFacet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/CurveFacet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/EnosysDexV3Facet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/HyperswapV3Facet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/IzumiV3Facet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/LaminarV3Facet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/NativeWrapperFacet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/PancakeV2.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/RabbitSwapV3.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/SyncSwapV2Facet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/VelodromeV2Facet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/Facets/XSwapV3Facet.t.sol (100%) rename test/solidity/Periphery/{Lda => LDAa}/utils/LdaDiamondTest.sol (100%) diff --git a/test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDAa/BaseCoreRouteTest.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/BaseCoreRouteTest.t.sol rename to test/solidity/Periphery/LDAa/BaseCoreRouteTest.t.sol diff --git a/test/solidity/Periphery/Lda/BaseDexFacet.t.sol b/test/solidity/Periphery/LDAa/BaseDEXFacet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/BaseDexFacet.t.sol rename to test/solidity/Periphery/LDAa/BaseDEXFacet.t.sol diff --git a/test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol b/test/solidity/Periphery/LDAa/BaseDEXFacetWithCallback.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/BaseDexFacetWithCallback.t.sol rename to test/solidity/Periphery/LDAa/BaseDEXFacetWithCallback.t.sol diff --git a/test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol b/test/solidity/Periphery/LDAa/BaseUniV2StyleDEXFacet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/BaseUniV2StyleDexFacet.t.sol rename to test/solidity/Periphery/LDAa/BaseUniV2StyleDEXFacet.t.sol diff --git a/test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol b/test/solidity/Periphery/LDAa/BaseUniV3StyleDEXFacet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/BaseUniV3StyleDexFacet.t.sol rename to test/solidity/Periphery/LDAa/BaseUniV3StyleDEXFacet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/LDAa/Facets/AlgebraFacet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/AlgebraFacet.t.sol rename to test/solidity/Periphery/LDAa/Facets/AlgebraFacet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/LDAa/Facets/CoreRouteFacet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/CoreRouteFacet.t.sol rename to test/solidity/Periphery/LDAa/Facets/CoreRouteFacet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol b/test/solidity/Periphery/LDAa/Facets/CurveFacet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/CurveFacet.t.sol rename to test/solidity/Periphery/LDAa/Facets/CurveFacet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/LDAa/Facets/EnosysDexV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/EnosysDexV3Facet.t.sol rename to test/solidity/Periphery/LDAa/Facets/EnosysDexV3Facet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/LDAa/Facets/HyperswapV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/HyperswapV3Facet.t.sol rename to test/solidity/Periphery/LDAa/Facets/HyperswapV3Facet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/LDAa/Facets/IzumiV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/IzumiV3Facet.t.sol rename to test/solidity/Periphery/LDAa/Facets/IzumiV3Facet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/LDAa/Facets/LaminarV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/LaminarV3Facet.t.sol rename to test/solidity/Periphery/LDAa/Facets/LaminarV3Facet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/LDAa/Facets/NativeWrapperFacet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/NativeWrapperFacet.t.sol rename to test/solidity/Periphery/LDAa/Facets/NativeWrapperFacet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol b/test/solidity/Periphery/LDAa/Facets/PancakeV2.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/PancakeV2.t.sol rename to test/solidity/Periphery/LDAa/Facets/PancakeV2.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/LDAa/Facets/RabbitSwapV3.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/RabbitSwapV3.t.sol rename to test/solidity/Periphery/LDAa/Facets/RabbitSwapV3.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/LDAa/Facets/SyncSwapV2Facet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/SyncSwapV2Facet.t.sol rename to test/solidity/Periphery/LDAa/Facets/SyncSwapV2Facet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/LDAa/Facets/VelodromeV2Facet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/VelodromeV2Facet.t.sol rename to test/solidity/Periphery/LDAa/Facets/VelodromeV2Facet.t.sol diff --git a/test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/LDAa/Facets/XSwapV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/Lda/Facets/XSwapV3Facet.t.sol rename to test/solidity/Periphery/LDAa/Facets/XSwapV3Facet.t.sol diff --git a/test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol b/test/solidity/Periphery/LDAa/utils/LdaDiamondTest.sol similarity index 100% rename from test/solidity/Periphery/Lda/utils/LdaDiamondTest.sol rename to test/solidity/Periphery/LDAa/utils/LdaDiamondTest.sol From 06f73b1bed2e9a70f04a5a3efd57beda44c218fd Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:24:58 +0200 Subject: [PATCH 122/220] changed folder name --- test/solidity/Periphery/{LDAa => LDA}/BaseCoreRouteTest.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/BaseDEXFacet.t.sol | 0 .../Periphery/{LDAa => LDA}/BaseDEXFacetWithCallback.t.sol | 0 .../solidity/Periphery/{LDAa => LDA}/BaseUniV2StyleDEXFacet.t.sol | 0 .../solidity/Periphery/{LDAa => LDA}/BaseUniV3StyleDEXFacet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/AlgebraFacet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/CoreRouteFacet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/CurveFacet.t.sol | 0 .../Periphery/{LDAa => LDA}/Facets/EnosysDexV3Facet.t.sol | 0 .../Periphery/{LDAa => LDA}/Facets/HyperswapV3Facet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/IzumiV3Facet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/LaminarV3Facet.t.sol | 0 .../Periphery/{LDAa => LDA}/Facets/NativeWrapperFacet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/PancakeV2.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/RabbitSwapV3.t.sol | 0 .../solidity/Periphery/{LDAa => LDA}/Facets/SyncSwapV2Facet.t.sol | 0 .../Periphery/{LDAa => LDA}/Facets/VelodromeV2Facet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/Facets/XSwapV3Facet.t.sol | 0 test/solidity/Periphery/{LDAa => LDA}/utils/LdaDiamondTest.sol | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename test/solidity/Periphery/{LDAa => LDA}/BaseCoreRouteTest.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/BaseDEXFacet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/BaseDEXFacetWithCallback.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/BaseUniV2StyleDEXFacet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/BaseUniV3StyleDEXFacet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/AlgebraFacet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/CoreRouteFacet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/CurveFacet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/EnosysDexV3Facet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/HyperswapV3Facet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/IzumiV3Facet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/LaminarV3Facet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/NativeWrapperFacet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/PancakeV2.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/RabbitSwapV3.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/SyncSwapV2Facet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/VelodromeV2Facet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/Facets/XSwapV3Facet.t.sol (100%) rename test/solidity/Periphery/{LDAa => LDA}/utils/LdaDiamondTest.sol (100%) diff --git a/test/solidity/Periphery/LDAa/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/BaseCoreRouteTest.t.sol rename to test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol diff --git a/test/solidity/Periphery/LDAa/BaseDEXFacet.t.sol b/test/solidity/Periphery/LDA/BaseDEXFacet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/BaseDEXFacet.t.sol rename to test/solidity/Periphery/LDA/BaseDEXFacet.t.sol diff --git a/test/solidity/Periphery/LDAa/BaseDEXFacetWithCallback.t.sol b/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/BaseDEXFacetWithCallback.t.sol rename to test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol diff --git a/test/solidity/Periphery/LDAa/BaseUniV2StyleDEXFacet.t.sol b/test/solidity/Periphery/LDA/BaseUniV2StyleDEXFacet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/BaseUniV2StyleDEXFacet.t.sol rename to test/solidity/Periphery/LDA/BaseUniV2StyleDEXFacet.t.sol diff --git a/test/solidity/Periphery/LDAa/BaseUniV3StyleDEXFacet.t.sol b/test/solidity/Periphery/LDA/BaseUniV3StyleDEXFacet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/BaseUniV3StyleDEXFacet.t.sol rename to test/solidity/Periphery/LDA/BaseUniV3StyleDEXFacet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/LDA/Facets/AlgebraFacet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/AlgebraFacet.t.sol rename to test/solidity/Periphery/LDA/Facets/AlgebraFacet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/CoreRouteFacet.t.sol rename to test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/CurveFacet.t.sol b/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/CurveFacet.t.sol rename to test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/EnosysDexV3Facet.t.sol b/test/solidity/Periphery/LDA/Facets/EnosysDexV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/EnosysDexV3Facet.t.sol rename to test/solidity/Periphery/LDA/Facets/EnosysDexV3Facet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/HyperswapV3Facet.t.sol b/test/solidity/Periphery/LDA/Facets/HyperswapV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/HyperswapV3Facet.t.sol rename to test/solidity/Periphery/LDA/Facets/HyperswapV3Facet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/IzumiV3Facet.t.sol b/test/solidity/Periphery/LDA/Facets/IzumiV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/IzumiV3Facet.t.sol rename to test/solidity/Periphery/LDA/Facets/IzumiV3Facet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/LaminarV3Facet.t.sol b/test/solidity/Periphery/LDA/Facets/LaminarV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/LaminarV3Facet.t.sol rename to test/solidity/Periphery/LDA/Facets/LaminarV3Facet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/NativeWrapperFacet.t.sol rename to test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/PancakeV2.t.sol b/test/solidity/Periphery/LDA/Facets/PancakeV2.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/PancakeV2.t.sol rename to test/solidity/Periphery/LDA/Facets/PancakeV2.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/RabbitSwapV3.t.sol b/test/solidity/Periphery/LDA/Facets/RabbitSwapV3.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/RabbitSwapV3.t.sol rename to test/solidity/Periphery/LDA/Facets/RabbitSwapV3.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/SyncSwapV2Facet.t.sol b/test/solidity/Periphery/LDA/Facets/SyncSwapV2Facet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/SyncSwapV2Facet.t.sol rename to test/solidity/Periphery/LDA/Facets/SyncSwapV2Facet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/VelodromeV2Facet.t.sol rename to test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol diff --git a/test/solidity/Periphery/LDAa/Facets/XSwapV3Facet.t.sol b/test/solidity/Periphery/LDA/Facets/XSwapV3Facet.t.sol similarity index 100% rename from test/solidity/Periphery/LDAa/Facets/XSwapV3Facet.t.sol rename to test/solidity/Periphery/LDA/Facets/XSwapV3Facet.t.sol diff --git a/test/solidity/Periphery/LDAa/utils/LdaDiamondTest.sol b/test/solidity/Periphery/LDA/utils/LdaDiamondTest.sol similarity index 100% rename from test/solidity/Periphery/LDAa/utils/LdaDiamondTest.sol rename to test/solidity/Periphery/LDA/utils/LdaDiamondTest.sol From 2957af5cc4c27aab288eada1da140e8061be596b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:25:50 +0200 Subject: [PATCH 123/220] changed file name --- .../LDA/utils/{LdaDiamondTest.sol => LDADiamondTest2.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/solidity/Periphery/LDA/utils/{LdaDiamondTest.sol => LDADiamondTest2.sol} (100%) diff --git a/test/solidity/Periphery/LDA/utils/LdaDiamondTest.sol b/test/solidity/Periphery/LDA/utils/LDADiamondTest2.sol similarity index 100% rename from test/solidity/Periphery/LDA/utils/LdaDiamondTest.sol rename to test/solidity/Periphery/LDA/utils/LDADiamondTest2.sol From a10b05d288d2ea73e0391b07e43c88bdd2e8b03a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 01:26:05 +0200 Subject: [PATCH 124/220] changed folder name --- .../LDA/utils/{LDADiamondTest2.sol => LDADiamondTest.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/solidity/Periphery/LDA/utils/{LDADiamondTest2.sol => LDADiamondTest.sol} (100%) diff --git a/test/solidity/Periphery/LDA/utils/LDADiamondTest2.sol b/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol similarity index 100% rename from test/solidity/Periphery/LDA/utils/LDADiamondTest2.sol rename to test/solidity/Periphery/LDA/utils/LDADiamondTest.sol From 7475797a3de09d56be9552bfc0f8691f156bcf0d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 12:15:53 +0200 Subject: [PATCH 125/220] Add KatanaV3Facet contract and corresponding unit tests for swap functionality --- src/Periphery/LDA/Facets/KatanaV3Facet.sol | 84 +++++++ .../Periphery/LDA/Facets/KatanaV3Facet.t.sol | 230 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 src/Periphery/LDA/Facets/KatanaV3Facet.sol create mode 100644 test/solidity/Periphery/LDA/Facets/KatanaV3Facet.t.sol diff --git a/src/Periphery/LDA/Facets/KatanaV3Facet.sol b/src/Periphery/LDA/Facets/KatanaV3Facet.sol new file mode 100644 index 000000000..4524bc118 --- /dev/null +++ b/src/Periphery/LDA/Facets/KatanaV3Facet.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { IKatanaV3Pool } from "lifi/Interfaces/KatanaV3/IKatanaV3Pool.sol"; +import { IKatanaV3Governance } from "lifi/Interfaces/KatanaV3/IKatanaV3Governance.sol"; +import { IKatanaV3AggregateRouter } from "lifi/Interfaces/KatanaV3/IKatanaV3AggregateRouter.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseRouteConstants } from "../BaseRouteConstants.sol"; + +/// @title KatanaV3Facet +/// @author LI.FI (https://li.fi) +/// @notice Handles KatanaV3 swaps. Callbacks are on the router contract itself. +/// @custom:version 1.0.0 +contract KatanaV3Facet is BaseRouteConstants { + using LibPackedStream for uint256; + + // ==== Constants ==== + /// @dev KatanaV3 swap command for exact input + bytes internal constant KATANA_V3_SWAP_EXACT_IN = hex"00"; + + // ==== External Functions ==== + /// @notice Performs a swap through KatanaV3 pools + /// @dev This function handles swaps through KatanaV3 pools. + /// @param swapData Encoded swap parameters [pool, direction, recipient] + /// @param from Where to take liquidity for swap + /// @param tokenIn Input token + /// @param amountIn Amount of tokenIn to take for swap + function swapKatanaV3( + bytes memory swapData, + address from, + address tokenIn, + uint256 amountIn + ) external { + uint256 stream = LibPackedStream.createStream(swapData); + + address pool = stream.readAddress(); + bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; + address recipient = stream.readAddress(); + + if (pool == address(0) || recipient == address(0)) + revert InvalidCallData(); + + // get router address from pool governance + address governance = IKatanaV3Pool(pool).governance(); + address router = IKatanaV3Governance(governance).getRouter(); + + // get pool info for constructing the path + uint24 fee = IKatanaV3Pool(pool).fee(); + + // determine tokenOut based on swap direction + address tokenOut = direction + ? IKatanaV3Pool(pool).token1() + : IKatanaV3Pool(pool).token0(); + + // transfer tokens to the router + if (from == msg.sender) { + LibAsset.transferFromERC20(tokenIn, msg.sender, router, amountIn); + } else if (from == address(this)) { + LibAsset.transferERC20(tokenIn, router, amountIn); + } + + // encode the inputs for V3_SWAP_EXACT_IN + // set payerIsUser to false since we already transferred tokens to the router + bytes[] memory inputs = new bytes[](1); + inputs[0] = abi.encode( + recipient, // recipient + amountIn, // amountIn + 0, // amountOutMin (0, as we handle slippage at higher level) + abi.encodePacked(tokenIn, fee, tokenOut), // construct the path for V3 swap (tokenIn -> tokenOut with fee) + false // payerIsUser (false since tokens are already in router) + ); + + // call the router's execute function + // first parameter for execute is the command for V3_SWAP_EXACT_IN (0x00) + IKatanaV3AggregateRouter(router).execute( + KATANA_V3_SWAP_EXACT_IN, + inputs + ); + + // katanaV3SwapCallback implementation is in the router contract itself + } +} diff --git a/test/solidity/Periphery/LDA/Facets/KatanaV3Facet.t.sol b/test/solidity/Periphery/LDA/Facets/KatanaV3Facet.t.sol new file mode 100644 index 000000000..a284bc528 --- /dev/null +++ b/test/solidity/Periphery/LDA/Facets/KatanaV3Facet.t.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { KatanaV3Facet } from "lifi/Periphery/LDA/Facets/KatanaV3Facet.sol"; +import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; +import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; + +/// @title KatanaV3FacetTest +/// @notice Ronin Katana V3 +contract KatanaV3FacetTest is BaseDEXFacetTest { + /// @notice Facet proxy bound to the diamond after setup. + KatanaV3Facet internal katanaV3Facet; + + // ==== Types ==== + + /// @notice Swap data payload packed for KatanaV3Facet. + struct KatanaV3SwapData { + address pool; + SwapDirection direction; + address destinationAddress; + } + + // ==== Setup Functions ==== + + /// @notice Picks Optimism fork and block height. + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + networkName: "ronin", + blockNumber: 47105304 + }); + } + + /// @notice Deploys facet and returns its swap selector. + function _createFacetAndSelectors() + internal + override + returns (address, bytes4[] memory) + { + katanaV3Facet = new KatanaV3Facet(); + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = KatanaV3Facet.swapKatanaV3.selector; + return (address(katanaV3Facet), functionSelectors); + } + + /// @notice Sets the facet instance to the diamond proxy. + function _setFacetInstance(address payable ldaDiamond) internal override { + katanaV3Facet = KatanaV3Facet(ldaDiamond); + } + + /// @notice Assigns tokens used in tests; pool addresses are resolved per-test from the router. + function _setupDexEnv() internal override { + tokenIn = IERC20(0x0B7007c13325C48911F73A2daD5FA5dCBf808aDc); // USDC + tokenOut = IERC20(0xe514d9DEB7966c8BE0ca922de8a064264eA6bcd4); // WRAPPED_RON + poolInOut = 0x392d372F2A51610E9AC5b741379D5631Ca9A1c7f; // USDC_WRAPPED_RON_POOL + } + + /// @notice Default amount for 6-decimal tokens on Optimism. + function _getDefaultAmountForTokenIn() + internal + pure + override + returns (uint256) + { + return 1_000 * 1e6; + } + + // ==== Test Cases ==== + + function test_CanSwap() public override { + // Transfer 1 000 crvUSD from whale to USER_SENDER + deal(address(tokenIn), USER_SENDER, _getDefaultAmountForTokenIn()); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildKatanaV3SwapData( + KatanaV3SwapData({ + pool: poolInOut, + direction: SwapDirection.Token0ToToken1, + destinationAddress: address(USER_SENDER) + }) + ); + + _buildRouteAndExecuteAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeUserERC20 + }), + swapData + ); + + vm.stopPrank(); + } + + function test_CanSwap_FromDexAggregator() public override { + // Fund the aggregator with 1000 USDC + deal( + address(tokenIn), + address(ldaDiamond), + _getDefaultAmountForTokenIn() + ); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildKatanaV3SwapData( + KatanaV3SwapData({ + pool: poolInOut, + direction: SwapDirection.Token0ToToken1, + destinationAddress: address(USER_SENDER) + }) + ); + + _buildRouteAndExecuteAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn() - 1, // Account for slot-undrain + minOut: 0, + sender: address(ldaDiamond), + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeSelfERC20 + }), + swapData + ); + + vm.stopPrank(); + } + + function test_CanSwap_MultiHop() public override { + // SKIPPED: KatanaV3 multi-hop unsupported due to AS requirement. + // KatanaV3 (being a similar implementation to UniV3) does not support a "one-pool" second hop today, + // because the aggregator (ProcessOnePool) always passes amountSpecified = 0 into + // the pool.swap call. UniV3-style pools immediately revert on + // require(amountSpecified != 0, 'AS'), so you can't chain two V3 pools in a single processRoute invocation. + } + + function testRevert_KatanaV3InvalidPool() public { + vm.startPrank(USER_SENDER); + + // build route with invalid pool address + bytes memory route = _buildKatanaV3SwapData( + KatanaV3SwapData({ + pool: address(0), + direction: SwapDirection.Token0ToToken1, + destinationAddress: USER_SENDER + }) + ); + + _buildRouteAndExecuteAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeUserERC20 + }), + route, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + + function testRevert_KatanaV3InvalidRecipient() public { + vm.startPrank(USER_SENDER); + + // build route with invalid recipient + bytes memory route = _buildKatanaV3SwapData( + KatanaV3SwapData({ + pool: poolInOut, + direction: SwapDirection.Token0ToToken1, + destinationAddress: address(0) // invalid recipient + }) + ); + + _buildRouteAndExecuteAndVerifySwap( + SwapTestParams({ + tokenIn: address(tokenIn), + tokenOut: address(tokenOut), + amountIn: _getDefaultAmountForTokenIn(), + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeUserERC20 + }), + route, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + + /// @notice Empty test as KatanaV3 does not use callbacks for regular swaps + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + /// @dev Note: While KatanaV3 has flashloan callbacks, they are separate from swap callbacks + function testRevert_CallbackFromUnexpectedSender() public override { + // KatanaV3 does not use callbacks for swaps - test intentionally empty + } + + /// @notice Empty test as KatanaV3 does not use callbacks for regular swaps + /// @dev Explicitly left empty as this DEX's architecture doesn't require callback verification + /// @dev Note: While KatanaV3 has flashloan callbacks, they are separate from swap callbacks + function testRevert_SwapWithoutCallback() public override { + // KatanaV3 does not use callbacks for swaps - test intentionally empty + } + + // ==== Helper Functions ==== + + /// @notice Encodes swap payload for KatanaV3Facet.swapKatanaV3. + /// @param params pool/direction/destinationAddress/callback status. + /// @return Packed bytes payload. + function _buildKatanaV3SwapData( + KatanaV3SwapData memory params + ) private pure returns (bytes memory) { + return + abi.encodePacked( + KatanaV3Facet.swapKatanaV3.selector, + params.pool, + uint8(params.direction), + params.destinationAddress + ); + } +} From 5bb5f3b4cc7ef1e5e27ca6422cc6cf3145581d65 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 13:04:41 +0200 Subject: [PATCH 126/220] Update sentinel address documentation --- src/Periphery/LDA/BaseRouteConstants.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Periphery/LDA/BaseRouteConstants.sol b/src/Periphery/LDA/BaseRouteConstants.sol index a457825db..e0dc875eb 100644 --- a/src/Periphery/LDA/BaseRouteConstants.sol +++ b/src/Periphery/LDA/BaseRouteConstants.sol @@ -9,7 +9,7 @@ pragma solidity ^0.8.17; abstract contract BaseRouteConstants { /// @dev Constant indicating swap direction from token0 to token1 uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; - /// @dev A sentinel address (0x0) used in the `from` parameter of a swap. + /// @dev A sentinel address (address(1)) used in the `from` parameter of a swap. /// It signals that the input tokens for the swap are already held by the /// receiving contract (e.g., from a previous swap in a multi-step route). /// This tells the facet to use its current token balance instead of From d845798dd5669dfbdbc3e816b1b93ba7ad677694 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 13:05:59 +0200 Subject: [PATCH 127/220] Fix type conversion for fromIndex and toIndex in CurveFacet contract --- src/Periphery/LDA/Facets/CurveFacet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Periphery/LDA/Facets/CurveFacet.sol b/src/Periphery/LDA/Facets/CurveFacet.sol index 25574a5e1..2e477fa22 100644 --- a/src/Periphery/LDA/Facets/CurveFacet.sol +++ b/src/Periphery/LDA/Facets/CurveFacet.sol @@ -72,8 +72,8 @@ contract CurveFacet is BaseRouteConstants { address pool = stream.readAddress(); bool isV2 = stream.readUint8() > 0; // Convert uint8 to bool. 0 = V1, 1 = V2 - int128 fromIndex = int8(stream.readUint8()); - int128 toIndex = int8(stream.readUint8()); + int128 fromIndex = int128(uint128(stream.readUint8())); + int128 toIndex = int128(uint128(stream.readUint8())); address destinationAddress = stream.readAddress(); address tokenOut = stream.readAddress(); From ff1b51116be0572da65ffde07d246deb233e80f0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 13:13:26 +0200 Subject: [PATCH 128/220] Add revert condition for native ETH input in V2 pools and corresponding unit test --- src/Periphery/LDA/Facets/CurveFacet.sol | 1 + .../Periphery/LDA/Facets/CurveFacet.t.sol | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/Periphery/LDA/Facets/CurveFacet.sol b/src/Periphery/LDA/Facets/CurveFacet.sol index 2e477fa22..99f5f8038 100644 --- a/src/Periphery/LDA/Facets/CurveFacet.sol +++ b/src/Periphery/LDA/Facets/CurveFacet.sol @@ -99,6 +99,7 @@ contract CurveFacet is BaseRouteConstants { } if (isV2) { + if (isNativeIn) revert InvalidCallData(); if (from == FUNDS_IN_RECEIVER) { // Optimistic NG hop: tokens already sent to pool by previous hop. // NG requires _dx > 0 and asserts actual delta >= _dx. diff --git a/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol b/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol index 50c7a84f3..f7d924daa 100644 --- a/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol @@ -287,6 +287,42 @@ contract CurveFacetTest is BaseDEXFacetTest { vm.stopPrank(); } + /// @notice Tests that V2/NG pools reject native ETH input + /// @dev Verifies the InvalidCallData revert condition when sending ETH to modern pools + function testRevert_NativeETHWithV2Pool() public { + uint256 amountIn = 1 ether; + vm.deal(USER_SENDER, amountIn); + + vm.startPrank(USER_SENDER); + + bytes memory swapData = _buildCurveSwapData( + CurveSwapParams({ + pool: poolInMid, // Using a V2 pool + isV2: true, + fromIndex: 0, + toIndex: 1, + destinationAddress: USER_SENDER, + tokenOut: address(tokenIn) + }) + ); + + _buildRouteAndExecuteAndVerifySwap( + SwapTestParams({ + tokenIn: address(0), // Native ETH + tokenOut: address(tokenIn), + amountIn: amountIn, + minOut: 0, + sender: USER_SENDER, + destinationAddress: USER_SENDER, + commandType: CommandType.DistributeNative + }), + swapData, + InvalidCallData.selector + ); + + vm.stopPrank(); + } + /// @notice Tests that the facet reverts on zero pool or destination addresses. /// @dev Verifies the InvalidCallData revert condition in swapCurve. function testRevert_InvalidPoolOrDestinationAddress() public { From b20a5b1f6bc3a6cb2400854ddfa88bf8f148ab16 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 13:13:40 +0200 Subject: [PATCH 129/220] Improve documentation for unwrapNative function in NativeWrapperFacet contract --- src/Periphery/LDA/Facets/NativeWrapperFacet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Periphery/LDA/Facets/NativeWrapperFacet.sol b/src/Periphery/LDA/Facets/NativeWrapperFacet.sol index fdf7b800c..082dc71bd 100644 --- a/src/Periphery/LDA/Facets/NativeWrapperFacet.sol +++ b/src/Periphery/LDA/Facets/NativeWrapperFacet.sol @@ -18,8 +18,8 @@ contract NativeWrapperFacet is BaseRouteConstants { /// @notice Unwraps WETH to native ETH /// @dev Handles unwrapping WETH and sending native ETH to recipient /// @param swapData Encoded swap parameters [destinationAddress] - /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; - /// otherwise assumes tokens are at receiver address (FUNDS_IN_RECEIVER) + /// @param from Token source. If from == msg.sender, pull tokens via transferFrom. + /// Otherwise, assume tokens are already held by this contract (e.g., address(this) or FUNDS_IN_RECEIVER). /// @param tokenIn WETH token address /// @param amountIn Amount of WETH to unwrap function unwrapNative( From db43d443d7881b2f1a697d1da42fc5c82404bdd4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 13:19:44 +0200 Subject: [PATCH 130/220] Update CoreRouteFacet instantiation and fix typo in callback documentation --- test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol | 2 +- test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol index 553a61bef..0cf543ff6 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol @@ -127,7 +127,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @notice Internal helper to deploy CoreRouteFacet and add its `processRoute` selector. /// @dev Sets `coreRouteFacet` to the diamond proxy after cut. function _addCoreRouteFacet() internal { - coreRouteFacet = new CoreRouteFacet(USER_DIAMOND_OWNER); + coreRouteFacet = new CoreRouteFacet(USER_LDA_DIAMOND_OWNER); bytes4[] memory selectors = new bytes4[](1); selectors[0] = CoreRouteFacet.processRoute.selector; addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); diff --git a/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol b/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol index 104953506..9acb87378 100644 --- a/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol +++ b/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol @@ -18,7 +18,7 @@ abstract contract BaseDEXFacetWithCallbackTest is BaseDEXFacetTest { /// @notice Builds swap data that arms callback verification for the DEX under test. /// @param pool Pool expected to invoke the callback. - /// @param destinationAddress Destionation address of swap proceeds. + /// @param destinationAddress Destination address of swap proceeds. /// @return swapData Encoded payload that triggers the DEX callback path. function _buildCallbackSwapData( address pool, From b989300239d4a23f166dc5ac669fc8a644e77874 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 13:53:33 +0200 Subject: [PATCH 131/220] Update CurveFacet and related tests to clarify token sourcing and fee-on-transfer behavior in documentation --- src/Periphery/LDA/Facets/CurveFacet.sol | 4 ++-- test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol | 12 ++++++------ test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol | 2 +- .../Periphery/LDA/Facets/VelodromeV2Facet.t.sol | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Periphery/LDA/Facets/CurveFacet.sol b/src/Periphery/LDA/Facets/CurveFacet.sol index 99f5f8038..572e53730 100644 --- a/src/Periphery/LDA/Facets/CurveFacet.sol +++ b/src/Periphery/LDA/Facets/CurveFacet.sol @@ -48,7 +48,7 @@ contract CurveFacet is BaseRouteConstants { /// - `from` controls token sourcing: /// - if `from == msg.sender` and amountIn > 0 - facet pulls `amountIn` from caller, /// - if `from != msg.sender` - tokens are assumed to be already available (e.g., previous hop). - /// Special case (NG optimistic): if `isV2 == 1` and `from == address(0)`, the facet calls + /// Special case (NG optimistic): if `isV2 == 1` and `from == FUNDS_IN_RECEIVER` (address(1)), the facet calls /// `exchange_received` on NG pools (tokens must have been pre-sent to the pool). /// - Indices (i,j) must match the pool's coin ordering. /// - Native ETH handling: @@ -59,7 +59,7 @@ contract CurveFacet is BaseRouteConstants { /// - Native ETH not supported, use wrapped versions /// @param swapData Encoded swap parameters [pool, isV2, fromIndex, toIndex, destinationAddress, tokenOut] /// @param from Token source address; if equals msg.sender, tokens will be pulled; - /// if set to address(0) with isV2==1, signals NG optimistic hop (tokens pre-sent) + /// if set to FUNDS_IN_RECEIVER (address(1)) with isV2==1, signals NG optimistic hop (tokens pre-sent) /// @param tokenIn Input token address (address(0) for native ETH in legacy pools) /// @param amountIn Amount of input tokens (ignored for NG optimistic hop) function swapCurve( diff --git a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol index 0cf543ff6..d267f3d83 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol @@ -381,9 +381,9 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @param route Pre-built route bytes (single or multi-hop) /// @param expectedRevert Error selector that should be thrown by processRoute /// @dev Special overload for testing failure cases: - /// - For aggregator funds (DistributeSelfERC20), uses amountIn-1 to trigger errors + /// - For aggregator funds (DistributeSelfERC20), sends amountIn-1 to trigger errors /// - For user funds, approves full amountIn but sends amountIn-1 - /// - Sets minOut to 0 for testing focus + /// - Sets minOut to 0 to focus on specific error cases /// - Verifies exact error selector match function _executeAndVerifySwap( SwapTestParams memory params, @@ -432,18 +432,18 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @param params SwapTestParams for building and executing the swap /// @param swapData DEX-specific swap data to pack into the route /// @param expectedEvents Additional events to expect during execution - /// @param expectRevert Whether to treat tokenIn as fee-on-transfer for spent checking + /// @param isFeeOnTransferToken Whether to allow 1 wei difference in spent amount for fee-on-transfer tokens /// @param verification Route event verification configuration /// @dev Comprehensive helper that: /// - Builds route using _buildBaseRoute /// - Executes swap with full verification options - /// - Supports all verification features: events, fee-on-transfer, exact output + /// - Supports all verification features: events, fee-on-transfer tokens, exact output /// - Primarily used by complex test scenarios to keep code concise function _buildRouteAndExecuteAndVerifySwap( SwapTestParams memory params, bytes memory swapData, ExpectedEvent[] memory expectedEvents, - bool expectRevert, + bool isFeeOnTransferToken, RouteEventVerification memory verification ) internal { bytes memory route = _buildBaseRoute(params, swapData); @@ -451,7 +451,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { params, route, expectedEvents, - expectRevert, + isFeeOnTransferToken, verification ); } diff --git a/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol b/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol index f7d924daa..01d0f4f4b 100644 --- a/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/CurveFacet.t.sol @@ -462,7 +462,7 @@ contract CurveFacetTest is BaseDEXFacetTest { }), swapData, new ExpectedEvent[](0), - true // Allow for small balance differences due to stETH rebasing + true // Allow for small balance differences due to stETH rebasing (fee-on-transfer behavior) ); vm.stopPrank(); diff --git a/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol index 45cc47150..bbe5fed69 100644 --- a/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol @@ -715,7 +715,7 @@ contract VelodromeV2FacetTest is BaseDEXFacetTest { }), route, expectedEvents, - false, + false, // Not a fee-on-transfer token RouteEventVerification({ expectedExactOut: expectedOutput[1], checkData: true From f993b80072e25f16e65690221512cca56fb802d4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 14:28:53 +0200 Subject: [PATCH 132/220] Add LDA (LiFi DEX Aggregator) conventions to conventions.md --- conventions.md | 185 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/conventions.md b/conventions.md index 4a1b5ee11..9c20f31e0 100644 --- a/conventions.md +++ b/conventions.md @@ -379,6 +379,191 @@ We use Foundry as our primary development and testing framework. Foundry provide } ``` +## LDA (LiFi DEX Aggregator) Conventions + +The LDA (LiFi DEX Aggregator) is a specialized component within the LI.FI ecosystem that provides efficient, modular DEX integration capabilities through its own Diamond Standard implementation. + +### Architecture Overview + +#### Core Components + +- **LDADiamond.sol**: Base EIP-2535 Diamond Proxy Contract for LDA +- **CoreRouteFacet.sol**: Orchestrates route execution using direct function selector dispatch +- **BaseRouteConstants.sol**: Shared constants across DEX facets +- **PoolCallbackAuthenticated.sol**: Abstract contract providing pool callback authentication + +#### Location Structure + +``` +src/Periphery/LDA/ +├── LDADiamond.sol # LDA Diamond proxy implementation +├── BaseRouteConstants.sol # Common constants for DEX facets +├── PoolCallbackAuthenticated.sol # Callback authentication base +├── Facets/ # LDA-specific facets +│ ├── CoreRouteFacet.sol # Route orchestration +│ ├── UniV3StyleFacet.sol # UniV3-style DEX integrations +│ ├── UniV2StyleFacet.sol # UniV2-style DEX integrations +│ ├── NativeWrapperFacet.sol # Native token wrapping +│ └── {DexName}Facet.sol # Custom DEX integrations +└── Errors/ + └── Errors.sol # LDA-specific error definitions +``` + +### DEX Integration Decision Tree + +When integrating a new DEX, follow this decision tree: + +1. **Is the DEX a UniV3 fork (same logic, different callback name)?** + - ✅ Yes → Extend `UniV3StyleFacet.sol` with a new callback + - Add tests inheriting `BaseUniV3StyleDEXFacetTest` + +2. **Else, is it a UniV2-style fork?** + - ✅ Yes → No new facet needed + - Add tests inheriting `BaseUniV2StyleDEXFacetTest` + +3. **Else, does it use a callback?** + - ✅ Yes → Create new custom facet with swap function and callback + - Write tests inheriting `BaseDEXFacetWithCallbackTest` + +4. **Else** → Create new custom facet with swap function without callbacks + - Write tests inheriting `BaseDEXFacetTest` + +### LDA Facet Requirements + +#### Naming and Location + +- Must reside in `src/Periphery/LDA/Facets/` +- Names must include "Facet" suffix +- Use descriptive names (e.g., `UniV3StyleFacet`, `CurveFacet`) + +#### Required Inheritance + +- **BaseRouteConstants**: For common constants (`DIRECTION_TOKEN0_TO_TOKEN1`, `FUNDS_IN_RECEIVER`) +- **PoolCallbackAuthenticated**: For facets requiring callback verification +- **No ReentrancyGuard**: CoreRouteFacet handles reentrancy protection + +#### Function Patterns + +**Swap Functions:** +- Must follow pattern: `swap{DexName}(bytes memory swapData, address from, address tokenIn, uint256 amountIn)` +- Use `LibPackedStream` for efficient parameter unpacking +- Handle token transfers based on `from` parameter (if `from == msg.sender`, pull tokens) + +**Callback Functions:** +- Must use `onlyExpectedPool` modifier +- **IMPORTANT**: Callback function names are protocol-specific and cannot be guessed. You must inspect the target pool contract's interface directly in the block explorer or source code to determine the exact callback function name(s). Examples include `uniswapV3SwapCallback`, `pancakeV3SwapCallback`, `swapX2YCallback`, `swapY2XCallback`, etc. +- Use `LibCallbackAuthenticator` for pool verification + +#### Parameter Handling + +- **swapData Encoding**: Use packed encoding for efficiency + - Common pattern: `[pool, direction, destinationAddress]` + - Additional parameters as needed per DEX +- **Direction Parameter**: Use `uint8` where `1 = token0 -> token1`, `0 = token1 -> token0` +- **Validation**: Always validate pool addresses and amounts. For invalid inputs, revert with `InvalidCallData()` from `GenericErrors.sol` + +#### Error Handling + +- **LDA-specific errors**: Define in `src/Periphery/LDA/Errors/Errors.sol` +- **Generic errors**: Use existing errors from `src/Errors/GenericErrors.sol` + +### LDA Testing Conventions + +#### Test File Structure + +``` +test/solidity/Periphery/LDA/ +├── BaseCoreRouteTest.t.sol # Base route testing functionality +├── BaseDEXFacet.t.sol # Base for custom DEX tests +├── BaseDEXFacetWithCallback.t.sol # Base for callback-enabled DEX tests +├── BaseUniV3StyleDEXFacet.t.sol # Base for UniV3-style DEX tests +├── BaseUniV2StyleDEXFacet.t.sol # Base for UniV2-style DEX tests +└── Facets/ + └── {DexName}Facet.t.sol # Specific DEX implementation tests +``` + +#### Test Implementation Requirements + +**All LDA DEX tests must implement:** + +1. **`_setupForkConfig()`**: Configure network and block number + - Use valid `networkName` from `config/networks.json` + - **IMPORTANT**: You must manually specify a block number where the target pools have healthy liquidity. Do not guess block numbers - check the pool's transaction history on a block explorer to find a recent block with sufficient reserves for testing + - Example: + ```solidity + function _setupForkConfig() internal override { + forkConfig = ForkConfig({ + networkName: "mainnet", + blockNumber: 18500000 // Manually verified block with healthy pool liquidity + }); + } + ``` + +2. **`_createFacetAndSelectors()`**: Deploy facet and return selectors + - Return facet address and function selectors for diamond cut + - Include both swap function and callback selectors (if applicable) + +3. **`_setFacetInstance()`**: Connect test handle to diamond proxy + - Set local facet instance to diamond address after cut + +4. **`_setupDexEnv()`**: Configure test tokens and pools + - Set `tokenIn`, `tokenOut`, `poolInOut` with sufficient liquidity + - Verify pools exist and have proper reserves + +#### Test Categories by Inheritance + +**BaseDEXFacetTest** (Custom DEX without callbacks): +- Implement single-hop and multi-hop tests (if supported) +- Focus on direct swap execution +- Example: `SyncSwapV2Facet.t.sol` + +**BaseDEXFacetWithCallbackTest** (Custom DEX with callbacks): +- Include callback verification tests +- Override `_getCallbackSelector()` for negative tests +- Implement `_deployNoCallbackPool()` if needed +- Example: `IzumiV3Facet.t.sol` + +**BaseUniV3StyleDEXFacetTest** (UniV3 forks): +- Override `_getCallbackSelector()` for DEX-specific callback +- No multi-hop support (skipped for UniV3-style) +- Example: `AlgebraFacet.t.sol` + +**BaseUniV2StyleDEXFacetTest** (UniV2 forks): +- Override `_getPoolFee()` for DEX-specific fee structure +- Support multi-hop routing +- Example: `VelodromeV2Facet.t.sol` + +#### Test Validation Requirements + +- **Liquidity Validation**: Ensure pools have sufficient liquidity for test amounts +- **Token Decimals**: Override `_getDefaultAmountForTokenIn()` for non-18 decimal tokens +- **Pool Verification**: Verify pool addresses exist and are correctly configured. Ensure the chosen fork block number has the pools deployed and contains sufficient liquidity for testing + +### LDA Deployment Scripts + +#### Location and Naming + +- **Location**: `script/deploy/facets/LDA/` +- **Naming**: `Deploy{DexName}Facet.s.sol` +- **Pattern**: Follow standard deployment script structure + +#### Deployment Script Structure + +```solidity +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { {DexName}Facet } from "lifi/Periphery/LDA/Facets/{DexName}Facet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("{DexName}Facet") {} + + function run() public returns ({DexName}Facet deployed) { + deployed = {DexName}Facet(deploy(type({DexName}Facet).creationCode)); + } +} +``` ## Solidity Test Conventions (.t.sol files) ### File Naming and Structure From e081ffbedbdca9bc38bd06106112b67e54a741d4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 14:39:09 +0200 Subject: [PATCH 133/220] fix --- test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol index d267f3d83..429dc5cd8 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol @@ -402,14 +402,10 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { vm.expectRevert(expectedRevert); { - uint256 sendAmount = params.commandType == - CommandType.DistributeSelfERC20 - ? params.amountIn - : params.amountIn - 1; if (LibAsset.isNativeAsset(params.tokenIn)) { - coreRouteFacet.processRoute{ value: sendAmount }( + coreRouteFacet.processRoute{ value: params.amountIn }( params.tokenIn, - sendAmount, + params.amountIn, params.tokenOut, 0, // minOut = 0 for tests params.destinationAddress, @@ -418,7 +414,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { } else { coreRouteFacet.processRoute( params.tokenIn, - sendAmount, + params.amountIn, params.tokenOut, 0, // minOut = 0 for tests params.destinationAddress, From 1e1b5036525d786986f9e3391b26dab7cbc6aa19 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 15:45:03 +0200 Subject: [PATCH 134/220] Add LDA facets including EmergencyPause, Ownership, and Periphery Registry with corresponding deployment scripts and tests --- docs/LDAPeripheryRegistryFacet.md | 27 ++ .../facets/DeployEmergencyPauseFacet.s.sol | 2 +- .../deploy/facets/LDA/DeployLDADiamond.s.sol | 37 +++ .../facets/LDA/DeployLDADiamondCutFacet.s.sol | 15 ++ .../LDA/DeployLDADiamondLoupeFacet.s.sol | 15 ++ .../LDA/DeployLDAEmergencyPauseFacet.s.sol | 32 +++ .../facets/LDA/DeployLDAOwnershipFacet.s.sol | 15 ++ .../LDA/DeployLDAPeripheryRegistryFacet.s.sol | 15 ++ .../facets/LDA/UpdateAlgebraFacet.s.sol | 13 + .../deploy/facets/LDA/UpdateCurveFacet.s.sol | 13 + .../facets/LDA/UpdateIzumiV3Facet.s.sol | 13 + .../LDA/UpdateLDAEmergencyPauseFacet.s.sol | 13 + .../facets/LDA/UpdateLDAOwnershipFacet.s.sol | 13 + .../facets/LDA/UpdateSyncSwapV2Facet.s.sol | 13 + .../facets/LDA/UpdateUniV2StyleFacet.s.sol | 13 + .../facets/LDA/UpdateUniV3StyleFacet.s.sol | 13 + .../DeployEmergencyPauseFacet.zksync.s.sol | 2 +- .../EmergencyPauseFacet.sol | 0 .../LDA/Facets/LDADiamondCutFacet.sol | 26 ++ .../LDA/Facets/LDADiamondLoupeFacet.sol | 89 +++++++ .../LDA/Facets/LDAEmergencyPauseFacet.sol | 247 ++++++++++++++++++ .../LDA/Facets/LDAOwnershipFacet.sol | 92 +++++++ .../LDA/Facets/LDAPeripheryRegistryFacet.sol | 57 ++++ .../EmergencyPauseFacet.fork.t.sol | 2 +- .../EmergencyPauseFacet.local.t.sol | 2 +- .../LDA/Facets/LDAOwnershipFacet.t.sol | 136 ++++++++++ .../Periphery/LDA/utils/LDADiamondTest.sol | 30 +-- test/solidity/utils/DiamondTest.sol | 2 +- 28 files changed, 927 insertions(+), 20 deletions(-) create mode 100644 docs/LDAPeripheryRegistryFacet.md create mode 100644 script/deploy/facets/LDA/DeployLDADiamond.s.sol create mode 100644 script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol create mode 100644 script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateCurveFacet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol rename src/{Security => Facets}/EmergencyPauseFacet.sol (100%) create mode 100644 src/Periphery/LDA/Facets/LDADiamondCutFacet.sol create mode 100644 src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol create mode 100644 src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol create mode 100644 src/Periphery/LDA/Facets/LDAOwnershipFacet.sol create mode 100644 src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol rename test/solidity/{Security => Facets}/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol (99%) rename test/solidity/{Security => Facets}/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol (99%) create mode 100644 test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol diff --git a/docs/LDAPeripheryRegistryFacet.md b/docs/LDAPeripheryRegistryFacet.md new file mode 100644 index 000000000..23ecabe61 --- /dev/null +++ b/docs/LDAPeripheryRegistryFacet.md @@ -0,0 +1,27 @@ +# Periphery Registry Facet + +## Description + +A simple facet for registering and keeping track of periphery contracts in LDA + +## How To Use + +This contract contract has two simple methods. One for registering a contract address with key +and another for retrieving that address by its key. + +Registering + +```solidity +/// @notice Registers a periphery contract address with a specified name +/// @param _name the name to register the contract address under +/// @param _contractAddress the address of the contract to register +function registerPeripheryContract(string calldata _name, address _contractAddress) +``` + +Retrieving + +```solidity +/// @notice Returns the registered contract address by its name +/// @param _name the registered name of the contract +function getPeripheryContract(string calldata _name) +``` diff --git a/script/deploy/facets/DeployEmergencyPauseFacet.s.sol b/script/deploy/facets/DeployEmergencyPauseFacet.s.sol index 87245eb7b..3fa8b8dec 100644 --- a/script/deploy/facets/DeployEmergencyPauseFacet.s.sol +++ b/script/deploy/facets/DeployEmergencyPauseFacet.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; -import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; contract DeployScript is DeployScriptBase { using stdJson for string; diff --git a/script/deploy/facets/LDA/DeployLDADiamond.s.sol b/script/deploy/facets/LDA/DeployLDADiamond.s.sol new file mode 100644 index 000000000..7b2efc356 --- /dev/null +++ b/script/deploy/facets/LDA/DeployLDADiamond.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; + +contract DeployScript is DeployScriptBase { + using stdJson for string; + + constructor() DeployScriptBase("LDADiamond") {} + + function run() + public + returns (LDADiamond deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + deployed = LDADiamond(deploy(type(LDADiamond).creationCode)); + } + + function getConstructorArgs() internal override returns (bytes memory) { + string memory path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); + address diamondCut = _getConfigContractAddress( + path, + ".DiamondCutFacet" + ); + + return abi.encode(deployerAddress, diamondCut); + } +} diff --git a/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol new file mode 100644 index 000000000..016809162 --- /dev/null +++ b/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("LDADiamondCutFacet") {} + + function run() public returns (LDADiamondCutFacet deployed) { + deployed = LDADiamondCutFacet( + deploy(type(LDADiamondCutFacet).creationCode) + ); + } +} diff --git a/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol new file mode 100644 index 000000000..478d227d0 --- /dev/null +++ b/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("LDADiamondLoupeFacet") {} + + function run() public returns (LDADiamondLoupeFacet deployed) { + deployed = LDADiamondLoupeFacet( + deploy(type(LDADiamondLoupeFacet).creationCode) + ); + } +} diff --git a/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol new file mode 100644 index 000000000..8f4b5d125 --- /dev/null +++ b/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; + +contract DeployScript is DeployScriptBase { + using stdJson for string; + + constructor() DeployScriptBase("LDAEmergencyPauseFacet") {} + + function run() + public + returns (LDAEmergencyPauseFacet deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + + deployed = LDAEmergencyPauseFacet( + deploy(type(LDAEmergencyPauseFacet).creationCode) + ); + } + + function getConstructorArgs() internal override returns (bytes memory) { + string memory path = string.concat(root, "/config/global.json"); + string memory json = vm.readFile(path); + + address pauserWallet = json.readAddress(".pauserWallet"); + + return abi.encode(pauserWallet); + } +} diff --git a/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol new file mode 100644 index 000000000..67b8f4f08 --- /dev/null +++ b/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("LDAOwnershipFacet") {} + + function run() public returns (LDAOwnershipFacet deployed) { + deployed = LDAOwnershipFacet( + deploy(type(LDAOwnershipFacet).creationCode) + ); + } +} diff --git a/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol b/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol new file mode 100644 index 000000000..acafc3f98 --- /dev/null +++ b/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; + +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("LDAPeripheryRegistryFacet") {} + + function run() public returns (LDAPeripheryRegistryFacet deployed) { + deployed = LDAPeripheryRegistryFacet( + deploy(type(LDAPeripheryRegistryFacet).creationCode) + ); + } +} diff --git a/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol b/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol new file mode 100644 index 000000000..d2d566c43 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("AlgebraFacet"); + } +} diff --git a/script/deploy/facets/LDA/UpdateCurveFacet.s.sol b/script/deploy/facets/LDA/UpdateCurveFacet.s.sol new file mode 100644 index 000000000..044e5fdb5 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateCurveFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("CurveFacet"); + } +} diff --git a/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol b/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol new file mode 100644 index 000000000..044e5fdb5 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("CurveFacet"); + } +} diff --git a/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol new file mode 100644 index 000000000..ec9004bf2 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("LDAEmergencyPauseFacet"); + } +} diff --git a/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol new file mode 100644 index 000000000..741bd45e5 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("LDAOwnershipFacet"); + } +} diff --git a/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol new file mode 100644 index 000000000..54b890522 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("SyncSwapV2Facet"); + } +} diff --git a/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol new file mode 100644 index 000000000..baea4809a --- /dev/null +++ b/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("UniV2StyleFacet"); + } +} diff --git a/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol new file mode 100644 index 000000000..f01e8f418 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("UniV3StyleFacet"); + } +} diff --git a/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol b/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol index 87245eb7b..3fa8b8dec 100644 --- a/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol +++ b/script/deploy/zksync/DeployEmergencyPauseFacet.zksync.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; -import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; contract DeployScript is DeployScriptBase { using stdJson for string; diff --git a/src/Security/EmergencyPauseFacet.sol b/src/Facets/EmergencyPauseFacet.sol similarity index 100% rename from src/Security/EmergencyPauseFacet.sol rename to src/Facets/EmergencyPauseFacet.sol diff --git a/src/Periphery/LDA/Facets/LDADiamondCutFacet.sol b/src/Periphery/LDA/Facets/LDADiamondCutFacet.sol new file mode 100644 index 000000000..e641f6551 --- /dev/null +++ b/src/Periphery/LDA/Facets/LDADiamondCutFacet.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; + +/// @title LDADiamondCutFacet +/// @author LI.FI (https://li.fi) +/// @notice Core EIP-2535 Facet for upgrading Diamond Proxies. +/// @custom:version 1.0.0 +contract LDADiamondCutFacet is IDiamondCut { + /// @notice Add/replace/remove any number of functions and optionally execute + /// a function with delegatecall + /// @param _diamondCut Contains the facet addresses and function selectors + /// @param _init The address of the contract or facet to execute _calldata + /// @param _calldata A function call, including function selector and arguments + /// _calldata is executed with delegatecall on _init + function diamondCut( + LibDiamond.FacetCut[] calldata _diamondCut, + address _init, + bytes calldata _calldata + ) external { + LibDiamond.enforceIsContractOwner(); + LibDiamond.diamondCut(_diamondCut, _init, _calldata); + } +} diff --git a/src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol b/src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol new file mode 100644 index 000000000..faf7bcf3f --- /dev/null +++ b/src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { IERC165 } from "lifi/Interfaces/IERC165.sol"; + +/// @title LDADiamondLoupeFacet +/// @author LI.FI (https://li.fi) +/// @notice Core EIP-2535 Facet for inspecting Diamond Proxies. +/// @custom:version 1.0.0 +contract LDADiamondLoupeFacet is IDiamondLoupe, IERC165 { + // Diamond Loupe Functions + //////////////////////////////////////////////////////////////////// + /// These functions are expected to be called frequently by tools. + // + // struct Facet { + // address facetAddress; + // bytes4[] functionSelectors; + // } + + /// @notice Gets all facets and their selectors. + /// @return facets_ Facet + function facets() external view override returns (Facet[] memory facets_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + uint256 numFacets = ds.facetAddresses.length; + facets_ = new Facet[](numFacets); + for (uint256 i = 0; i < numFacets; ) { + address facetAddress_ = ds.facetAddresses[i]; + facets_[i].facetAddress = facetAddress_; + facets_[i].functionSelectors = ds + .facetFunctionSelectors[facetAddress_] + .functionSelectors; + unchecked { + ++i; + } + } + } + + /// @notice Gets all the function selectors provided by a facet. + /// @param _facet The facet address. + /// @return facetFunctionSelectors_ + function facetFunctionSelectors( + address _facet + ) + external + view + override + returns (bytes4[] memory facetFunctionSelectors_) + { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetFunctionSelectors_ = ds + .facetFunctionSelectors[_facet] + .functionSelectors; + } + + /// @notice Get all the facet addresses used by a diamond. + /// @return facetAddresses_ + function facetAddresses() + external + view + override + returns (address[] memory facetAddresses_) + { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetAddresses_ = ds.facetAddresses; + } + + /// @notice Gets the facet that supports the given selector. + /// @dev If facet is not found return address(0). + /// @param _functionSelector The function selector. + /// @return facetAddress_ The facet address. + function facetAddress( + bytes4 _functionSelector + ) external view override returns (address facetAddress_) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + facetAddress_ = ds + .selectorToFacetAndPosition[_functionSelector] + .facetAddress; + } + + // This implements ERC-165. + function supportsInterface( + bytes4 _interfaceId + ) external view override returns (bool) { + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + return ds.supportedInterfaces[_interfaceId]; + } +} diff --git a/src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol b/src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol new file mode 100644 index 000000000..6013a0768 --- /dev/null +++ b/src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; +import { UnAuthorized, InvalidCallData, DiamondIsPaused, InvalidConfig } from "lifi/Errors/GenericErrors.sol"; +import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; + +/// @title LDAEmergencyPauseFacet (Admin only) +/// @author LI.FI (https://li.fi) +/// @notice Allows a LI.FI-owned and -controlled, non-multisig "PauserWallet" to remove a facet +/// or pause the diamond in case of emergency +/// @custom:version 1.0.2 +/// @dev Admin-Facet for emergency purposes only +contract LDAEmergencyPauseFacet { + /// Events /// + event EmergencyFacetRemoved( + address indexed facetAddress, + address indexed msgSender + ); + event EmergencyPaused(address indexed msgSender); + event EmergencyUnpaused(address indexed msgSender); + + /// Errors /// + error FacetIsNotRegistered(); + error NoFacetToPause(); + + /// Storage /// + // solhint-disable-next-line immutable-vars-naming + address public immutable pauserWallet; + // solhint-disable-next-line immutable-vars-naming + bytes32 internal constant NAMESPACE = + keccak256("com.lifi.facets.emergencyPauseFacet"); + // solhint-disable-next-line immutable-vars-naming + address internal immutable _emergencyPauseFacetAddress; + + struct Storage { + IDiamondLoupe.Facet[] facets; + } + + /// Modifiers /// + modifier OnlyPauserWalletOrOwner() { + if ( + msg.sender != pauserWallet && + msg.sender != LibDiamond.contractOwner() + ) revert UnAuthorized(); + _; + } + + /// Constructor /// + /// @param _pauserWallet The address of the wallet that can execute emergency facet removal actions + constructor(address _pauserWallet) { + if (_pauserWallet == address(0)) revert InvalidConfig(); + pauserWallet = _pauserWallet; + _emergencyPauseFacetAddress = address(this); + } + + /// External Methods /// + + /// @notice Removes the given facet from the diamond + /// @param _facetAddress The address of the facet that should be removed + /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner + function removeFacet( + address _facetAddress + ) external OnlyPauserWalletOrOwner { + // make sure that the EmergencyPauseFacet itself cannot be removed through this function + if (_facetAddress == _emergencyPauseFacetAddress) + revert InvalidCallData(); + + // get function selectors for this facet + LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); + bytes4[] memory functionSelectors = ds + .facetFunctionSelectors[_facetAddress] + .functionSelectors; + + // do not continue if no registered function selectors were found + if (functionSelectors.length == 0) revert FacetIsNotRegistered(); + + // make sure that DiamondCutFacet cannot be removed + if (functionSelectors[0] == DiamondCutFacet.diamondCut.selector) + revert InvalidCallData(); + + // remove facet + LibDiamond.removeFunctions(address(0), functionSelectors); + + emit EmergencyFacetRemoved(_facetAddress, msg.sender); + } + + /// @notice Effectively pauses the diamond contract by overwriting the facetAddress-to-function-selector + /// mappings in storage for all facets and redirecting all function selectors to the + /// EmergencyPauseFacet (this will remain as the only registered facet) so that + /// a meaningful error message will be returned when third parties try to call the diamond + /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner + /// @dev This function could potentially run out of gas if too many facets/function selectors are involved. + /// We mitigate this issue by having a test on forked mainnet (which has most facets) + /// that checks if the diamond can be paused + /// @dev forked mainnet (which has most facets) that checks if the diamond can be paused + function pauseDiamond() external OnlyPauserWalletOrOwner { + Storage storage s = getStorage(); + + // get a list of all facets that need to be removed (=all facets except EmergencyPauseFacet) + IDiamondLoupe.Facet[] + memory facets = _getAllFacetFunctionSelectorsToBeRemoved(); + + // prevent invalid contract state + if (facets.length == 0) revert NoFacetToPause(); + + // go through all facets + for (uint256 i; i < facets.length; ) { + // redirect all function selectors to this facet (i.e. to its fallback function + // with the DiamondIsPaused() error message) + LibDiamond.replaceFunctions( + _emergencyPauseFacetAddress, + facets[i].functionSelectors + ); + + // write facet information to storage (so it can be easily reactivated later on) + s.facets.push(facets[i]); + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + + emit EmergencyPaused(msg.sender); + } + + /// @notice Unpauses the diamond contract by re-adding all facetAddress-to-function-selector mappings to storage + /// @dev can only be executed by diamond owner (multisig) + /// @param _blacklist The address(es) of facet(s) that should not be reactivated + function unpauseDiamond(address[] calldata _blacklist) external { + // make sure this function can only be called by the owner + LibDiamond.enforceIsContractOwner(); + + // get all facets from storage + Storage storage s = getStorage(); + + // iterate through all facets and reinstate the facet with its function selectors + for (uint256 i; i < s.facets.length; ) { + LibDiamond.replaceFunctions( + s.facets[i].facetAddress, + s.facets[i].functionSelectors + ); + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + + // go through blacklist and overwrite all function selectors with zero address + // It would be easier to not reinstate these facets in the first place but + // a) that would leave their function selectors associated with address of EmergencyPauseFacet + // (=> throws 'DiamondIsPaused() error when called) + // b) it consumes a lot of gas to check every facet address if it's part of the blacklist + bytes4[] memory currentSelectors; + for (uint256 i; i < _blacklist.length; ) { + currentSelectors = LibDiamondLoupe.facetFunctionSelectors( + _blacklist[i] + ); + + // make sure that the DiamondCutFacet cannot be removed as this would make the diamond immutable + if (currentSelectors[0] == DiamondCutFacet.diamondCut.selector) + continue; + + // build FacetCut parameter + LibDiamond.FacetCut[] memory facetCut = new LibDiamond.FacetCut[]( + 1 + ); + facetCut[0] = LibDiamond.FacetCut({ + facetAddress: address(0), // needs to be address(0) for removals + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: currentSelectors + }); + + // remove facet and its selectors from diamond + LibDiamond.diamondCut(facetCut, address(0), ""); + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + + // free storage + delete s.facets; + + emit EmergencyUnpaused(msg.sender); + } + + /// INTERNAL HELPER FUNCTIONS + + function _getAllFacetFunctionSelectorsToBeRemoved() + internal + view + returns (IDiamondLoupe.Facet[] memory toBeRemoved) + { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory allFacets = LibDiamondLoupe.facets(); + + // initiate return variable with allFacets length - 1 (since we will not remove the EmergencyPauseFacet) + toBeRemoved = new IDiamondLoupe.Facet[](allFacets.length - 1); + + // iterate through facets, copy every facet but EmergencyPauseFacet + uint256 toBeRemovedCounter; + for (uint256 i; i < allFacets.length; ) { + // if its not the EmergencyPauseFacet, copy to the return value variable + if (allFacets[i].facetAddress != _emergencyPauseFacetAddress) { + toBeRemoved[toBeRemovedCounter].facetAddress = allFacets[i] + .facetAddress; + toBeRemoved[toBeRemovedCounter].functionSelectors = allFacets[ + i + ].functionSelectors; + + // gas-efficient way to increase counter + unchecked { + ++toBeRemovedCounter; + } + } + + // gas-efficient way to increase loop counter + unchecked { + ++i; + } + } + } + + /// @dev fetch local storage + function getStorage() private pure returns (Storage storage s) { + bytes32 namespace = NAMESPACE; + // solhint-disable-next-line no-inline-assembly + assembly { + s.slot := namespace + } + } + + // this function will be called when the diamond is paused to return a meaningful error message + // instead of "FunctionDoesNotExist" + fallback() external payable { + revert DiamondIsPaused(); + } + + // only added to silence compiler warnings that arose after adding the fallback function + receive() external payable {} +} diff --git a/src/Periphery/LDA/Facets/LDAOwnershipFacet.sol b/src/Periphery/LDA/Facets/LDAOwnershipFacet.sol new file mode 100644 index 000000000..89177242e --- /dev/null +++ b/src/Periphery/LDA/Facets/LDAOwnershipFacet.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { IERC173 } from "lifi/Interfaces/IERC173.sol"; +import { LibUtil } from "lifi/Libraries/LibUtil.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; + +/// @title LDAOwnershipFacet +/// @author LI.FI (https://li.fi) +/// @notice Manages ownership of the LiFi Diamond contract for admin purposes +/// @custom:version 1.0.0 +contract LDAOwnershipFacet is IERC173 { + /// Storage /// + + bytes32 internal constant NAMESPACE = + keccak256("com.lifi.facets.ownership"); + + /// Types /// + + struct Storage { + address newOwner; + } + + /// Errors /// + + error NoNullOwner(); + error NewOwnerMustNotBeSelf(); + error NoPendingOwnershipTransfer(); + error NotPendingOwner(); + + /// Events /// + + event OwnershipTransferRequested( + address indexed _from, + address indexed _to + ); + + /// External Methods /// + + /// @notice Initiates transfer of ownership to a new address + /// @param _newOwner the address to transfer ownership to + function transferOwnership(address _newOwner) external override { + LibDiamond.enforceIsContractOwner(); + Storage storage s = getStorage(); + + if (LibUtil.isZeroAddress(_newOwner)) revert NoNullOwner(); + + if (_newOwner == LibDiamond.contractOwner()) + revert NewOwnerMustNotBeSelf(); + + s.newOwner = _newOwner; + emit OwnershipTransferRequested(msg.sender, s.newOwner); + } + + /// @notice Cancel transfer of ownership + function cancelOwnershipTransfer() external { + LibDiamond.enforceIsContractOwner(); + Storage storage s = getStorage(); + + if (LibUtil.isZeroAddress(s.newOwner)) + revert NoPendingOwnershipTransfer(); + s.newOwner = address(0); + } + + /// @notice Confirms transfer of ownership to the calling address (msg.sender) + function confirmOwnershipTransfer() external { + Storage storage s = getStorage(); + address _pendingOwner = s.newOwner; + if (msg.sender != _pendingOwner) revert NotPendingOwner(); + emit OwnershipTransferred(LibDiamond.contractOwner(), _pendingOwner); + LibDiamond.setContractOwner(_pendingOwner); + s.newOwner = LibAsset.NULL_ADDRESS; + } + + /// @notice Return the current owner address + /// @return owner_ The current owner address + function owner() external view override returns (address owner_) { + owner_ = LibDiamond.contractOwner(); + } + + /// Private Methods /// + + /// @dev fetch local storage + function getStorage() private pure returns (Storage storage s) { + bytes32 namespace = NAMESPACE; + // solhint-disable-next-line no-inline-assembly + assembly { + s.slot := namespace + } + } +} diff --git a/src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol b/src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol new file mode 100644 index 000000000..f2a9d59d0 --- /dev/null +++ b/src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; + +/// @title Periphery Registry Facet +/// @author LI.FI (https://li.fi) +/// @notice A simple registry to track LIFI periphery contracts +/// @custom:version 1.0.0 +contract LDAPeripheryRegistryFacet { + /// Storage /// + + bytes32 internal constant NAMESPACE = + keccak256("com.lifi.facets.periphery_registry"); + + /// Types /// + + struct Storage { + mapping(string => address) contracts; + } + + /// Events /// + + event PeripheryContractRegistered(string name, address contractAddress); + + /// External Methods /// + + /// @notice Registers a periphery contract address with a specified name + /// @param _name the name to register the contract address under + /// @param _contractAddress the address of the contract to register + function registerPeripheryContract( + string calldata _name, + address _contractAddress + ) external { + LibDiamond.enforceIsContractOwner(); + Storage storage s = getStorage(); + s.contracts[_name] = _contractAddress; + emit PeripheryContractRegistered(_name, _contractAddress); + } + + /// @notice Returns the registered contract address by its name + /// @param _name the registered name of the contract + function getPeripheryContract( + string calldata _name + ) external view returns (address) { + return getStorage().contracts[_name]; + } + + /// @dev fetch local storage + function getStorage() private pure returns (Storage storage s) { + bytes32 namespace = NAMESPACE; + // solhint-disable-next-line no-inline-assembly + assembly { + s.slot := namespace + } + } +} diff --git a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol similarity index 99% rename from test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol rename to test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol index b37ffbaec..7271948ee 100644 --- a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { LibAllowList, TestBase } from "../../utils/TestBase.sol"; import { OnlyContractOwner, UnAuthorized, DiamondIsPaused } from "src/Errors/GenericErrors.sol"; -import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; diff --git a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol similarity index 99% rename from test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol rename to test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol index 14e0df41b..09996e8f2 100644 --- a/test/solidity/Security/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { TestBase } from "../../utils/TestBase.sol"; import { OnlyContractOwner, InvalidCallData, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist, InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; diff --git a/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol b/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol new file mode 100644 index 000000000..4e98abc6e --- /dev/null +++ b/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; +import { TestBase } from "../../../utils/TestBase.sol"; +import { OnlyContractOwner } from "lifi/Errors/GenericErrors.sol"; + +contract LDAOwnershipFacetTest is TestBase { + LDAOwnershipFacet internal ownershipFacet; + + error NoNullOwner(); + error NewOwnerMustNotBeSelf(); + error NoPendingOwnershipTransfer(); + error NotPendingOwner(); + + event OwnershipTransferRequested( + address indexed _from, + address indexed _to + ); + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + function setUp() public override { + initTestBase(); + + ownershipFacet = LDAOwnershipFacet(address(diamond)); + } + + function test_OwnerCanTransferOwnership() public { + vm.startPrank(USER_DIAMOND_OWNER); + + address newOwner = address(0x1234567890123456789012345678901234567890); + + vm.expectEmit(true, true, true, true, address(ownershipFacet)); + emit OwnershipTransferRequested(address(this), newOwner); + + ownershipFacet.transferOwnership(newOwner); + + assert(ownershipFacet.owner() != newOwner); + + vm.stopPrank(); + vm.startPrank(newOwner); + + vm.expectEmit(true, true, true, true, address(ownershipFacet)); + emit OwnershipTransferred(address(USER_DIAMOND_OWNER), newOwner); + + ownershipFacet.confirmOwnershipTransfer(); + + assert(ownershipFacet.owner() == newOwner); + + vm.stopPrank(); + } + + function testRevert_CannotCancelNonPendingOwnershipTransfer() public { + assert(ownershipFacet.owner() == USER_DIAMOND_OWNER); + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectRevert(NoPendingOwnershipTransfer.selector); + + ownershipFacet.cancelOwnershipTransfer(); + + assert(ownershipFacet.owner() == USER_DIAMOND_OWNER); + + vm.stopPrank(); + } + + function test_OwnerCanCancelOwnershipTransfer() public { + address newOwner = address(0x1234567890123456789012345678901234567890); + + ownershipFacet.transferOwnership(newOwner); + + assert(ownershipFacet.owner() != newOwner); + + ownershipFacet.cancelOwnershipTransfer(); + + assert(ownershipFacet.owner() != newOwner); + } + + function testRevert_NonOwnerCannotCancelOwnershipTransfer() public { + address newOwner = address(0x1234567890123456789012345678901234567890); + + ownershipFacet.transferOwnership(newOwner); + + assert(ownershipFacet.owner() != newOwner); + + vm.startPrank(newOwner); + + vm.expectRevert(OnlyContractOwner.selector); + + ownershipFacet.cancelOwnershipTransfer(); + + assert(ownershipFacet.owner() != newOwner); + + vm.stopPrank(); + } + + function testRevert_NonOwnerCannotTransferOwnership() public { + address newOwner = address(0x1234567890123456789012345678901234567890); + assert(ownershipFacet.owner() != newOwner); + vm.prank(newOwner); + + vm.expectRevert(OnlyContractOwner.selector); + + ownershipFacet.transferOwnership(newOwner); + } + + function testRevert_CannotTransferOnwershipToNullAddr() public { + address newOwner = address(0); + + vm.expectRevert(NoNullOwner.selector); + + ownershipFacet.transferOwnership(newOwner); + } + + function testRevert_PendingOwnershipTransferCannotBeConfirmedByNonNewOwner() + public + { + address newOwner = address(0x1234567890123456789012345678901234567890); + ownershipFacet.transferOwnership(newOwner); + + vm.expectRevert(NotPendingOwner.selector); + + ownershipFacet.confirmOwnershipTransfer(); + } + + function testRevert_CannotTransferOwnershipToSelf() public { + address newOwner = address(this); + + vm.expectRevert(NewOwnerMustNotBeSelf.selector); + + ownershipFacet.transferOwnership(newOwner); + } +} diff --git a/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol b/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol index 230898010..986c8ac36 100644 --- a/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol +++ b/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.17; import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; -import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; -import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; +import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; +import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; +import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; @@ -34,17 +34,17 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { address _pauserWallet ) internal returns (LDADiamond) { vm.startPrank(_diamondOwner); - DiamondCutFacet diamondCut = new DiamondCutFacet(); - DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); - OwnershipFacet ownership = new OwnershipFacet(); - EmergencyPauseFacet emergencyPause = new EmergencyPauseFacet( + LDADiamondCutFacet diamondCut = new LDADiamondCutFacet(); + LDADiamondLoupeFacet diamondLoupe = new LDADiamondLoupeFacet(); + LDAOwnershipFacet ownership = new LDAOwnershipFacet(); + LDAEmergencyPauseFacet emergencyPause = new LDAEmergencyPauseFacet( _pauserWallet ); LDADiamond diamond = new LDADiamond( _diamondOwner, address(diamondCut) ); - PeripheryRegistryFacet periphery = new PeripheryRegistryFacet(); + LDAPeripheryRegistryFacet periphery = new LDAPeripheryRegistryFacet(); // Add Diamond Loupe _addDiamondLoupeSelectors(address(diamondLoupe)); @@ -54,10 +54,10 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { // Add PeripheryRegistry bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = PeripheryRegistryFacet + functionSelectors[0] = LDAPeripheryRegistryFacet .registerPeripheryContract .selector; - functionSelectors[1] = PeripheryRegistryFacet + functionSelectors[1] = LDAPeripheryRegistryFacet .getPeripheryContract .selector; cut.push( @@ -81,7 +81,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { }) ); - DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); + LDADiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); delete cut; vm.stopPrank(); return diamond; @@ -89,7 +89,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice Tests that diamond creation fails when owner address is zero function testRevert_CannotDeployDiamondWithZeroOwner() public { - address diamondCutFacet = address(new DiamondCutFacet()); + address diamondCutFacet = address(new LDADiamondCutFacet()); vm.expectRevert(InvalidConfig.selector); new LDADiamond( @@ -124,7 +124,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { function testRevert_CannotCallUnregisteredSelector() public { // Use a real function selector that exists but hasn't been registered yet bytes memory unregisteredCalldata = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, // Valid selector but not registered yet + LDADiamondCutFacet.diamondCut.selector, // Valid selector but not registered yet new LibDiamond.FacetCut[](0), address(0), "" diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/utils/DiamondTest.sol index 4a7b6c8c8..4a940b378 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/utils/DiamondTest.sol @@ -5,7 +5,7 @@ import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; -import { EmergencyPauseFacet } from "lifi/Security/EmergencyPauseFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { BaseDiamondTest } from "./BaseDiamondTest.sol"; From 6370f9a9c4a81435cb73cddc3e98ed68aa1f233b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 16:21:14 +0200 Subject: [PATCH 135/220] Update SPDX license identifiers to LGPL-3.0-only for LDA deployment scripts and tests, and add new tests for LDAEmergencyPauseFacet functionality. --- .../facets/LDA/DeployLDADiamondCutFacet.s.sol | 2 +- .../LDA/DeployLDADiamondLoupeFacet.s.sol | 2 +- .../LDA/DeployLDAEmergencyPauseFacet.s.sol | 2 +- .../facets/LDA/DeployLDAOwnershipFacet.s.sol | 2 +- .../LDA/DeployLDAPeripheryRegistryFacet.s.sol | 2 +- .../facets/LDA/UpdateAlgebraFacet.s.sol | 2 +- .../deploy/facets/LDA/UpdateCurveFacet.s.sol | 2 +- .../facets/LDA/UpdateIzumiV3Facet.s.sol | 4 +- .../LDA/UpdateLDAEmergencyPauseFacet.s.sol | 2 +- .../facets/LDA/UpdateLDAOwnershipFacet.s.sol | 2 +- .../facets/LDA/UpdateSyncSwapV2Facet.s.sol | 2 +- .../facets/LDA/UpdateUniV2StyleFacet.s.sol | 2 +- .../facets/LDA/UpdateUniV3StyleFacet.s.sol | 2 +- .../LDAEmergencyPauseFacet.fork.t.sol | 283 ++++++++++ .../LDAEmergencyPauseFacet.local.t.sol | 483 ++++++++++++++++++ .../LDA/Facets/LDAOwnershipFacet.t.sol | 2 +- 16 files changed, 781 insertions(+), 15 deletions(-) create mode 100644 test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol create mode 100644 test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol diff --git a/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol index 016809162..26218a487 100644 --- a/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; diff --git a/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol index 478d227d0..a736dffe3 100644 --- a/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; diff --git a/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol index 8f4b5d125..d248d5420 100644 --- a/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; diff --git a/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol index 67b8f4f08..4bd1aeef6 100644 --- a/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; diff --git a/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol b/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol index acafc3f98..9e348a348 100644 --- a/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; diff --git a/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol b/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol index d2d566c43..404842927 100644 --- a/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; diff --git a/script/deploy/facets/LDA/UpdateCurveFacet.s.sol b/script/deploy/facets/LDA/UpdateCurveFacet.s.sol index 044e5fdb5..3e272e614 100644 --- a/script/deploy/facets/LDA/UpdateCurveFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateCurveFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; diff --git a/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol b/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol index 044e5fdb5..6adc89493 100644 --- a/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol +++ b/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; @@ -8,6 +8,6 @@ contract DeployScript is UpdateScriptBase { public returns (address[] memory facets, bytes memory cutData) { - return update("CurveFacet"); + return update("IzumiV3Facet"); } } diff --git a/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol index ec9004bf2..61fc6cd4b 100644 --- a/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; diff --git a/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol index 741bd45e5..6446fb028 100644 --- a/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; diff --git a/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol index 54b890522..267816659 100644 --- a/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol +++ b/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; diff --git a/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol index baea4809a..b456dd43e 100644 --- a/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; diff --git a/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol index f01e8f418..75e04f861 100644 --- a/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; diff --git a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol new file mode 100644 index 000000000..8e10b10ac --- /dev/null +++ b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LibAllowList, TestBase } from "../../../../utils/TestBase.sol"; +import { OnlyContractOwner, UnAuthorized, DiamondIsPaused } from "src/Errors/GenericErrors.sol"; +import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; +import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; + +// Stub EmergencyPauseFacet Contract +contract TestEmergencyPauseFacet is LDAEmergencyPauseFacet { + constructor(address _pauserWallet) LDAEmergencyPauseFacet(_pauserWallet) {} + + function addDex(address _dex) external { + LibAllowList.addAllowedContract(_dex); + } + + function setFunctionApprovalBySignature(bytes4 _signature) external { + LibAllowList.addAllowedSelector(_signature); + } +} + +contract LDAEmergencyPauseFacetPRODTest is TestBase { + // EVENTS + event EmergencyFacetRemoved( + address indexed facetAddress, + address indexed msgSender + ); + event EmergencyPaused(address indexed msgSender); + event EmergencyUnpaused(address indexed msgSender); + + // STORAGE + address internal constant ADDRESS_DIAMOND_MAINNET = + 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE; + address internal constant USER_DIAMOND_OWNER_MAINNET = + 0x37347dD595C49212C5FC2D95EA10d1085896f51E; + TestEmergencyPauseFacet internal emergencyPauseFacet; + address[] internal blacklist = new address[](0); + + function setUp() public override { + // set custom block number for forking + customBlockNumberForForking = 19979843; + + initTestBase(); + + // deploy EmergencyPauseFacet + emergencyPauseFacet = new TestEmergencyPauseFacet(USER_PAUSER); + + // prepare diamondCut + bytes4[] memory functionSelectors = new bytes4[](3); + functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; + functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; + functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; + + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(emergencyPauseFacet), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + + // add EmergencyPauseFacet to PROD diamond + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + LDADiamondCutFacet(address(ADDRESS_DIAMOND_MAINNET)).diamondCut( + cut, + address(0), + "" + ); + + // store diamond in local TestEmergencyPauseFacet variable + emergencyPauseFacet = TestEmergencyPauseFacet( + payable(address(ADDRESS_DIAMOND_MAINNET)) + ); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(emergencyPauseFacet), + "EmergencyPauseFacet" + ); + + vm.stopPrank(); + } + + function test_PauserWalletCanPauseDiamond() public { + vm.startPrank(USER_PAUSER); + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + LDADiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + } + + function test_DiamondOwnerCanPauseDiamond() public { + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_DIAMOND_OWNER_MAINNET); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + LDADiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + } + + function test_UnauthorizedWalletCannotPauseDiamond() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + } + + function test_DiamondOwnerCanUnpauseDiamond() public { + IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER_MAINNET); + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + assertTrue(initialFacets.length == finalFacets.length); + } + + function test_UnauthorizedWalletCannotUnpauseDiamond() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // try to pause the diamond with various wallets + vm.startPrank(USER_PAUSER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond is still paused + vm.expectRevert(DiamondIsPaused.selector); + LDADiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + } + + function test_DiamondOwnerCanRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // get LDAPeripheryRegistryFacet address + address facetAddress = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facetAddress( + LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_DIAMOND_OWNER_MAINNET); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER_MAINNET); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + LDADiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( + LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.stopPrank(); + } + + function test_PauserWalletCanRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // get PeripheryRegistryFacet address + address facetAddress = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facetAddress( + LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + LDADiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( + LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.stopPrank(); + } + + function test_UnauthorizedWalletCannotRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // get LDAPeripheryRegistryFacet address + address facetAddress = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facetAddress( + LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ); + + // try to remove facet + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( + address(emergencyPauseFacet) + ).facets(); + + // ensure that number of facets remains unchanged + assertTrue(initialFacets.length == finalFacets.length); + } +} diff --git a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol new file mode 100644 index 000000000..12b5a0b8b --- /dev/null +++ b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { TestBase } from "../../../../utils/TestBase.sol"; +import { OnlyContractOwner, InvalidCallData, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist, InvalidConfig } from "lifi/Errors/GenericErrors.sol"; +import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; +import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; +import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; + +contract LDAEmergencyPauseFacetLOCALTest is TestBase { + // EVENTS + event EmergencyFacetRemoved( + address indexed facetAddress, + address indexed msgSender + ); + event EmergencyPaused(address indexed msgSender); + event EmergencyUnpaused(address indexed msgSender); + + error NoFacetToPause(); + + uint256 internal counter; + event DiamondCut( + LibDiamond.FacetCut[] _diamondCut, + address _init, + bytes _calldata + ); + + // STORAGE + LDAEmergencyPauseFacet internal emergencyPauseFacet; + address[] internal blacklist = new address[](0); + + function setUp() public override { + // set custom block number for forking + customBlockNumberForForking = 19979843; + + initTestBase(); + + // // no need to add the facet to the diamond, it's already added in DiamondTest.sol + emergencyPauseFacet = LDAEmergencyPauseFacet( + payable(address(diamond)) + ); + + // set facet address in TestBase + setFacetAddressInTestBase( + address(emergencyPauseFacet), + "EmergencyPauseFacet" + ); + } + + function testRevert_WhenPauserWalletIsZeroAddress() public { + vm.expectRevert(InvalidConfig.selector); + new LDAEmergencyPauseFacet(address(0)); + } + + function test_PauserWalletCanPauseDiamond() public { + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + LDADiamondLoupeFacet(address(diamond)).facets(); + } + + function test_WillRevertWhenTryingToPauseTwice() public { + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(NoFacetToPause.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + } + + function test_DiamondOwnerCanPauseDiamond() public { + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_DIAMOND_OWNER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + LDADiamondLoupeFacet(address(diamond)).facets(); + } + + function test_UnauthorizedWalletCannotPauseDiamond() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + // pause the contract + emergencyPauseFacet.pauseDiamond(); + } + + function test_DiamondOwnerCanUnpauseDiamondWithEmptyBlacklist() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + assertTrue(allFacets.length == 5); + + // try the same again to make sure commands can be repeatedly executed + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + allFacets = LDADiamondLoupeFacet(address(diamond)).facets(); + + assertTrue(allFacets.length == 5); + + // try the same again to make sure commands can be repeatedly executed + test_PauserWalletCanPauseDiamond(); + } + + function test_CanUnpauseDiamondWithSingleBlacklist() public { + address ownershipFacetAddress = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; + + // get function selectors of OwnershipFacet + bytes4[] memory ownershipFunctionSelectors = IDiamondLoupe( + address(diamond) + ).facetFunctionSelectors(ownershipFacetAddress); + + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + // build diamondCut + LibDiamond.FacetCut[] memory facetCut = new LibDiamond.FacetCut[](1); + facetCut[0] = LibDiamond.FacetCut({ + facetAddress: address(0), + // facetAddress: ownershipFacetAddress, + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: ownershipFunctionSelectors + }); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit DiamondCut(facetCut, address(0), ""); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + blacklist = new address[](1); + blacklist[0] = ownershipFacetAddress; // OwnershipFacet + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + assertTrue(allFacets.length == 4); + + // make sure ownershipFacet is not available anymore + vm.expectRevert(FunctionDoesNotExist.selector); + LDAOwnershipFacet(address(diamond)).owner(); + } + + function test_CanUnpauseDiamondWithMultiBlacklist() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // unpause diamond as owner + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + blacklist = new address[](2); + blacklist[0] = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; // OwnershipFacet + blacklist[1] = 0xA412555Fa40F6AA4B67a773dB5a7f85983890341; // PeripheryRegistryFacet + + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond works normal again and has all facets reinstated + IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + assertTrue(allFacets.length == 3); + + // make sure ownershipFacet is not available anymore + vm.expectRevert(FunctionDoesNotExist.selector); + LDAOwnershipFacet(address(diamond)).owner(); + + // make sure PeripheryRegistryFacet is not available anymore + vm.expectRevert(FunctionDoesNotExist.selector); + LDAPeripheryRegistryFacet(address(diamond)).getPeripheryContract( + "Executor" + ); + } + + function test_UnauthorizedWalletCannotUnpauseDiamond() public { + // pause diamond first + test_PauserWalletCanPauseDiamond(); + + // make sure it's paused + vm.expectRevert(DiamondIsPaused.selector); + IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + vm.startPrank(USER_PAUSER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(OnlyContractOwner.selector); + emergencyPauseFacet.unpauseDiamond(blacklist); + + // make sure diamond is still paused + vm.expectRevert(DiamondIsPaused.selector); + allFacets = LDADiamondLoupeFacet(address(diamond)).facets(); + } + + function test_DiamondOwnerCanRemoveFacetAndUnpauseDiamond() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + // get LDAPeripheryRegistryFacet address + address facetAddress = LDADiamondLoupeFacet(address(diamond)) + .facetAddress( + LDAPeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_DIAMOND_OWNER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + LDADiamondLoupeFacet(address(diamond)).facetAddress( + LDAPeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyUnpaused(USER_DIAMOND_OWNER); + + // unpause diamond with empty blacklist + emergencyPauseFacet.unpauseDiamond(blacklist); + + vm.stopPrank(); + } + + function test_PauserWalletCanRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + // get LDAPeripheryRegistryFacet address + address facetAddress = LDADiamondLoupeFacet(address(diamond)) + .facetAddress( + LDAPeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ); + + // remove facet + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); + + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + // ensure that one facet less is registered now + assertTrue(initialFacets.length == finalFacets.length + 1); + // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress + assertTrue( + LDADiamondLoupeFacet(address(diamond)).facetAddress( + LDAPeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ) == address(0) + ); + + vm.stopPrank(); + } + + function test_WillRevertWhenTryingToRemoveDiamondCutFacet() public { + vm.startPrank(USER_PAUSER); + + // get address of diamondCutFacet + address diamondCutAddress = LDADiamondLoupeFacet(address(diamond)) + .facetAddress( + LDADiamondCutFacet(address(diamond)).diamondCut.selector + ); + + vm.expectRevert(InvalidCallData.selector); + + // remove facet + emergencyPauseFacet.removeFacet(diamondCutAddress); + + vm.stopPrank(); + } + + function test_WillRevertWhenTryingToRemoveEmergencyPauseFacet() public { + vm.startPrank(USER_PAUSER); + + // get address of EmergencyPauseFacet + address emergencyPauseAddress = LDADiamondLoupeFacet(address(diamond)) + .facetAddress( + LDAEmergencyPauseFacet(payable(address(diamond))) + .pauseDiamond + .selector + ); + + vm.expectRevert(InvalidCallData.selector); + + // remove facet + emergencyPauseFacet.removeFacet(emergencyPauseAddress); + + vm.stopPrank(); + } + + function test_UnauthorizedWalletCannotRemoveFacet() public { + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + // get LDAPeripheryRegistryFacet address + address facetAddress = LDADiamondLoupeFacet(address(diamond)) + .facetAddress( + LDAPeripheryRegistryFacet(address(diamond)) + .registerPeripheryContract + .selector + ); + + // try to remove facet + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + vm.startPrank(USER_RECEIVER); + vm.expectRevert(UnAuthorized.selector); + emergencyPauseFacet.removeFacet(facetAddress); + + // get a list of all registered facet addresses + IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + // ensure that number of facets remains unchanged + assertTrue(initialFacets.length == finalFacets.length); + } + + function test_HowManyFacetsCanWePauseMax() public { + uint256 contractsCount = 500; + // deploy dummy contracts and store their addresses + address[] memory contractAddresses = new address[](contractsCount); + + for (uint256 i; i < contractsCount; i++) { + contractAddresses[i] = address(new DummyContract()); + } + + // build diamondCut data + // Add the diamondCut external function from the diamondCutFacet + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[]( + contractsCount + ); + for (uint256 i; i < contractsCount; i++) { + cut[i] = LibDiamond.FacetCut({ + facetAddress: contractAddresses[i], + action: LibDiamond.FacetCutAction.Add, + functionSelectors: generateRandomBytes4Array() + }); + } + LDADiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); + + // + IDiamondLoupe.Facet[] memory facets = LDADiamondLoupeFacet( + address(diamond) + ).facets(); + + assert(facets.length >= contractsCount); + + // try to pause + + vm.startPrank(USER_PAUSER); + + vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + emit EmergencyPaused(USER_PAUSER); + + // pause the contract + emergencyPauseFacet.pauseDiamond(); + + // try to get a list of all registered facets via DiamondLoupe + vm.expectRevert(DiamondIsPaused.selector); + LDADiamondLoupeFacet(address(diamond)).facets(); + } + + function generateRandomBytes4Array() + public + returns (bytes4[] memory randomValues) + { + randomValues = new bytes4[](3); + + for (uint256 i = 0; i < 3; i++) { + counter++; + randomValues[i] = bytes4( + keccak256( + abi.encodePacked( + block.timestamp, + block.number, + msg.sender, + counter + ) + ) + ); + } + + return randomValues; + } +} + +contract DummyContract { + string internal bla = "I am a dummy contract"; +} diff --git a/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol b/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol index 4e98abc6e..63ebaff73 100644 --- a/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; From 9aef00c8cb27eafff2940e4898a33d21ed3e704c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 19:37:16 +0200 Subject: [PATCH 136/220] removed duplicates --- script/removeDuplicateEventsFromABI.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/removeDuplicateEventsFromABI.ts b/script/removeDuplicateEventsFromABI.ts index acd08cd96..082b1fd33 100644 --- a/script/removeDuplicateEventsFromABI.ts +++ b/script/removeDuplicateEventsFromABI.ts @@ -49,7 +49,9 @@ async function removeDuplicateEventsFromABI() { // Files that we know have duplicate events const filesToClean = [ 'out/OwnershipFacet.sol/OwnershipFacet.json', + 'out/LDAOwnershipFacet.sol/LDAOwnershipFacet.json', 'out/DiamondCutFacet.sol/DiamondCutFacet.json', + 'out/LDADiamondCutFacet.sol/LDADiamondCutFacet.json', ] for (const file of filesToClean) { From 1cb9bb487007e9cd5063de7f25f19b1f576be432 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 29 Aug 2025 22:11:56 +0200 Subject: [PATCH 137/220] Add LDA deployment scripts and facets, including core facets and update functionality for LDADiamond. Introduce new helper functions for deploying and managing LDA contracts, and update existing scripts to support LDA-specific operations. --- config/global.json | 10 + deployments/_deployments_log_file.json | 153 ++++++++++++ deployments/arbitrum.lda.staging.json | 11 + script/deploy/deployAllLDAContracts.sh | 229 ++++++++++++++++++ .../deploy/deployFacetAndAddToLDADiamond.sh | 123 ++++++++++ script/deploy/deployLDACoreFacets.sh | 70 ++++++ script/deploy/deploySingleContract.sh | 35 ++- .../facets/LDA/DeployAlgebraFacet.s.sol | 6 +- .../facets/LDA/DeployCoreRouteFacet.s.sol | 11 +- .../deploy/facets/LDA/DeployCurveFacet.s.sol | 6 +- .../facets/LDA/DeployIzumiV3Facet.s.sol | 6 +- .../deploy/facets/LDA/DeployLDADiamond.s.sol | 31 ++- .../facets/LDA/DeployLDADiamondCutFacet.s.sol | 6 +- .../LDA/DeployLDADiamondLoupeFacet.s.sol | 6 +- .../LDA/DeployLDAEmergencyPauseFacet.s.sol | 6 +- .../facets/LDA/DeployLDAOwnershipFacet.s.sol | 6 +- .../LDA/DeployLDAPeripheryRegistryFacet.s.sol | 6 +- .../facets/LDA/DeployNativeWrapperFacet.s.sol | 13 + .../facets/LDA/DeploySyncSwapV2Facet.s.sol | 6 +- .../facets/LDA/DeployUniV2StyleFacet.s.sol | 6 +- .../facets/LDA/DeployUniV3StyleFacet.s.sol | 6 +- .../facets/LDA/DeployVelodromeV2Facet.s.sol | 6 +- .../facets/LDA/UpdateLDACoreFacets.s.sol | 121 +++++++++ .../facets/LDA/UpdateNativeWrapperFacet.s.sol | 13 + .../facets/LDA/utils/DeployLDAScriptBase.sol | 74 ++++++ .../facets/LDA/utils/UpdateLDAScriptBase.sol | 213 ++++++++++++++++ script/deploy/updateLDADiamondLog.ts | 223 +++++++++++++++++ script/helperFunctions.sh | 113 +++++++++ script/scriptMaster.sh | 199 +++++++++++++-- script/tasks/ldaDiamondUpdateFacet.sh | 168 +++++++++++++ .../LDA/BaseUniV2StyleDEXFacet.t.sol | 2 +- .../LDAEmergencyPauseFacet.fork.t.sol | 2 +- 32 files changed, 1819 insertions(+), 67 deletions(-) create mode 100644 deployments/arbitrum.lda.staging.json create mode 100644 script/deploy/deployAllLDAContracts.sh create mode 100644 script/deploy/deployFacetAndAddToLDADiamond.sh create mode 100644 script/deploy/deployLDACoreFacets.sh create mode 100644 script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol create mode 100644 script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol create mode 100644 script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol create mode 100644 script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol create mode 100644 script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol create mode 100644 script/deploy/updateLDADiamondLog.ts create mode 100644 script/tasks/ldaDiamondUpdateFacet.sh diff --git a/config/global.json b/config/global.json index 782cb17bb..5a7d5d1ad 100644 --- a/config/global.json +++ b/config/global.json @@ -66,5 +66,15 @@ "LiFiTimelockController", "Permit2Proxy", "TokenWrapper" + ], + "ldaCoreFacets": [ + "LDADiamondCutFacet", + "LDADiamondLoupeFacet", + "LDAOwnershipFacet", + "LDAEmergencyPauseFacet", + "LDAPeripheryRegistryFacet", + "CoreRouteFacet", + "UniV3StyleFacet", + "UniV2StyleFacet" ] } diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 84eb69fb4..5f145a65b 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39718,5 +39718,158 @@ ] } } + }, + "LDADiamondCutFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:10:27", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "LDADiamondLoupeFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:11:52", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "LDAOwnershipFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:13:16", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "LDAEmergencyPauseFacet": { + "arbitrum": { + "staging": { + "1.0.2": [ + { + "ADDRESS": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:14:45", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "LDAPeripheryRegistryFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:16:11", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "CoreRouteFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:37:53", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "UniV3StyleFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x414fc3eB441664807027346817dcBe8490938C78", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:39:16", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "UniV2StyleFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 21:40:41", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "LDADiamond": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-08-29 22:02:41", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000fa0a1fd9e492f0f75168a7ef72418420379057b7", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } } } diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json new file mode 100644 index 000000000..abad6efd8 --- /dev/null +++ b/deployments/arbitrum.lda.staging.json @@ -0,0 +1,11 @@ +{ + "LDADiamondCutFacet": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", + "LDADiamondLoupeFacet": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", + "LDAOwnershipFacet": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", + "LDAEmergencyPauseFacet": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", + "LDAPeripheryRegistryFacet": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", + "CoreRouteFacet": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", + "UniV3StyleFacet": "0x414fc3eB441664807027346817dcBe8490938C78", + "UniV2StyleFacet": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", + "LDADiamond": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d" +} \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh new file mode 100644 index 000000000..856a659f5 --- /dev/null +++ b/script/deploy/deployAllLDAContracts.sh @@ -0,0 +1,229 @@ +#!/bin/bash + +deployAllLDAContracts() { + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployAllLDAContracts" + + # load required resources + source script/config.sh + source script/helperFunctions.sh + source script/deploy/deployAndStoreCREATE3Factory.sh + source script/deploy/deployLDACoreFacets.sh + source script/deploy/deployFacetAndAddToLDADiamond.sh + source script/tasks/ldaDiamondUpdateFacet.sh + + # read function arguments into variables + local NETWORK="$1" + local ENVIRONMENT="$2" + + # load env variables + source .env + + # get file suffix based on value in variable ENVIRONMENT + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # logging for debug purposes + echo "" + echoDebug "in function deployAllLDAContracts" + echoDebug "NETWORK=$NETWORK" + echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echo "" + + # Ask user where to start the LDA deployment process + echo "Which LDA deployment stage would you like to start from?" + START_FROM=$( + gum choose \ + "1) Initial setup and CREATE3Factory deployment" \ + "2) Deploy LDA core facets" \ + "3) Deploy LDA diamond and update with core facets" \ + "4) Deploy LDA DEX facets and add to diamond" \ + "5) Run LDA health check only" + ) + + # Extract the stage number from the selection + if [[ "$START_FROM" == *"1)"* ]]; then + START_STAGE=1 + elif [[ "$START_FROM" == *"2)"* ]]; then + START_STAGE=2 + elif [[ "$START_FROM" == *"3)"* ]]; then + START_STAGE=3 + elif [[ "$START_FROM" == *"4)"* ]]; then + START_STAGE=4 + elif [[ "$START_FROM" == *"5)"* ]]; then + START_STAGE=5 + else + error "invalid selection: $START_FROM - exiting script now" + exit 1 + fi + + echo "Starting LDA deployment from stage $START_STAGE: $START_FROM" + echo "" + + # LDA Diamond contract name + local LDA_DIAMOND_CONTRACT_NAME="LDADiamond" + + # Stage 1: Initial setup and CREATE3Factory deployment + if [[ $START_STAGE -le 1 ]]; then + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 1: Initial setup and CREATE3Factory deployment" + + # make sure that proposals are sent to diamond directly (for production deployments) + if [[ "$SEND_PROPOSALS_DIRECTLY_TO_DIAMOND" == "false" ]]; then + echo "SEND_PROPOSALS_DIRECTLY_TO_DIAMOND is set to false in your .env file" + echo "This script requires SEND_PROPOSALS_DIRECTLY_TO_DIAMOND to be true for PRODUCTION deployments" + echo "Would you like to set it to true for this execution? (y/n)" + read -r response + if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + export SEND_PROPOSALS_DIRECTLY_TO_DIAMOND=true + echo "SEND_PROPOSALS_DIRECTLY_TO_DIAMOND set to true for this execution" + else + echo "Continuing with SEND_PROPOSALS_DIRECTLY_TO_DIAMOND=false (STAGING deployment???)" + fi + fi + + # add RPC URL to MongoDB if needed + CREATE3_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.create3Factory") + if [[ -z "$CREATE3_ADDRESS" || "$CREATE3_ADDRESS" == "null" ]]; then + echo "" + echo "Adding RPC URL from networks.json to MongoDB and fetching all URLs" + bun add-network-rpc --network "$NETWORK" --rpc-url "$(getRpcUrlFromNetworksJson "$NETWORK")" + bun fetch-rpcs + # reload .env file to have the new RPC URL available + source .env + fi + + # get deployer wallet balance + BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") + echo "Deployer Wallet Balance: $BALANCE" + if [[ -z "$BALANCE" || "$BALANCE" == "0" ]]; then + echo "Deployer wallet does not have any balance in network $NETWORK. Please fund the wallet and try again" + exit 1 + fi + + echo "[info] deployer wallet balance in this network: $BALANCE" + echo "" + checkRequiredVariablesInDotEnv "$NETWORK" + + echo "isZkEVM: $(isZkEvmNetwork "$NETWORK")" + + # deploy CREATE3Factory if needed + if isZkEvmNetwork "$NETWORK"; then + echo "zkEVM network detected, skipping CREATE3Factory deployment" + else + if [[ -z "$CREATE3_ADDRESS" || "$CREATE3_ADDRESS" == "null" ]]; then + deployAndStoreCREATE3Factory "$NETWORK" "$ENVIRONMENT" + checkFailure $? "deploy CREATE3Factory to network $NETWORK" + echo "" + else + echo "CREATE3Factory already deployed for $NETWORK (address: $CREATE3_ADDRESS), skipping CREATE3Factory deployment." + fi + fi + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 1 completed" + fi + + # Stage 2: Deploy LDA core facets + if [[ $START_STAGE -le 2 ]]; then + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 2: Deploy LDA core facets" + + # deploy LDA core facets + deployLDACoreFacets "$NETWORK" "$ENVIRONMENT" + echo "" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 2 completed" + fi + + # Stage 3: Deploy LDA diamond and update with core facets + if [[ $START_STAGE -le 3 ]]; then + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 3: Deploy LDA diamond and update with core facets" + + # get current LDA diamond contract version + local VERSION=$(getCurrentContractVersion "$LDA_DIAMOND_CONTRACT_NAME") + + # deploy LDA diamond + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying $LDA_DIAMOND_CONTRACT_NAME now" + deploySingleContract "$LDA_DIAMOND_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" "true" "true" + + # check if last command was executed successfully, otherwise exit script with error message + checkFailure $? "deploy contract $LDA_DIAMOND_CONTRACT_NAME to network $NETWORK" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $LDA_DIAMOND_CONTRACT_NAME successfully deployed" + + # update LDA diamond with core facets + echo "" + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now updating core facets in LDA diamond contract" + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" false + + # check if last command was executed successfully, otherwise exit script with error message + checkFailure $? "update LDA core facets in $LDA_DIAMOND_CONTRACT_NAME on network $NETWORK" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA core facets update completed" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 3 completed" + fi + + # Stage 4: Deploy LDA DEX facets and add to diamond + if [[ $START_STAGE -le 4 ]]; then + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Deploy LDA DEX facets and add to diamond" + + # deploy all LDA DEX facets and add to diamond + echo "" + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now deploying LDA DEX facets and adding to diamond contract" + + # get all LDA facet contract names (excluding core facets) + local LDA_FACETS_PATH="script/deploy/facets/LDA/" + + # Read LDA core facets from config for exclusion + local GLOBAL_CONFIG_PATH="./config/global.json" + if [[ ! -f "$GLOBAL_CONFIG_PATH" ]]; then + error "Global config file not found: $GLOBAL_CONFIG_PATH" + return 1 + fi + + # Get LDA core facets from JSON config + local LDA_CORE_FACETS_JSON=$(jq -r '.ldaCoreFacets[]' "$GLOBAL_CONFIG_PATH") + local LDA_CORE_FACETS=() + while IFS= read -r facet; do + LDA_CORE_FACETS+=("$facet") + done <<< "$LDA_CORE_FACETS_JSON" + + # Add LDADiamond to exclusions and build regex + LDA_CORE_FACETS+=("LDADiamond") + local EXCLUDED_LDA_FACETS_REGEXP="^($(printf '%s|' "${LDA_CORE_FACETS[@]}"))" + EXCLUDED_LDA_FACETS_REGEXP="${EXCLUDED_LDA_FACETS_REGEXP%|})$" + + # loop through LDA facet contract names + for DEPLOY_SCRIPT in $(ls -1 "$LDA_FACETS_PATH" | grep '^Deploy.*\.s\.sol$'); do + FACET_NAME=$(echo "$DEPLOY_SCRIPT" | sed -e 's/Deploy//' -e 's/\.s\.sol$//') + + if ! [[ "$FACET_NAME" =~ $EXCLUDED_LDA_FACETS_REGEXP ]]; then + # check if facet is existing in target state JSON for LDA + TARGET_VERSION=$(findContractVersionInTargetState "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$LDA_DIAMOND_CONTRACT_NAME") + + # check result + if [[ $? -ne 0 ]]; then + echo "[info] No matching entry found in target state file for NETWORK=$NETWORK, ENVIRONMENT=$ENVIRONMENT, CONTRACT=$FACET_NAME >> no deployment needed" + else + # deploy LDA facet and add to LDA diamond + deployFacetAndAddToLDADiamond "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$LDA_DIAMOND_CONTRACT_NAME" "$TARGET_VERSION" + fi + fi + done + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA DEX facets part completed" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 4 completed" + fi + + # Stage 5: Run LDA health check only + if [[ $START_STAGE -le 5 ]]; then + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Run LDA health check only" + echo "[info] LDA health check functionality will be implemented later" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" + fi + + echo "" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" +} diff --git a/script/deploy/deployFacetAndAddToLDADiamond.sh b/script/deploy/deployFacetAndAddToLDADiamond.sh new file mode 100644 index 000000000..d826934eb --- /dev/null +++ b/script/deploy/deployFacetAndAddToLDADiamond.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# deploys an LDA facet contract and adds it to LDADiamond contract +function deployFacetAndAddToLDADiamond() { + # load env variables + source .env + + # load config & helper functions + source script/config.sh + source script/helperFunctions.sh + source script/deploy/deploySingleContract.sh + source script/tasks/ldaDiamondUpdateFacet.sh + + # read function arguments into variables + local NETWORK="$1" + local ENVIRONMENT="$2" + local FACET_CONTRACT_NAME="$3" + local DIAMOND_CONTRACT_NAME="$4" + local VERSION="$5" + + # if no NETWORK was passed to this function, ask user to select it + if [[ -z "$NETWORK" ]]; then + checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" + NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") + checkRequiredVariablesInDotEnv $NETWORK + fi + + # if no ENVIRONMENT was passed to this function, determine it + if [[ -z "$ENVIRONMENT" ]]; then + if [[ "$PRODUCTION" == "true" ]]; then + # make sure that PRODUCTION was selected intentionally by user + echo " " + echo " " + printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!! ATTENTION !!!!!!!!!!!!!!!!!!!!!!!!"; + printf '\033[33m%s\033[0m\n' "The config environment variable PRODUCTION is set to true"; + printf '\033[33m%s\033[0m\n' "This means you will be deploying LDA contracts to production"; + printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; + echo " " + printf '\033[33m%s\033[0m\n' "Last chance: Do you want to skip?"; + PROD_SELECTION=$(gum choose \ + "yes" \ + "no" \ + ) + + if [[ $PROD_SELECTION != "no" ]]; then + echo "...exiting script" + exit 0 + fi + + ENVIRONMENT="production" + else + ENVIRONMENT="staging" + fi + fi + + # get file suffix based on value in variable ENVIRONMENT + FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # if no FACET_CONTRACT_NAME was passed to this function, ask user to select it + if [[ -z "$FACET_CONTRACT_NAME" ]]; then + echo "" + echo "Please select which LDA facet you would like to deploy" + local SCRIPT=$(ls -1 script/deploy/facets/LDA/ | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy LDA Script") + FACET_CONTRACT_NAME=$(echo $SCRIPT | sed -e 's/Deploy//') + fi + + # if no DIAMOND_CONTRACT_NAME was passed to this function, default to LDADiamond + if [[ -z "$DIAMOND_CONTRACT_NAME" ]]; then + DIAMOND_CONTRACT_NAME="LDADiamond" + fi + + # get LDA diamond address from deployments script (using .lda. file suffix) + local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.${FILE_SUFFIX}json" + local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$LDA_DEPLOYMENT_FILE") + + # if no diamond address was found, throw an error and exit this script + if [[ "$DIAMOND_ADDRESS" == "null" ]]; then + error "could not find address for $DIAMOND_CONTRACT_NAME on network $NETWORK in file '$LDA_DEPLOYMENT_FILE' - exiting script now" + return 1 + fi + + # if no VERSION was passed to this function, we assume that the latest version should be deployed + if [[ -z "$VERSION" ]]; then + VERSION=$(getCurrentContractVersion "$FACET_CONTRACT_NAME") + fi + + # logging for debug purposes + echo "" + echoDebug "in function deployFacetAndAddToLDADiamond" + echoDebug "NETWORK=$NETWORK" + echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echoDebug "FACET_CONTRACT_NAME=$FACET_CONTRACT_NAME" + echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" + echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "VERSION=$VERSION" + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying LDA $FACET_CONTRACT_NAME for $DIAMOND_CONTRACT_NAME now...." + + # deploy LDA facet (using LDA-specific deploy script directory) + deploySingleContract "$FACET_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" false "$DIAMOND_CONTRACT_NAME" "LDA" + + # check if function call was successful + if [ $? -ne 0 ] + then + warning "this call was not successful: deploySingleContract $FACET_CONTRACT_NAME $NETWORK $ENVIRONMENT $VERSION false $DIAMOND_CONTRACT_NAME LDA" + return 1 + fi + + # prepare LDA update script name + local UPDATE_SCRIPT="Update$FACET_CONTRACT_NAME" + + # update LDA diamond + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" true + + if [ $? -ne 0 ] + then + warning "this call was not successful: ldaDiamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" + return 1 + fi + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA $FACET_CONTRACT_NAME successfully deployed and added to $DIAMOND_CONTRACT_NAME" + return 0 +} diff --git a/script/deploy/deployLDACoreFacets.sh b/script/deploy/deployLDACoreFacets.sh new file mode 100644 index 000000000..50818a3db --- /dev/null +++ b/script/deploy/deployLDACoreFacets.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# deploys LDA core facets +deployLDACoreFacets() { + # load config & helper functions + source script/config.sh + source script/helperFunctions.sh + source script/deploy/deploySingleContract.sh + + # read function arguments into variables + local NETWORK="$1" + local ENVIRONMENT="$2" + + # load env variables + source .env + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployLDACoreFacets" + + # get file suffix based on value in variable ENVIRONMENT + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # logging for debug purposes + echo "" + echoDebug "in function deployLDACoreFacets" + echoDebug "NETWORK=$NETWORK" + echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echo "" + + # Read LDA core facets from global.json config file + local GLOBAL_CONFIG_PATH="./config/global.json" + if [[ ! -f "$GLOBAL_CONFIG_PATH" ]]; then + error "Global config file not found: $GLOBAL_CONFIG_PATH" + return 1 + fi + + # Extract LDA core facets from JSON config + local LDA_CORE_FACETS_JSON=$(jq -r '.ldaCoreFacets[]' "$GLOBAL_CONFIG_PATH") + local LDA_CORE_FACETS=() + while IFS= read -r facet; do + LDA_CORE_FACETS+=("$facet") + done <<< "$LDA_CORE_FACETS_JSON" + + echo "[info] Found ${#LDA_CORE_FACETS[@]} LDA core facets in config: ${LDA_CORE_FACETS[*]}" + + # loop through LDA core facets and deploy them + for FACET_NAME in "${LDA_CORE_FACETS[@]}"; do + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying LDA core facet: $FACET_NAME" + + # get current contract version + local VERSION=$(getCurrentContractVersion "$FACET_NAME") + + # deploy the LDA core facet + deploySingleContract "$FACET_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" "false" "true" + + # check if last command was executed successfully + if [ $? -eq 0 ]; then + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA core facet $FACET_NAME successfully deployed" + else + error "failed to deploy LDA core facet $FACET_NAME to network $NETWORK" + return 1 + fi + done + + echo "" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployLDACoreFacets completed" + + return 0 +} diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index 645b38c70..adb2499d1 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -2,7 +2,7 @@ # deploys a single contract # should be called like this: -# $(deploySingleContract "Executor" "BSC" "staging" "1.0.0" true) +# $(deploySingleContract "Executor" "BSC" "staging" "1.0.0" true false) deploySingleContract() { # load config & helper functions source script/config.sh @@ -15,7 +15,7 @@ deploySingleContract() { local ENVIRONMENT="$3" local VERSION="$4" local EXIT_ON_ERROR="$5" - + local IS_LDA_CONTRACT="$6" # load env variables source .env @@ -67,12 +67,26 @@ deploySingleContract() { FILE_EXTENSION=".s.sol" - # Handle ZkEVM Chains - # We need to use zksync specific scripts that are able to be compiled for - # the zkvm + # Determine deployment script directory based on network type and contract type + # We need to support 4 combinations: + # 1. Regular + Non-zkEVM = script/deploy/facets/ + # 2. Regular + zkEVM = script/deploy/zksync/ + # 3. LDA + Non-zkEVM = script/deploy/facets/LDA/ + # 4. LDA + zkEVM = script/deploy/zksync/LDA/ + if isZkEvmNetwork "$NETWORK"; then - DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/" + if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/LDA/" + else + DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/" + fi FILE_EXTENSION=".zksync.s.sol" + else + if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" + else + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" + fi fi if [[ -z "$CONTRACT" ]]; then @@ -112,6 +126,15 @@ deploySingleContract() { # get file suffix based on value in variable ENVIRONMENT FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # For LDA contracts, modify FILE_SUFFIX to include "lda." + if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + if [[ "$ENVIRONMENT" == "production" ]]; then + FILE_SUFFIX="lda." + else + FILE_SUFFIX="lda.staging." + fi + fi if [[ -z "$GAS_ESTIMATE_MULTIPLIER" ]]; then GAS_ESTIMATE_MULTIPLIER=130 # this is foundry's default value diff --git a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol index 40fedd9f1..2ee324755 100644 --- a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol +++ b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("AlgebraFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("AlgebraFacet") {} function run() public returns (AlgebraFacet deployed) { deployed = AlgebraFacet(deploy(type(AlgebraFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index 352d08355..3621e6ac1 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -1,13 +1,18 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("CoreRouteFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("CoreRouteFacet") {} function run() public returns (CoreRouteFacet deployed) { deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); } + + function getConstructorArgs() internal view override returns (bytes memory) { + // CoreRouteFacet constructor requires _owner parameter + return abi.encode(deployerAddress); + } } diff --git a/script/deploy/facets/LDA/DeployCurveFacet.s.sol b/script/deploy/facets/LDA/DeployCurveFacet.s.sol index 18b3c43ce..9f723542a 100644 --- a/script/deploy/facets/LDA/DeployCurveFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCurveFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("CurveFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("CurveFacet") {} function run() public returns (CurveFacet deployed) { deployed = CurveFacet(deploy(type(CurveFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol index 6a295772b..201f91926 100644 --- a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol +++ b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("IzumiV3Facet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("IzumiV3Facet") {} function run() public returns (IzumiV3Facet deployed) { deployed = IzumiV3Facet(deploy(type(IzumiV3Facet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployLDADiamond.s.sol b/script/deploy/facets/LDA/DeployLDADiamond.s.sol index 7b2efc356..fb23a1856 100644 --- a/script/deploy/facets/LDA/DeployLDADiamond.s.sol +++ b/script/deploy/facets/LDA/DeployLDADiamond.s.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; -contract DeployScript is DeployScriptBase { +contract DeployScript is DeployLDAScriptBase { using stdJson for string; - constructor() DeployScriptBase("LDADiamond") {} + constructor() DeployLDAScriptBase("LDADiamond") {} function run() public @@ -19,17 +19,38 @@ contract DeployScript is DeployScriptBase { } function getConstructorArgs() internal override returns (bytes memory) { + // Check if fileSuffix already contains "lda." to avoid double prefix + string memory ldaPrefix = ""; + bytes memory fileSuffixBytes = bytes(fileSuffix); + bool hasLdaPrefix = false; + + // Check if fileSuffix starts with "lda." + if (fileSuffixBytes.length >= 4) { + hasLdaPrefix = ( + fileSuffixBytes[0] == 'l' && + fileSuffixBytes[1] == 'd' && + fileSuffixBytes[2] == 'a' && + fileSuffixBytes[3] == '.' + ); + } + + if (!hasLdaPrefix) { + ldaPrefix = ".lda."; + } else { + ldaPrefix = "."; + } + string memory path = string.concat( root, "/deployments/", network, - ".", + ldaPrefix, fileSuffix, "json" ); address diamondCut = _getConfigContractAddress( path, - ".DiamondCutFacet" + ".LDADiamondCutFacet" ); return abi.encode(deployerAddress, diamondCut); diff --git a/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol index 26218a487..1294ed540 100644 --- a/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDADiamondCutFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDADiamondCutFacet") {} function run() public returns (LDADiamondCutFacet deployed) { deployed = LDADiamondCutFacet( diff --git a/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol index a736dffe3..7963855d9 100644 --- a/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDADiamondLoupeFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDADiamondLoupeFacet") {} function run() public returns (LDADiamondLoupeFacet deployed) { deployed = LDADiamondLoupeFacet( diff --git a/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol index d248d5420..ced775bde 100644 --- a/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; -contract DeployScript is DeployScriptBase { +contract DeployScript is DeployLDAScriptBase { using stdJson for string; - constructor() DeployScriptBase("LDAEmergencyPauseFacet") {} + constructor() DeployLDAScriptBase("LDAEmergencyPauseFacet") {} function run() public diff --git a/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol index 4bd1aeef6..2e86201a2 100644 --- a/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDAOwnershipFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDAOwnershipFacet") {} function run() public returns (LDAOwnershipFacet deployed) { deployed = LDAOwnershipFacet( diff --git a/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol b/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol index 9e348a348..7ae636c20 100644 --- a/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol +++ b/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDAPeripheryRegistryFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDAPeripheryRegistryFacet") {} function run() public returns (LDAPeripheryRegistryFacet deployed) { deployed = LDAPeripheryRegistryFacet( diff --git a/script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol b/script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol new file mode 100644 index 000000000..fe4a98a9e --- /dev/null +++ b/script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("NativeWrapperFacet") {} + + function run() public returns (NativeWrapperFacet deployed) { + deployed = NativeWrapperFacet(deploy(type(NativeWrapperFacet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol index 6d10e3c4b..f5ba26cbd 100644 --- a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("SyncSwapV2Facet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("SyncSwapV2Facet") {} function run() public returns (SyncSwapV2Facet deployed) { deployed = SyncSwapV2Facet(deploy(type(SyncSwapV2Facet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol index b08a859c7..88982d4ea 100644 --- a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("UniV2StyleFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("UniV2StyleFacet") {} function run() public returns (UniV2StyleFacet deployed) { deployed = UniV2StyleFacet(deploy(type(UniV2StyleFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol index fc825770f..fe5e99377 100644 --- a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("UniV3StyleFacet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("UniV3StyleFacet") {} function run() public returns (UniV3StyleFacet deployed) { deployed = UniV3StyleFacet(deploy(type(UniV3StyleFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol index 1c1df461e..82491b8d1 100644 --- a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("VelodromeV2Facet") {} +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("VelodromeV2Facet") {} function run() public returns (VelodromeV2Facet deployed) { deployed = VelodromeV2Facet( diff --git a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol new file mode 100644 index 000000000..e67f0ec45 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; + +contract UpdateLDACoreFacets is UpdateLDAScriptBase { + using stdJson for string; + + error FailedToReadLDACoreFacetsFromConfig(); + + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + // Read LDA core facets dynamically from lda-global.json config + string memory ldaGlobalConfigPath = string.concat( + vm.projectRoot(), + "/config/global.json" + ); + string memory ldaGlobalConfig = vm.readFile(ldaGlobalConfigPath); + string[] memory ldaCoreFacets = ldaGlobalConfig.readStringArray( + ".ldaCoreFacets" + ); + + emit log("LDA core facets found in config/global.json: "); + emit log_uint(ldaCoreFacets.length); + + bytes4[] memory exclude; + + // Check if the LDA loupe was already added to the diamond + bool loupeExists; + try loupe.facetAddresses() returns (address[] memory) { + // If call was successful, loupe exists on LDA diamond already + emit log("LDA Loupe exists on diamond already"); + loupeExists = true; + } catch { + // No need to do anything, just making sure that the flow continues in both cases with try/catch + } + + // Handle LDADiamondLoupeFacet separately as it needs special treatment + if (!loupeExists) { + emit log("LDA Loupe does not exist on diamond yet"); + address ldaDiamondLoupeAddress = _getConfigContractAddress( + path, + ".LDADiamondLoupeFacet" + ); + bytes4[] memory loupeSelectors = getSelectors( + "LDADiamondLoupeFacet", + exclude + ); + + buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + // Reset diamond cut variable to remove LDA diamondLoupe information + delete cut; + } + + // Process all LDA core facets dynamically + for (uint256 i = 0; i < ldaCoreFacets.length; i++) { + string memory facetName = ldaCoreFacets[i]; + + // Skip LDADiamondCutFacet and LDADiamondLoupeFacet as they were already handled + if ( + keccak256(bytes(facetName)) == + keccak256(bytes("LDADiamondLoupeFacet")) + ) { + continue; + } + // Skip LDADiamondCutFacet as it was already handled during LDA diamond deployment + if ( + keccak256(bytes(facetName)) == + keccak256(bytes("LDADiamondCutFacet")) + ) { + continue; + } + + emit log("Now adding LDA facet: "); + emit log(facetName); + // Use _getConfigContractAddress which validates the contract exists + address facetAddress = _getConfigContractAddress( + path, + string.concat(".", facetName) + ); + bytes4[] memory selectors = getSelectors(facetName, exclude); + + // at this point we know for sure that LDA diamond loupe exists on diamond + buildDiamondCut(selectors, facetAddress); + } + + // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + LDADiamondCutFacet.diamondCut.selector, + cut, + address(0), + "" + ); + } + emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); + emit log_bytes(cutData); + emit log("=== END CALLDATA ==="); + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + facets = loupe.facetAddresses(); + } +} diff --git a/script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol b/script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol new file mode 100644 index 000000000..a25a05e60 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; + +contract DeployScript is UpdateScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("NativeWrapperFacet"); + } +} diff --git a/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol b/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol new file mode 100644 index 000000000..000ffc86f --- /dev/null +++ b/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { ScriptBase } from "../../utils/ScriptBase.sol"; +import { CREATE3Factory } from "create3-factory/CREATE3Factory.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; + +contract DeployLDAScriptBase is ScriptBase { + address internal predicted; + CREATE3Factory internal factory; + bytes32 internal salt; + + constructor(string memory contractName) { + address factoryAddress = vm.envAddress("CREATE3_FACTORY_ADDRESS"); + string memory saltPrefix = vm.envString("DEPLOYSALT"); + bool deployToDefaultLDADiamondAddress = vm.envOr( + "DEPLOY_TO_DEFAULT_LDA_DIAMOND_ADDRESS", + false + ); + + // Special handling for LDADiamond if default address deployment is enabled + // This allows for deterministic LDA diamond addresses similar to LiFi diamond + if ( + keccak256(abi.encodePacked(contractName)) == + keccak256(abi.encodePacked("LDADiamond")) && + deployToDefaultLDADiamondAddress + ) { + // Use a different salt for LDA diamond to avoid conflicts with LiFi diamond + salt = vm.envOr( + "DEFAULT_LDA_DIAMOND_ADDRESS_DEPLOYSALT", + keccak256(abi.encodePacked(saltPrefix, "LDA", contractName)) + ); + } else { + // For all other LDA contracts, use standard salt with LDA prefix to avoid conflicts + salt = keccak256(abi.encodePacked(saltPrefix, "LDA", contractName)); + } + + factory = CREATE3Factory(factoryAddress); + predicted = factory.getDeployed(deployerAddress, salt); + } + + function getConstructorArgs() internal virtual returns (bytes memory) {} + + function deploy( + bytes memory creationCode + ) internal virtual returns (address payable deployed) { + bytes memory constructorArgs = getConstructorArgs(); + + vm.startBroadcast(deployerPrivateKey); + emit log_named_address("LI.FI LDA: Predicted Address: ", predicted); + + if (LibAsset.isContract(predicted)) { + emit log("LI.FI LDA: Contract is already deployed"); + return payable(predicted); + } + + // @DEV: activate on demand when deployment fails (e.g. to try manual deployment) + // reproduce and log calldata that is sent to CREATE3 + // bytes memory create3Calldata = abi.encodeWithSelector( + // CREATE3Factory.deploy.selector, + // salt, + // bytes.concat(creationCode, constructorArgs) + // ); + // emit log("LI.FI LDA: Will send this calldata to CREATE3Factory now: "); + // emit log_bytes(create3Calldata); + // emit log(" "); + + deployed = payable( + factory.deploy(salt, bytes.concat(creationCode, constructorArgs)) + ); + + vm.stopBroadcast(); + } +} diff --git a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol new file mode 100644 index 000000000..f772a298c --- /dev/null +++ b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { ScriptBase } from "../../utils/ScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; + +contract UpdateLDAScriptBase is ScriptBase { + using stdJson for string; + + error InvalidHexDigit(uint8 d); + + address internal ldaDiamond; + LibDiamond.FacetCut[] internal cut; + bytes4[] internal selectorsToReplace; + bytes4[] internal selectorsToRemove; + bytes4[] internal selectorsToAdd; + LDADiamondCutFacet internal cutter; + LDADiamondLoupeFacet internal loupe; + string internal path; + string internal json; + bool internal noBroadcast = false; + + constructor() { + noBroadcast = vm.envOr("NO_BROADCAST", false); + + // Create LDA-specific deployment file path + path = string.concat( + root, + "/deployments/", + network, + ".lda.", + fileSuffix, + "json" + ); + json = vm.readFile(path); + + // Get LDA Diamond address (not LiFi Diamond) + ldaDiamond = json.readAddress(".LDADiamond"); + cutter = LDADiamondCutFacet(ldaDiamond); + loupe = LDADiamondLoupeFacet(ldaDiamond); + } + + function update( + string memory name + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + address facet = json.readAddress(string.concat(".", name)); + + bytes4[] memory excludes = getExcludes(); + bytes memory callData = getCallData(); + + buildDiamondCut(getSelectors(name, excludes), facet); + + // prepare full diamondCut calldata and log for debugging purposes + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + LDADiamondCutFacet.diamondCut.selector, + cut, + callData.length > 0 ? facet : address(0), + callData + ); + + emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); + emit log_bytes(cutData); + emit log("=== END CALLDATA ==="); + } + + if (noBroadcast) { + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + + if (cut.length > 0) { + cutter.diamondCut( + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } + + function getExcludes() internal virtual returns (bytes4[] memory) {} + + function getCallData() internal virtual returns (bytes memory) {} + + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + + function buildDiamondCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + address oldFacet; + + selectorsToAdd = new bytes4[](0); + selectorsToReplace = new bytes4[](0); + selectorsToRemove = new bytes4[](0); + + // Get selectors to add or replace + for (uint256 i; i < newSelectors.length; i++) { + if (loupe.facetAddress(newSelectors[i]) == address(0)) { + selectorsToAdd.push(newSelectors[i]); + // Don't replace if the new facet address is the same as the old facet address + } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { + selectorsToReplace.push(newSelectors[i]); + oldFacet = loupe.facetAddress(newSelectors[i]); + } + } + + // Get selectors to remove + bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); + for (uint256 i; i < oldSelectors.length; i++) { + bool found = false; + for (uint256 j; j < newSelectors.length; j++) { + if (oldSelectors[i] == newSelectors[j]) { + found = true; + break; + } + } + if (!found) { + selectorsToRemove.push(oldSelectors[i]); + } + } + + // Build diamond cut + if (selectorsToReplace.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Replace, + functionSelectors: selectorsToReplace + }) + ); + } + + if (selectorsToRemove.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(0), + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: selectorsToRemove + }) + ); + } + + if (selectorsToAdd.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectorsToAdd + }) + ); + } + } + + function buildInitialCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: newSelectors + }) + ); + } + + function toHexDigit(uint8 d) internal pure returns (bytes1) { + if (0 <= d && d <= 9) { + return bytes1(uint8(bytes1("0")) + d); + } else if (10 <= uint8(d) && uint8(d) <= 15) { + return bytes1(uint8(bytes1("a")) + d - 10); + } + revert InvalidHexDigit(d); + } + + function fromCode(bytes4 code) public pure returns (string memory) { + bytes memory result = new bytes(10); + result[0] = bytes1("0"); + result[1] = bytes1("x"); + for (uint256 i = 0; i < 4; ++i) { + result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); + result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); + } + return string(result); + } +} diff --git a/script/deploy/updateLDADiamondLog.ts b/script/deploy/updateLDADiamondLog.ts new file mode 100644 index 000000000..f7d404660 --- /dev/null +++ b/script/deploy/updateLDADiamondLog.ts @@ -0,0 +1,223 @@ +#!/usr/bin/env bun + +import fs from 'fs' +import { defineCommand, runMain } from 'citty' +import consola from 'consola' +import { getEnvVar } from './safe/safe-utils' + +// Interface for LDA Diamond file structure +interface ILDADiamondFile { + [diamondName: string]: { + Facets: { + [address: string]: { + Name: string + Version: string + } + } + Periphery?: { + [name: string]: string + } + } +} + +const main = defineCommand({ + meta: { + name: 'updateLDADiamondLog', + description: 'Update LDA Diamond deployment logs', + }, + args: { + environment: { + type: 'string', + description: 'Environment (staging or production)', + required: true, + }, + network: { + type: 'string', + description: 'Network name (optional, if not provided updates all networks)', + required: false, + }, + contractName: { + type: 'string', + description: 'Contract name to update', + required: false, + }, + address: { + type: 'string', + description: 'Contract address', + required: false, + }, + version: { + type: 'string', + description: 'Contract version', + required: false, + }, + isPeriphery: { + type: 'boolean', + description: 'Whether the contract is periphery', + default: false, + }, + }, +}) + +const updateLDADiamond = function ( + name: string, + network: string, + address: string, + isProduction: boolean, + options: { + isPeriphery?: boolean + version?: string + } +) { + let data: ILDADiamondFile = {} + + const ldaDiamondContractName = 'LDADiamond' + + const ldaDiamondFile = isProduction + ? `deployments/${network}.lda.diamond.json` + : `deployments/${network}.lda.diamond.staging.json` + + try { + data = JSON.parse(fs.readFileSync(ldaDiamondFile, 'utf8')) as ILDADiamondFile + } catch { + // File doesn't exist yet, start with empty structure + } + + if (!data[ldaDiamondContractName]) + data[ldaDiamondContractName] = { + Facets: {}, + Periphery: {}, + } + + if (options.isPeriphery) { + data[ldaDiamondContractName].Periphery![name] = address + } else { + // Check if entry with name already exists + // If so, replace it + data[ldaDiamondContractName].Facets = Object.fromEntries( + Object.entries(data[ldaDiamondContractName].Facets).map(([key, value]) => { + if (value.Name === name) + return [address, { Name: name, Version: options.version || '' }] + + return [key, value] + }) + ) + // If not, add new entry + data[ldaDiamondContractName].Facets[address] = { + Name: name, + Version: options.version || '', + } + } + + fs.writeFileSync(ldaDiamondFile, JSON.stringify(data, null, 2)) + + consola.success(`Updated LDA diamond log for ${name} at ${address} in ${ldaDiamondFile}`) +} + +const updateAllLDANetworks = function (environment: string) { + try { + // Read networks configuration + const networksConfigPath = './config/networks.json' + if (!fs.existsSync(networksConfigPath)) { + consola.error('Networks config file not found') + return + } + + const networksConfig = JSON.parse(fs.readFileSync(networksConfigPath, 'utf8')) + const networks = Object.keys(networksConfig) + + consola.info(`Updating LDA diamond logs for ${networks.length} networks in ${environment} environment`) + + for (const network of networks) { + try { + // Read network-specific deployment file + const deploymentFileSuffix = environment === 'production' ? 'json' : 'staging.json' + const deploymentFile = `deployments/${network}.lda.${deploymentFileSuffix}` + + if (!fs.existsSync(deploymentFile)) { + consola.warn(`LDA deployment file not found for ${network}: ${deploymentFile}`) + continue + } + + const deployments = JSON.parse(fs.readFileSync(deploymentFile, 'utf8')) + + // Update LDA diamond log for each deployed contract + for (const [contractName, contractAddress] of Object.entries(deployments)) { + if (typeof contractAddress === 'string') { + // Get version from contract if possible (simplified version) + const version = '1.0.0' // Default version - could be enhanced to read actual version + + updateLDADiamond( + contractName, + network, + contractAddress, + environment === 'production', + { + isPeriphery: false, // Could be enhanced to detect periphery contracts + version, + } + ) + } + } + + consola.success(`Updated LDA diamond log for network: ${network}`) + } catch (error) { + consola.error(`Failed to update LDA diamond log for ${network}:`, error) + } + } + } catch (error) { + consola.error('Failed to update LDA diamond logs for all networks:', error) + } +} + +export default main + +export { updateLDADiamond, updateAllLDANetworks } + +// Handle direct execution +if (import.meta.main) { + runMain(main).then((args) => { + try { + const environment = args.environment + const network = args.network + const contractName = args.contractName + const address = args.address + const version = args.version + const isPeriphery = args.isPeriphery + + // Validate environment + if (!['staging', 'production'].includes(environment)) { + consola.error('Environment must be either "staging" or "production"') + process.exit(1) + } + + if (network && contractName && address) { + // Update specific contract + updateLDADiamond( + contractName, + network, + address, + environment === 'production', + { + isPeriphery, + version, + } + ) + } else if (network) { + // Update specific network + consola.info(`Updating LDA diamond log for network: ${network}`) + // This would need implementation for single network update + consola.warn('Single network update not yet implemented, updating all networks') + updateAllLDANetworks(environment) + } else { + // Update all networks + updateAllLDANetworks(environment) + } + + consola.success('LDA diamond log update completed') + } catch (error) { + consola.error('Failed to update LDA diamond log:', error) + process.exit(1) + } + }) +} diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 7ae645048..f1e9c684f 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4764,3 +4764,116 @@ function removeNetworkFromTargetStateJSON() { return 1 fi } + +# ==== LDA-SPECIFIC HELPER FUNCTIONS ==== + +# Deploy LDA Diamond with core facets +deployLDADiamondWithCoreFacets() { + local NETWORK="$1" + local ENVIRONMENT="$2" + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployLDADiamondWithCoreFacets" + + # load required resources + source script/config.sh + source script/helperFunctions.sh + source script/deploy/deployLDACoreFacets.sh + source script/deploy/deploySingleContract.sh + source script/tasks/ldaDiamondUpdateFacet.sh + + # Deploy LDA core facets first + deployLDACoreFacets "$NETWORK" "$ENVIRONMENT" + checkFailure $? "deploy LDA core facets to network $NETWORK" + + # Get LDA Diamond version + local VERSION=$(getCurrentContractVersion "LDADiamond") + + # Deploy LDA Diamond + deploySingleContract "LDADiamond" "$NETWORK" "$ENVIRONMENT" "$VERSION" "true" "true" + checkFailure $? "deploy LDADiamond to network $NETWORK" + + # Update LDA Diamond with core facets + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "LDADiamond" "UpdateLDACoreFacets" false + checkFailure $? "update LDA core facets in LDADiamond on network $NETWORK" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployLDADiamondWithCoreFacets completed" + + return 0 +} + +# Deploy and add contract to LDA Diamond +deployAndAddContractToLDADiamond() { + local NETWORK="$1" + local ENVIRONMENT="$2" + local CONTRACT="$3" + local DIAMOND_NAME="$4" + local VERSION="$5" + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployAndAddContractToLDADiamond" + + # load required resources + source script/config.sh + source script/helperFunctions.sh + source script/deploy/deploySingleContract.sh + source script/tasks/ldaDiamondUpdateFacet.sh + + # Deploy the contract (LDA contract) + deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "$VERSION" false "true" + checkFailure $? "deploy contract $CONTRACT to network $NETWORK" + + # Add contract to LDA Diamond + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_NAME" "Update${CONTRACT}" false + checkFailure $? "add contract $CONTRACT to $DIAMOND_NAME on network $NETWORK" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAndAddContractToLDADiamond completed" + + return 0 +} + +# Deploy facet and add to LDA Diamond +deployFacetAndAddToLDADiamond() { + local NETWORK="$1" + local ENVIRONMENT="$2" + local FACET_NAME="$3" + local DIAMOND_NAME="$4" + local VERSION="$5" + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployFacetAndAddToLDADiamond for $FACET_NAME" + + # Deploy the facet and add it to LDA Diamond + deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$DIAMOND_NAME" "$VERSION" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployFacetAndAddToLDADiamond completed for $FACET_NAME" + + return 0 +} + +# Get LDA deployment file path +getLDADeploymentFilePath() { + local NETWORK="$1" + local ENVIRONMENT="$2" + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + echo "deployments/${NETWORK}.lda.${FILE_SUFFIX}json" +} + +# Update LDA diamond logs +updateLDADiamondLogs() { + local ENVIRONMENT="$1" + local NETWORK="$2" + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start updateLDADiamondLogs" + + if [[ -n "$NETWORK" ]]; then + echo "[info] Updating LDA diamond log for network: $NETWORK" + bunx tsx script/deploy/updateLDADiamondLog.ts --environment "$ENVIRONMENT" --network "$NETWORK" + else + echo "[info] Updating LDA diamond logs for all networks" + bunx tsx script/deploy/updateLDADiamondLog.ts --environment "$ENVIRONMENT" + fi + + checkFailure $? "update LDA diamond logs" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< updateLDADiamondLogs completed" + + return 0 +} diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index 76bb942cb..524316308 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -39,8 +39,10 @@ scriptMaster() { # load deploy script & helper functions source script/deploy/deploySingleContract.sh source script/deploy/deployAllContracts.sh + source script/deploy/deployAllLDAContracts.sh source script/helperFunctions.sh source script/deploy/deployFacetAndAddToDiamond.sh + source script/deploy/deployLDACoreFacets.sh source script/deploy/deployPeripheryContracts.sh source script/deploy/deployUpgradesToSAFE.sh for script in script/tasks/*.sh; do [ -f "$script" ] && source "$script"; done # sources all script in folder script/tasks/ @@ -121,7 +123,11 @@ scriptMaster() { "10) Create updated target state from Google Docs (STAGING or PRODUCTION)" \ "11) Update diamond log(s)" \ "12) Propose upgrade TX to Gnosis SAFE" \ - "13) Remove facets or periphery from diamond" + "13) Remove facets or periphery from diamond" \ + "14) Deploy LDA Diamond with core facets to one network" \ + "15) Deploy all LDA contracts to one selected network (=new network)" \ + "16) Deploy one LDA facet to one network" \ + "17) Update LDA diamond log(s)" \ ) #--------------------------------------------------------------------------------------------------------------------- @@ -155,6 +161,21 @@ scriptMaster() { # get user-selected deploy script and contract from list SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") else + # Ask user to choose between regular and LDA contracts + echo "Which type of contract do you want to deploy?" + CONTRACT_TYPE=$( + gum choose \ + "Regular LiFi contracts" \ + "LDA (LiFi DEX Aggregator) contracts" + ) + + # Set script directory based on contract type + if [[ "$CONTRACT_TYPE" == "LDA"* ]]; then + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" + else + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" + fi + # get user-selected deploy script and contract from list SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") fi @@ -162,16 +183,30 @@ scriptMaster() { # get user-selected deploy script and contract from list CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') - # check if new contract should be added to diamond after deployment (only check for - if [[ ! "$CONTRACT" == "LiFiDiamond"* ]]; then - echo "" - echo "Do you want to add this contract to a diamond after deployment?" - ADD_TO_DIAMOND=$( - gum choose \ - "yes - to LiFiDiamond" \ - "yes - to LiFiDiamondImmutable" \ - " no - do not update any diamond" - ) + # check if new contract should be added to diamond after deployment + if [[ "$CONTRACT_TYPE" == "LDA"* ]]; then + # LDA contracts + if [[ ! "$CONTRACT" == "LDADiamond"* ]]; then + echo "" + echo "Do you want to add this LDA contract to a diamond after deployment?" + ADD_TO_DIAMOND=$( + gum choose \ + "yes - to LDADiamond" \ + " no - do not update any diamond" + ) + fi + else + # Regular LiFi contracts + if [[ ! "$CONTRACT" == "LiFiDiamond"* ]]; then + echo "" + echo "Do you want to add this contract to a diamond after deployment?" + ADD_TO_DIAMOND=$( + gum choose \ + "yes - to LiFiDiamond" \ + "yes - to LiFiDiamondImmutable" \ + " no - do not update any diamond" + ) + fi fi # get current contract version @@ -181,15 +216,21 @@ scriptMaster() { if [[ "$ADD_TO_DIAMOND" == "yes"* ]]; then echo "[info] selected option: $ADD_TO_DIAMOND" - # determine the name of the LiFiDiamond contract and call helper function with correct diamond name - if [[ "$ADD_TO_DIAMOND" == *"LiFiDiamondImmutable"* ]]; then + # determine the diamond type and call appropriate function + if [[ "$ADD_TO_DIAMOND" == *"LDADiamond"* ]]; then + deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LDADiamond" "$VERSION" + elif [[ "$ADD_TO_DIAMOND" == *"LiFiDiamondImmutable"* ]]; then deployAndAddContractToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamondImmutable" "$VERSION" else deployAndAddContractToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamond" "$VERSION" fi else - # just deploy the contract - deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false + # just deploy the contract (determine if LDA based on CONTRACT_TYPE) + if [[ "$CONTRACT_TYPE" == "LDA"* ]]; then + deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "true" + else + deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "false" + fi fi # check if last command was executed successfully, otherwise exit script with error message @@ -555,6 +596,134 @@ scriptMaster() { elif [[ "$SELECTION" == "13)"* ]]; then bunx tsx script/tasks/cleanUpProdDiamond.ts + #--------------------------------------------------------------------------------------------------------------------- + # use case 14: Deploy LDA Diamond with core facets to one network + elif [[ "$SELECTION" == "14)"* ]]; then + echo "" + echo "[info] selected use case: Deploy LDA Diamond with core facets to one network" + + checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" + # get user-selected network from list + local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") + echo "[info] selected network: $NETWORK" + + # get deployer wallet balance + BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") + echo "[info] deployer wallet balance in this network: $BALANCE" + echo "" + checkRequiredVariablesInDotEnv "$NETWORK" + + # call LDA deploy script + deployLDADiamondWithCoreFacets "$NETWORK" "$ENVIRONMENT" + + # check if last command was executed successfully, otherwise exit script with error message + checkFailure $? "deploy LDA Diamond with core facets to network $NETWORK" + + playNotificationSound + + #--------------------------------------------------------------------------------------------------------------------- + # use case 15: Deploy all LDA contracts to one selected network (=new network) + elif [[ "$SELECTION" == "15)"* ]]; then + echo "" + echo "[info] selected use case: Deploy all LDA contracts to one selected network (=new network)" + + checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" + # get user-selected network from list + local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") + echo "[info] selected network: $NETWORK" + + # call deploy script + deployAllLDAContracts "$NETWORK" "$ENVIRONMENT" + + # check if last command was executed successfully, otherwise exit script with error message + checkFailure $? "deploy all LDA contracts to network $NETWORK" + + playNotificationSound + + #--------------------------------------------------------------------------------------------------------------------- + # use case 16: Deploy one LDA facet to one network + elif [[ "$SELECTION" == "16)"* ]]; then + echo "" + echo "[info] selected use case: Deploy one LDA facet to one network" + + checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" + # get user-selected network from list + local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") + + echo "[info] selected network: $NETWORK" + echo "[info] loading deployer wallet balance..." + + # get deployer wallet balance + BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") + + echo "[info] deployer wallet balance in this network: $BALANCE" + echo "" + checkRequiredVariablesInDotEnv "$NETWORK" + + # get user-selected LDA deploy script and contract from list + SCRIPT=$(ls -1 "script/deploy/facets/LDA/" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "LDA Deploy Script") + CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') + + # check if new contract should be added to LDA diamond after deployment + if [[ ! "$CONTRACT" == "LDADiamond"* ]]; then + echo "" + echo "Do you want to add this LDA contract to LDADiamond after deployment?" + ADD_TO_DIAMOND=$( + gum choose \ + "yes - to LDADiamond" \ + " no - do not update any diamond" + ) + fi + + # get current contract version + local VERSION=$(getCurrentContractVersion "$CONTRACT") + + # check if contract should be added after deployment + if [[ "$ADD_TO_DIAMOND" == "yes"* ]]; then + echo "[info] selected option: $ADD_TO_DIAMOND" + deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LDADiamond" "$VERSION" + else + # just deploy the LDA contract + deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "true" + fi + + # check if last command was executed successfully, otherwise exit script with error message + checkFailure $? "deploy LDA contract $CONTRACT to network $NETWORK" + + #--------------------------------------------------------------------------------------------------------------------- + # use case 17: Update LDA diamond log(s) + elif [[ "$SELECTION" == "17)"* ]]; then + # ask user if logs should be updated only for one network or for all networks + echo "Would you like to update LDA logs for all networks or one specific network?" + SELECTION_NETWORK=$( + gum choose \ + "1) All networks" \ + "2) One specific network (selection in next screen)" + ) + echo "[info] selected option: $SELECTION_NETWORK" + + if [[ "$SELECTION_NETWORK" == "1)"* ]]; then + # call update LDA diamond log function for all networks + updateLDADiamondLogs "$ENVIRONMENT" + else + checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" + # get user-selected network from list + local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") + + echo "[info] selected network: $NETWORK" + echo "[info] loading deployer wallet balance..." + + # get deployer wallet balance + BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") + + echo "[info] deployer wallet balance in this network: $BALANCE" + echo "" + checkRequiredVariablesInDotEnv "$NETWORK" + + # call update LDA diamond log function for specific network + updateLDADiamondLogs "$ENVIRONMENT" "$NETWORK" + fi + else error "invalid use case selected ('$SELECTION') - exiting script" cleanup diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh new file mode 100644 index 000000000..a31b582c3 --- /dev/null +++ b/script/tasks/ldaDiamondUpdateFacet.sh @@ -0,0 +1,168 @@ +#!/bin/bash + +# executes a diamond update script to update an LDA facet on LDADiamond +function ldaDiamondUpdateFacet() { + + # load required variables and helper functions + source script/config.sh + source script/helperFunctions.sh + + # read function arguments into variables + local NETWORK="$1" + local ENVIRONMENT="$2" + local DIAMOND_CONTRACT_NAME="$3" + local UPDATE_SCRIPT="$4" + local SHOW_LOGS="$5" + + # if no NETWORK was passed to this function, ask user to select it + if [[ -z "$NETWORK" ]]; then + checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" + NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") + checkRequiredVariablesInDotEnv $NETWORK + fi + + # if no ENVIRONMENT was passed to this function, determine it + if [[ -z "$ENVIRONMENT" ]]; then + if [[ "$PRODUCTION" == "true" ]]; then + # make sure that PRODUCTION was selected intentionally by user + echo " " + echo " " + printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!! ATTENTION !!!!!!!!!!!!!!!!!!!!!!!!"; + printf '\033[33m%s\033[0m\n' "The config environment variable PRODUCTION is set to true"; + printf '\033[33m%s\033[0m\n' "This means you will be updating LDA contracts in production"; + printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; + echo " " + printf '\033[33m%s\033[0m\n' "Last chance: Do you want to skip?"; + PROD_SELECTION=$(gum choose \ + "yes" \ + "no" \ + ) + + if [[ $PROD_SELECTION != "no" ]]; then + echo "...exiting script" + exit 0 + fi + + ENVIRONMENT="production" + else + ENVIRONMENT="staging" + fi + fi + + # get file suffix based on value in variable ENVIRONMENT + FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # if no DIAMOND_CONTRACT_NAME was passed to this function, default to LDADiamond + if [[ -z "$DIAMOND_CONTRACT_NAME" ]]; then + DIAMOND_CONTRACT_NAME="LDADiamond" + fi + + # if no UPDATE_SCRIPT was passed to this function, ask user to select it + if [[ -z "$UPDATE_SCRIPT" ]]; then + echo "" + echo "Please select which LDA update script you would like to execute" + local SCRIPT=$(ls -1 script/deploy/facets/LDA/ | sed -e 's/\.s.sol$//' | grep 'Update' | gum filter --placeholder "Update LDA Script") + UPDATE_SCRIPT="$SCRIPT" + fi + + # set LDA-specific script directory + LDA_UPDATE_SCRIPT_PATH="script/deploy/facets/LDA/${UPDATE_SCRIPT}.s.sol" + + # check if LDA update script exists + if ! checkIfFileExists "$LDA_UPDATE_SCRIPT_PATH" >/dev/null; then + error "could not find LDA update script for $UPDATE_SCRIPT in this path: $LDA_UPDATE_SCRIPT_PATH." + return 1 + fi + + # get LDA diamond address from LDA deployment file + local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.${FILE_SUFFIX}json" + local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$LDA_DEPLOYMENT_FILE") + + # if no diamond address was found, throw an error and exit this script + if [[ "$DIAMOND_ADDRESS" == "null" ]]; then + error "could not find address for $DIAMOND_CONTRACT_NAME on network $NETWORK in file '$LDA_DEPLOYMENT_FILE' - exiting script now" + return 1 + fi + + if [[ -z "$GAS_ESTIMATE_MULTIPLIER" ]]; then + GAS_ESTIMATE_MULTIPLIER=130 # this is foundry's default value + fi + + # logging for debug purposes + if [[ $SHOW_LOGS == "true" ]]; then + echo "" + echoDebug "in function ldaDiamondUpdateFacet" + echoDebug "NETWORK=$NETWORK" + echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" + echoDebug "UPDATE_SCRIPT=$UPDATE_SCRIPT" + echoDebug "LDA_UPDATE_SCRIPT_PATH=$LDA_UPDATE_SCRIPT_PATH" + echoDebug "DIAMOND_ADDRESS=$DIAMOND_ADDRESS" + echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echoDebug "GAS_ESTIMATE_MULTIPLIER=$GAS_ESTIMATE_MULTIPLIER (default value: 130, set in .env for example to 200 for doubling Foundry's estimate)" + echo "" + fi + + # execute LDA diamond update script + local attempts=1 + + while [ $attempts -le "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; do + echo "[info] trying to execute LDA diamond update script $UPDATE_SCRIPT now - attempt ${attempts} (max attempts: $MAX_ATTEMPTS_PER_SCRIPT_EXECUTION) " + + # ensure that gas price is below maximum threshold (for mainnet only) + doNotContinueUnlessGasIsBelowThreshold "$NETWORK" + + # try to execute call + RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") + + local RETURN_CODE=$? + + # print return data only if debug mode is activated + echoDebug "RAW_RETURN_DATA: $RAW_RETURN_DATA" + + # check return data for error message (regardless of return code as this is not 100% reliable) + if [[ $RAW_RETURN_DATA == *"\"logs\":[]"* && $RAW_RETURN_DATA == *"\"returns\":{}"* ]]; then + warning "The transaction was executed but the return value suggests that no logs were emitted" + warning "This happens if contracts are already up-to-date." + warning "This may also be a sign that the transaction was not executed properly." + warning "Please check manually if the transaction was executed and if the LDA diamond was updated" + echo "" + return 0 + fi + + # get returned JSON + JSON_RETURN_DATA=$(getJsonFromRawForgeScriptReturnValue "$RAW_RETURN_DATA") + + # check whether call was successful or not + if [[ $RETURN_CODE -eq 0 ]] && [[ ! -z "$JSON_RETURN_DATA" ]]; then + + # check whether the call was successful + local CONTRACT_ADDRESS=$(echo "$JSON_RETURN_DATA" | jq -r '.returns.deployed.value') + + echo "[info] LDA diamond update was successful" + if [[ $SHOW_LOGS == "true" ]]; then + echo "[info] new contract address: $CONTRACT_ADDRESS" + fi + + return 0 + + else + echo "[error] Call failed with error code $RETURN_CODE" + echo "[error] Error message: $RAW_RETURN_DATA" + + attempts=$((attempts + 1)) + + # exit the loop if this was the last attempt + if [ $attempts -gt "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; then + error "max attempts reached, execution of LDA diamond update for $UPDATE_SCRIPT failed" + return 1 + fi + + # wait a bit before retrying + echo "retrying in $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR seconds..." + sleep $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR + fi + done + + return 1 +} \ No newline at end of file diff --git a/test/solidity/Periphery/LDA/BaseUniV2StyleDEXFacet.t.sol b/test/solidity/Periphery/LDA/BaseUniV2StyleDEXFacet.t.sol index a0c8dbd77..c56075d10 100644 --- a/test/solidity/Periphery/LDA/BaseUniV2StyleDEXFacet.t.sol +++ b/test/solidity/Periphery/LDA/BaseUniV2StyleDEXFacet.t.sol @@ -74,7 +74,7 @@ abstract contract BaseUniV2StyleDEXFacetTest is BaseDEXFacetTest { /// @return Packed payload starting with `swapUniV2` selector. function _buildUniV2SwapData( UniV2SwapParams memory params - ) internal returns (bytes memory) { + ) internal view returns (bytes memory) { return abi.encodePacked( uniV2Facet.swapUniV2.selector, diff --git a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol index 8e10b10ac..dbfe8458f 100644 --- a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol +++ b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { LibAllowList, TestBase } from "../../../../utils/TestBase.sol"; -import { OnlyContractOwner, UnAuthorized, DiamondIsPaused } from "src/Errors/GenericErrors.sol"; +import { OnlyContractOwner, UnAuthorized, DiamondIsPaused } from "lifi/Errors/GenericErrors.sol"; import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; From c82a1d369b5c68b45eabc36f8a4533a749fe0c1e Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 1 Sep 2025 16:11:47 +0200 Subject: [PATCH 138/220] Add ownership transfer stage to LDA deployment script, including health check confirmation and user prompt for production environment. Introduce new ldaHealthCheck.ts script for verifying LDA diamond configuration and deployed contracts. Update existing deployment scripts for consistency and error handling. --- script/deploy/deployAllLDAContracts.sh | 73 ++++- .../deploy/deployFacetAndAddToLDADiamond.sh | 1 + .../facets/LDA/DeployCoreRouteFacet.s.sol | 5 - .../facets/LDA/UpdateLDACoreFacets.s.sol | 38 ++- .../facets/LDA/utils/DeployLDAScriptBase.sol | 6 +- .../facets/LDA/utils/UpdateLDAScriptBase.sol | 2 +- script/deploy/ldaHealthCheck.ts | 250 ++++++++++++++++++ script/deploy/updateLDADiamondLog.ts | 223 ---------------- script/tasks/ldaDiamondUpdateFacet.sh | 52 ++-- 9 files changed, 386 insertions(+), 264 deletions(-) create mode 100644 script/deploy/ldaHealthCheck.ts delete mode 100644 script/deploy/updateLDADiamondLog.ts diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 856a659f5..858e9aac5 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -37,7 +37,8 @@ deployAllLDAContracts() { "2) Deploy LDA core facets" \ "3) Deploy LDA diamond and update with core facets" \ "4) Deploy LDA DEX facets and add to diamond" \ - "5) Run LDA health check only" + "5) Run LDA health check only" \ + "6) Ownership transfer to timelock (production only)" ) # Extract the stage number from the selection @@ -51,6 +52,8 @@ deployAllLDAContracts() { START_STAGE=4 elif [[ "$START_FROM" == *"5)"* ]]; then START_STAGE=5 + elif [[ "$START_FROM" == *"6)"* ]]; then + START_STAGE=6 else error "invalid selection: $START_FROM - exiting script now" exit 1 @@ -220,8 +223,74 @@ deployAllLDAContracts() { if [[ $START_STAGE -le 5 ]]; then echo "" echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Run LDA health check only" - echo "[info] LDA health check functionality will be implemented later" + bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" + + # Pause and ask user if they want to continue with ownership transfer (for production) + if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 5 ]]; then + echo "" + echo "Health check completed. Do you want to continue with ownership transfer to timelock?" + echo "This should only be done if the health check shows only diamond ownership errors." + echo "Continue with stage 6 (ownership transfer)? (y/n)" + read -r response + if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + echo "Proceeding with stage 6..." + else + echo "Skipping stage 6 - ownership transfer cancelled by user" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" + return + fi + fi + fi + + # Stage 6: Ownership transfer to timelock (production only) + if [[ $START_STAGE -le 6 ]]; then + if [[ "$ENVIRONMENT" == "production" ]]; then + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Ownership transfer to timelock (production only)" + + # make sure SAFE_ADDRESS is available (if starting in stage 6 it's not available yet) + SAFE_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.safeAddress") + if [[ -z "$SAFE_ADDRESS" || "$SAFE_ADDRESS" == "null" ]]; then + echo "SAFE address not found in networks.json. Cannot prepare ownership transfer to Timelock" + exit 1 + fi + + # ------------------------------------------------------------ + # Prepare ownership transfer to Timelock + echo "" + echo "Preparing LDA Diamond ownership transfer to Timelock" + TIMELOCK_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "LiFiTimelockController") + if [[ -z "$TIMELOCK_ADDRESS" ]]; then + echo "Timelock address not found. Cannot prepare ownership transfer to Timelock" + exit 1 + fi + + # get LDA diamond address + LDA_DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME") + if [[ -z "$LDA_DIAMOND_ADDRESS" ]]; then + echo "LDA Diamond address not found. Cannot prepare ownership transfer to Timelock" + exit 1 + fi + + # initiate ownership transfer + echo "Initiating LDA Diamond ownership transfer to LiFiTimelockController ($TIMELOCK_ADDRESS)" + cast send "$LDA_DIAMOND_ADDRESS" "transferOwnership(address)" "$TIMELOCK_ADDRESS" --private-key "$PRIVATE_KEY_PRODUCTION" --rpc-url "$(getRPCUrl "$NETWORK")" --legacy + echo "LDA Diamond ownership transfer to LiFiTimelockController ($TIMELOCK_ADDRESS) initiated" + echo "" + + echo "" + echo "Proposing LDA Diamond ownership transfer acceptance tx to multisig ($SAFE_ADDRESS) via LiFiTimelockController ($TIMELOCK_ADDRESS)" + # propose tx with calldata 0x79ba5097 = confirmOwnershipTransfer() to LDA diamond (propose to multisig and wrap in timelock calldata with --timelock flag) + bun script/deploy/safe/propose-to-safe.ts --to "$LDA_DIAMOND_ADDRESS" --calldata 0x79ba5097 --network "$NETWORK" --rpcUrl "$(getRPCUrl "$NETWORK")" --privateKey "$PRIVATE_KEY_PRODUCTION" --timelock + echo "LDA Diamond ownership transfer acceptance proposed to multisig ($SAFE_ADDRESS) via LiFiTimelockController ($TIMELOCK_ADDRESS)" + echo "" + # ------------------------------------------------------------ + else + echo "Stage 6 skipped - ownership transfer to timelock is only for production environment" + fi + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 6 completed" fi echo "" diff --git a/script/deploy/deployFacetAndAddToLDADiamond.sh b/script/deploy/deployFacetAndAddToLDADiamond.sh index d826934eb..c1cb717d1 100644 --- a/script/deploy/deployFacetAndAddToLDADiamond.sh +++ b/script/deploy/deployFacetAndAddToLDADiamond.sh @@ -121,3 +121,4 @@ function deployFacetAndAddToLDADiamond() { echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA $FACET_CONTRACT_NAME successfully deployed and added to $DIAMOND_CONTRACT_NAME" return 0 } + diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index 3621e6ac1..8de8482da 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -10,9 +10,4 @@ contract DeployScript is DeployLDAScriptBase { function run() public returns (CoreRouteFacet deployed) { deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); } - - function getConstructorArgs() internal view override returns (bytes memory) { - // CoreRouteFacet constructor requires _owner parameter - return abi.encode(deployerAddress); - } } diff --git a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol index e67f0ec45..87b55f810 100644 --- a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol +++ b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol @@ -4,12 +4,41 @@ pragma solidity ^0.8.17; import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { IERC173 } from "lifi/Interfaces/IERC173.sol"; +import { TransferrableOwnership } from "lifi/Helpers/TransferrableOwnership.sol"; contract UpdateLDACoreFacets is UpdateLDAScriptBase { using stdJson for string; error FailedToReadLDACoreFacetsFromConfig(); + /// @notice Returns function selectors to exclude for specific facets + /// @param facetName The name of the facet being processed + function getExcludes( + string memory facetName + ) internal pure returns (bytes4[] memory) { + // Exclude ownership function selectors from CoreRouteFacet to avoid collision with LDAOwnershipFacet + if ( + keccak256(bytes(facetName)) == keccak256(bytes("CoreRouteFacet")) + ) { + bytes4[] memory excludes = new bytes4[](5); + excludes[0] = IERC173.transferOwnership.selector; + excludes[1] = TransferrableOwnership + .cancelOwnershipTransfer + .selector; + excludes[2] = TransferrableOwnership + .confirmOwnershipTransfer + .selector; + excludes[3] = IERC173.owner.selector; + excludes[4] = bytes4(keccak256("pendingOwner()")); // public state variable not a function + return excludes; + } + + // No exclusions for other facets + bytes4[] memory emptyExcludes = new bytes4[](0); + return emptyExcludes; + } + function run() public returns (address[] memory facets, bytes memory cutData) @@ -27,8 +56,6 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log("LDA core facets found in config/global.json: "); emit log_uint(ldaCoreFacets.length); - bytes4[] memory exclude; - // Check if the LDA loupe was already added to the diamond bool loupeExists; try loupe.facetAddresses() returns (address[] memory) { @@ -48,7 +75,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { ); bytes4[] memory loupeSelectors = getSelectors( "LDADiamondLoupeFacet", - exclude + getExcludes("LDADiamondLoupeFacet") ); buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); @@ -88,7 +115,10 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { path, string.concat(".", facetName) ); - bytes4[] memory selectors = getSelectors(facetName, exclude); + bytes4[] memory selectors = getSelectors( + facetName, + getExcludes(facetName) + ); // at this point we know for sure that LDA diamond loupe exists on diamond buildDiamondCut(selectors, facetAddress); diff --git a/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol b/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol index 000ffc86f..48ad7ef41 100644 --- a/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol +++ b/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol @@ -32,9 +32,11 @@ contract DeployLDAScriptBase is ScriptBase { ); } else { // For all other LDA contracts, use standard salt with LDA prefix to avoid conflicts - salt = keccak256(abi.encodePacked(saltPrefix, "LDA", contractName)); + salt = keccak256( + abi.encodePacked(saltPrefix, "LDA", contractName) + ); } - + factory = CREATE3Factory(factoryAddress); predicted = factory.getDeployed(deployerAddress, salt); } diff --git a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol index f772a298c..d6876b1b4 100644 --- a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol @@ -36,7 +36,7 @@ contract UpdateLDAScriptBase is ScriptBase { "json" ); json = vm.readFile(path); - + // Get LDA Diamond address (not LiFi Diamond) ldaDiamond = json.readAddress(".LDADiamond"); cutter = LDADiamondCutFacet(ldaDiamond); diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts new file mode 100644 index 000000000..2b81ed17c --- /dev/null +++ b/script/deploy/ldaHealthCheck.ts @@ -0,0 +1,250 @@ +#!/usr/bin/env node + +import { execSync } from 'child_process' + +import { defineCommand, runMain } from 'citty' +import { consola } from 'consola' + +const errors: string[] = [] + +// Helper function to get RPC URL from networks.json +const getRpcUrl = ( + network: string, + networksConfig: Record +): string => { + return networksConfig[network.toLowerCase()]?.rpcUrl || '' +} + +// Helper function to check if contract is deployed using cast +const checkIsDeployedWithCast = async ( + contractName: string, + deployedContracts: Record, + rpcUrl: string +): Promise => { + if (!deployedContracts[contractName]) return false + + try { + const address = deployedContracts[contractName] + const result = execSync(`cast code ${address} --rpc-url "${rpcUrl}"`, { + encoding: 'utf8', + stdio: 'pipe', + }).trim() + + // If the result is '0x' or empty, contract is not deployed + return result !== '0x' && result !== '' + } catch (error) { + return false + } +} + +const main = defineCommand({ + meta: { + name: 'LDA Diamond Health Check', + description: 'Check that the LDA diamond is configured correctly', + }, + args: { + network: { + type: 'string', + description: 'EVM network to check', + required: true, + }, + }, + async run({ args }) { + const { network } = args + + // Skip tronshasta testnet but allow tron mainnet + if (network.toLowerCase() === 'tronshasta') { + consola.info('Health checks are not implemented for Tron Shasta testnet.') + consola.info('Skipping all tests.') + process.exit(0) + } + + // Determine if we're working with Tron mainnet + const isTron = network.toLowerCase() === 'tron' + + if (isTron) { + consola.info('LDA health checks for Tron are not yet implemented.') + consola.info('Skipping all tests.') + process.exit(0) + } + + // Load LDA-specific deployments (production only) + const ldaDeploymentFile = `../../deployments/${network.toLowerCase()}.lda.production.json` + let ldaDeployedContracts: Record + + try { + const { default: contracts } = await import(ldaDeploymentFile) + ldaDeployedContracts = contracts + } catch (error) { + consola.error(`Failed to load LDA deployment file: ${ldaDeploymentFile}`) + consola.error( + 'Please ensure LDA contracts are deployed to production first.' + ) + process.exit(1) + } + + // Load main deployment file for shared infrastructure (like LiFiTimelockController) + const mainDeploymentFile = `../../deployments/${network.toLowerCase()}.json` + let mainDeployedContracts: Record = {} + + try { + const { default: contracts } = await import(mainDeploymentFile) + mainDeployedContracts = contracts + } catch (error) { + consola.warn(`Failed to load main deployment file: ${mainDeploymentFile}`) + consola.warn('Some shared infrastructure checks will be skipped.') + } + + // Note: We keep LDA and main contracts separate for clarity + + // Load global config for LDA core facets + const globalConfig = await import('../../config/global.json') + const networksConfigModule = await import('../../config/networks.json') + const networksConfig = networksConfigModule.default + + // Get LDA core facets from global config + const ldaCoreFacets = globalConfig.ldaCoreFacets || [] + + // Get RPC URL + const rpcUrl = getRpcUrl(network, networksConfig) + if (!rpcUrl) { + consola.error(`No RPC URL found for network: ${network}`) + process.exit(1) + } + + consola.info('Running LDA Diamond post deployment checks...\n') + + // ╭─────────────────────────────────────────────────────────╮ + // │ Check LDA Diamond Contract │ + // ╰─────────────────────────────────────────────────────────╯ + consola.box('Checking LDADiamond Contract...') + const diamondDeployed = await checkIsDeployedWithCast( + 'LDADiamond', + ldaDeployedContracts, + rpcUrl + ) + + if (!diamondDeployed) { + logError('LDADiamond not deployed') + finish() + } else consola.success('LDADiamond deployed') + + const diamondAddress = ldaDeployedContracts['LDADiamond'] + + // ╭─────────────────────────────────────────────────────────╮ + // │ Check LDA core facets │ + // ╰─────────────────────────────────────────────────────────╯ + consola.box('Checking LDA Core Facets...') + for (const facet of ldaCoreFacets) { + const isDeployed = await checkIsDeployedWithCast( + facet, + ldaDeployedContracts, + rpcUrl + ) + + if (!isDeployed) { + logError(`LDA Facet ${facet} not deployed`) + continue + } + consola.success(`LDA Facet ${facet} deployed`) + } + + // ╭─────────────────────────────────────────────────────────╮ + // │ Check that LDA facets are registered │ + // ╰─────────────────────────────────────────────────────────╯ + consola.box('Checking LDA facets registered in diamond...') + + let registeredFacets: string[] = [] + try { + // Use cast to call facets() function + const rawString = execSync( + `cast call "${diamondAddress}" "facets() returns ((address,bytes4[])[])" --rpc-url "${rpcUrl}"`, + { encoding: 'utf8', stdio: 'pipe' } + ) + + const jsonCompatibleString = rawString + .replace(/\(/g, '[') + .replace(/\)/g, ']') + .replace(/0x[0-9a-fA-F]+/g, '"$&"') + + const onChainFacets = JSON.parse(jsonCompatibleString) + + if (Array.isArray(onChainFacets)) { + const configFacetsByAddress = Object.fromEntries( + Object.entries(ldaDeployedContracts).map(([name, address]) => { + return [address.toLowerCase(), name] + }) + ) + + registeredFacets = onChainFacets + .map(([address]) => configFacetsByAddress[address.toLowerCase()]) + .filter(Boolean) as string[] + } + } catch (error) { + consola.warn( + 'Unable to call facets() - skipping facet registration check' + ) + consola.warn('Error:', (error as Error).message) + } + + for (const facet of ldaCoreFacets) + if (!registeredFacets.includes(facet)) + logError( + `LDA Facet ${facet} not registered in Diamond or possibly unverified` + ) + else consola.success(`LDA Facet ${facet} registered in Diamond`) + + // Basic ownership check using cast + try { + consola.box('Checking LDA Diamond ownership...') + const owner = execSync( + `cast call "${diamondAddress}" "owner() returns (address)" --rpc-url "${rpcUrl}"`, + { encoding: 'utf8', stdio: 'pipe' } + ).trim() + + consola.info(`LDADiamond current owner: ${owner}`) + + // Check if timelock is deployed and compare (timelock is in main deployments, not LDA deployments) + const timelockAddress = mainDeployedContracts['LiFiTimelockController'] + if (timelockAddress) { + consola.info(`Found LiFiTimelockController at: ${timelockAddress}`) + if (owner.toLowerCase() === timelockAddress.toLowerCase()) + consola.success('LDADiamond is owned by LiFiTimelockController') + else + logError(`LDADiamond owner is ${owner}, expected ${timelockAddress}`) + } else { + consola.error( + 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' + ) + consola.info( + 'Note: LiFiTimelockController should be deployed as shared infrastructure before LDA deployment' + ) + } + } catch (error) { + logError( + `Failed to check LDADiamond ownership: ${(error as Error).message}` + ) + } + + finish() + }, +}) + +const logError = (msg: string) => { + consola.error(msg) + errors.push(msg) +} + +const finish = () => { + // this line ensures that all logs are actually written before the script ends + process.stdout.write('', () => process.stdout.end()) + if (errors.length) { + consola.error(`${errors.length} Errors found in LDA Diamond deployment`) + process.exit(1) + } else { + consola.success('LDA Diamond deployment checks passed') + process.exit(0) + } +} + +runMain(main) diff --git a/script/deploy/updateLDADiamondLog.ts b/script/deploy/updateLDADiamondLog.ts deleted file mode 100644 index f7d404660..000000000 --- a/script/deploy/updateLDADiamondLog.ts +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/env bun - -import fs from 'fs' -import { defineCommand, runMain } from 'citty' -import consola from 'consola' -import { getEnvVar } from './safe/safe-utils' - -// Interface for LDA Diamond file structure -interface ILDADiamondFile { - [diamondName: string]: { - Facets: { - [address: string]: { - Name: string - Version: string - } - } - Periphery?: { - [name: string]: string - } - } -} - -const main = defineCommand({ - meta: { - name: 'updateLDADiamondLog', - description: 'Update LDA Diamond deployment logs', - }, - args: { - environment: { - type: 'string', - description: 'Environment (staging or production)', - required: true, - }, - network: { - type: 'string', - description: 'Network name (optional, if not provided updates all networks)', - required: false, - }, - contractName: { - type: 'string', - description: 'Contract name to update', - required: false, - }, - address: { - type: 'string', - description: 'Contract address', - required: false, - }, - version: { - type: 'string', - description: 'Contract version', - required: false, - }, - isPeriphery: { - type: 'boolean', - description: 'Whether the contract is periphery', - default: false, - }, - }, -}) - -const updateLDADiamond = function ( - name: string, - network: string, - address: string, - isProduction: boolean, - options: { - isPeriphery?: boolean - version?: string - } -) { - let data: ILDADiamondFile = {} - - const ldaDiamondContractName = 'LDADiamond' - - const ldaDiamondFile = isProduction - ? `deployments/${network}.lda.diamond.json` - : `deployments/${network}.lda.diamond.staging.json` - - try { - data = JSON.parse(fs.readFileSync(ldaDiamondFile, 'utf8')) as ILDADiamondFile - } catch { - // File doesn't exist yet, start with empty structure - } - - if (!data[ldaDiamondContractName]) - data[ldaDiamondContractName] = { - Facets: {}, - Periphery: {}, - } - - if (options.isPeriphery) { - data[ldaDiamondContractName].Periphery![name] = address - } else { - // Check if entry with name already exists - // If so, replace it - data[ldaDiamondContractName].Facets = Object.fromEntries( - Object.entries(data[ldaDiamondContractName].Facets).map(([key, value]) => { - if (value.Name === name) - return [address, { Name: name, Version: options.version || '' }] - - return [key, value] - }) - ) - // If not, add new entry - data[ldaDiamondContractName].Facets[address] = { - Name: name, - Version: options.version || '', - } - } - - fs.writeFileSync(ldaDiamondFile, JSON.stringify(data, null, 2)) - - consola.success(`Updated LDA diamond log for ${name} at ${address} in ${ldaDiamondFile}`) -} - -const updateAllLDANetworks = function (environment: string) { - try { - // Read networks configuration - const networksConfigPath = './config/networks.json' - if (!fs.existsSync(networksConfigPath)) { - consola.error('Networks config file not found') - return - } - - const networksConfig = JSON.parse(fs.readFileSync(networksConfigPath, 'utf8')) - const networks = Object.keys(networksConfig) - - consola.info(`Updating LDA diamond logs for ${networks.length} networks in ${environment} environment`) - - for (const network of networks) { - try { - // Read network-specific deployment file - const deploymentFileSuffix = environment === 'production' ? 'json' : 'staging.json' - const deploymentFile = `deployments/${network}.lda.${deploymentFileSuffix}` - - if (!fs.existsSync(deploymentFile)) { - consola.warn(`LDA deployment file not found for ${network}: ${deploymentFile}`) - continue - } - - const deployments = JSON.parse(fs.readFileSync(deploymentFile, 'utf8')) - - // Update LDA diamond log for each deployed contract - for (const [contractName, contractAddress] of Object.entries(deployments)) { - if (typeof contractAddress === 'string') { - // Get version from contract if possible (simplified version) - const version = '1.0.0' // Default version - could be enhanced to read actual version - - updateLDADiamond( - contractName, - network, - contractAddress, - environment === 'production', - { - isPeriphery: false, // Could be enhanced to detect periphery contracts - version, - } - ) - } - } - - consola.success(`Updated LDA diamond log for network: ${network}`) - } catch (error) { - consola.error(`Failed to update LDA diamond log for ${network}:`, error) - } - } - } catch (error) { - consola.error('Failed to update LDA diamond logs for all networks:', error) - } -} - -export default main - -export { updateLDADiamond, updateAllLDANetworks } - -// Handle direct execution -if (import.meta.main) { - runMain(main).then((args) => { - try { - const environment = args.environment - const network = args.network - const contractName = args.contractName - const address = args.address - const version = args.version - const isPeriphery = args.isPeriphery - - // Validate environment - if (!['staging', 'production'].includes(environment)) { - consola.error('Environment must be either "staging" or "production"') - process.exit(1) - } - - if (network && contractName && address) { - // Update specific contract - updateLDADiamond( - contractName, - network, - address, - environment === 'production', - { - isPeriphery, - version, - } - ) - } else if (network) { - // Update specific network - consola.info(`Updating LDA diamond log for network: ${network}`) - // This would need implementation for single network update - consola.warn('Single network update not yet implemented, updating all networks') - updateAllLDANetworks(environment) - } else { - // Update all networks - updateAllLDANetworks(environment) - } - - consola.success('LDA diamond log update completed') - } catch (error) { - consola.error('Failed to update LDA diamond log:', error) - process.exit(1) - } - }) -} diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh index a31b582c3..9e38f6689 100644 --- a/script/tasks/ldaDiamondUpdateFacet.sh +++ b/script/tasks/ldaDiamondUpdateFacet.sh @@ -130,38 +130,36 @@ function ldaDiamondUpdateFacet() { return 0 fi - # get returned JSON - JSON_RETURN_DATA=$(getJsonFromRawForgeScriptReturnValue "$RAW_RETURN_DATA") - - # check whether call was successful or not - if [[ $RETURN_CODE -eq 0 ]] && [[ ! -z "$JSON_RETURN_DATA" ]]; then - - # check whether the call was successful - local CONTRACT_ADDRESS=$(echo "$JSON_RETURN_DATA" | jq -r '.returns.deployed.value') - - echo "[info] LDA diamond update was successful" - if [[ $SHOW_LOGS == "true" ]]; then - echo "[info] new contract address: $CONTRACT_ADDRESS" + # check the return code the last call + if [ "$RETURN_CODE" -eq 0 ]; then + # extract the "returns" property directly from the JSON output + RETURN_DATA=$(echo "$RAW_RETURN_DATA" | jq -r '.returns // empty' 2>/dev/null) + + # get the facet addresses that are known to the diamond from the return data + FACETS=$(echo "$RETURN_DATA" | jq -r '.facets.value // "{}"') + if [[ $FACETS != "{}" ]]; then + echo "[info] LDA diamond update was successful" + if [[ $SHOW_LOGS == "true" ]]; then + echo "[info] Updated diamond now has $(echo "$FACETS" | jq -r '. | length') facets" + fi + return 0 # exit the loop if the operation was successful fi + fi - return 0 - - else - echo "[error] Call failed with error code $RETURN_CODE" - echo "[error] Error message: $RAW_RETURN_DATA" + echo "[error] Call failed with error code $RETURN_CODE" + echo "[error] Error message: $RAW_RETURN_DATA" - attempts=$((attempts + 1)) + attempts=$((attempts + 1)) - # exit the loop if this was the last attempt - if [ $attempts -gt "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; then - error "max attempts reached, execution of LDA diamond update for $UPDATE_SCRIPT failed" - return 1 - fi - - # wait a bit before retrying - echo "retrying in $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR seconds..." - sleep $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR + # exit the loop if this was the last attempt + if [ $attempts -gt "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; then + error "max attempts reached, execution of LDA diamond update for $UPDATE_SCRIPT failed" + return 1 fi + + # wait a bit before retrying + echo "retrying in $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR seconds..." + sleep $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR done return 1 From dbfa258533659039532bee8317365d40a2a82b9b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 1 Sep 2025 19:18:41 +0200 Subject: [PATCH 139/220] Add NativeWrapperFacet and IzumiV3Facet to deployment configurations for Arbitrum and Optimism networks. Update global.json to include NativeWrapperFacet and enhance deployment scripts for improved functionality and consistency. --- config/global.json | 1 + deployments/_deployments_log_file.json | 199 ++++++++++++++++++ deployments/arbitrum.lda.staging.json | 4 +- deployments/optimism.lda.staging.json | 13 ++ script/deploy/deployAllLDAContracts.sh | 87 ++------ .../facets/LDA/DeployCoreRouteFacet.s.sol | 28 ++- .../facets/LDA/UpdateAlgebraFacet.s.sol | 4 +- .../deploy/facets/LDA/UpdateCurveFacet.s.sol | 4 +- .../facets/LDA/UpdateIzumiV3Facet.s.sol | 4 +- .../LDA/UpdateLDAEmergencyPauseFacet.s.sol | 4 +- .../facets/LDA/UpdateLDAOwnershipFacet.s.sol | 4 +- .../facets/LDA/UpdateNativeWrapperFacet.s.sol | 4 +- .../facets/LDA/UpdateSyncSwapV2Facet.s.sol | 4 +- .../facets/LDA/UpdateUniV2StyleFacet.s.sol | 4 +- .../facets/LDA/UpdateUniV3StyleFacet.s.sol | 4 +- script/deploy/ldaHealthCheck.ts | 94 +++++++-- script/scriptMaster.sh | 117 +--------- script/tasks/ldaDiamondUpdateFacet.sh | 6 +- 18 files changed, 359 insertions(+), 226 deletions(-) create mode 100644 deployments/optimism.lda.staging.json diff --git a/config/global.json b/config/global.json index 5a7d5d1ad..5bc3d9936 100644 --- a/config/global.json +++ b/config/global.json @@ -74,6 +74,7 @@ "LDAEmergencyPauseFacet", "LDAPeripheryRegistryFacet", "CoreRouteFacet", + "NativeWrapperFacet", "UniV3StyleFacet", "UniV2StyleFacet" ] diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 5f145a65b..406880c47 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39734,6 +39734,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:41:58", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LDADiamondLoupeFacet": { @@ -39751,6 +39766,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:42:13", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LDAOwnershipFacet": { @@ -39768,6 +39798,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:42:31", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LDAEmergencyPauseFacet": { @@ -39785,6 +39830,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.2": [ + { + "ADDRESS": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:42:53", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LDAPeripheryRegistryFacet": { @@ -39802,6 +39862,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:43:10", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "CoreRouteFacet": { @@ -39819,6 +39894,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:59:55", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV3StyleFacet": { @@ -39836,6 +39926,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x414fc3eB441664807027346817dcBe8490938C78", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:00:28", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV2StyleFacet": { @@ -39853,6 +39958,21 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:00:44", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LDADiamond": { @@ -39870,6 +39990,85 @@ } ] } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:44:18", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000fa0a1fd9e492f0f75168a7ef72418420379057b7", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "NativeWrapperFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 16:24:13", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:00:12", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "IzumiV3Facet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2688a406d7F7A99C7448d959371063275CfC2E81", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 18:41:17", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2688a406d7F7A99C7448d959371063275CfC2E81", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:02:14", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } } } diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json index abad6efd8..1af232b74 100644 --- a/deployments/arbitrum.lda.staging.json +++ b/deployments/arbitrum.lda.staging.json @@ -7,5 +7,7 @@ "CoreRouteFacet": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", "UniV3StyleFacet": "0x414fc3eB441664807027346817dcBe8490938C78", "UniV2StyleFacet": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", - "LDADiamond": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d" + "LDADiamond": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", + "NativeWrapperFacet": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", + "IzumiV3Facet": "0x2688a406d7F7A99C7448d959371063275CfC2E81" } \ No newline at end of file diff --git a/deployments/optimism.lda.staging.json b/deployments/optimism.lda.staging.json new file mode 100644 index 000000000..84d3dd79f --- /dev/null +++ b/deployments/optimism.lda.staging.json @@ -0,0 +1,13 @@ +{ + "LDADiamondCutFacet": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", + "LDADiamondLoupeFacet": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", + "LDAOwnershipFacet": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", + "LDAEmergencyPauseFacet": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", + "LDAPeripheryRegistryFacet": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", + "LDADiamond": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", + "CoreRouteFacet": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", + "NativeWrapperFacet": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", + "UniV3StyleFacet": "0x414fc3eB441664807027346817dcBe8490938C78", + "UniV2StyleFacet": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", + "IzumiV3Facet": "0x2688a406d7F7A99C7448d959371063275CfC2E81" +} \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 858e9aac5..f876050b3 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -36,9 +36,8 @@ deployAllLDAContracts() { "1) Initial setup and CREATE3Factory deployment" \ "2) Deploy LDA core facets" \ "3) Deploy LDA diamond and update with core facets" \ - "4) Deploy LDA DEX facets and add to diamond" \ - "5) Run LDA health check only" \ - "6) Ownership transfer to timelock (production only)" + "4) Run LDA health check only" \ + "5) Ownership transfer to timelock (production only)" ) # Extract the stage number from the selection @@ -52,8 +51,6 @@ deployAllLDAContracts() { START_STAGE=4 elif [[ "$START_FROM" == *"5)"* ]]; then START_STAGE=5 - elif [[ "$START_FROM" == *"6)"* ]]; then - START_STAGE=6 else error "invalid selection: $START_FROM - exiting script now" exit 1 @@ -165,91 +162,37 @@ deployAllLDAContracts() { echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 3 completed" fi - # Stage 4: Deploy LDA DEX facets and add to diamond + # Stage 4: Run LDA health check if [[ $START_STAGE -le 4 ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Deploy LDA DEX facets and add to diamond" - - # deploy all LDA DEX facets and add to diamond - echo "" - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now deploying LDA DEX facets and adding to diamond contract" - - # get all LDA facet contract names (excluding core facets) - local LDA_FACETS_PATH="script/deploy/facets/LDA/" - - # Read LDA core facets from config for exclusion - local GLOBAL_CONFIG_PATH="./config/global.json" - if [[ ! -f "$GLOBAL_CONFIG_PATH" ]]; then - error "Global config file not found: $GLOBAL_CONFIG_PATH" - return 1 - fi - - # Get LDA core facets from JSON config - local LDA_CORE_FACETS_JSON=$(jq -r '.ldaCoreFacets[]' "$GLOBAL_CONFIG_PATH") - local LDA_CORE_FACETS=() - while IFS= read -r facet; do - LDA_CORE_FACETS+=("$facet") - done <<< "$LDA_CORE_FACETS_JSON" - - # Add LDADiamond to exclusions and build regex - LDA_CORE_FACETS+=("LDADiamond") - local EXCLUDED_LDA_FACETS_REGEXP="^($(printf '%s|' "${LDA_CORE_FACETS[@]}"))" - EXCLUDED_LDA_FACETS_REGEXP="${EXCLUDED_LDA_FACETS_REGEXP%|})$" - - # loop through LDA facet contract names - for DEPLOY_SCRIPT in $(ls -1 "$LDA_FACETS_PATH" | grep '^Deploy.*\.s\.sol$'); do - FACET_NAME=$(echo "$DEPLOY_SCRIPT" | sed -e 's/Deploy//' -e 's/\.s\.sol$//') - - if ! [[ "$FACET_NAME" =~ $EXCLUDED_LDA_FACETS_REGEXP ]]; then - # check if facet is existing in target state JSON for LDA - TARGET_VERSION=$(findContractVersionInTargetState "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$LDA_DIAMOND_CONTRACT_NAME") - - # check result - if [[ $? -ne 0 ]]; then - echo "[info] No matching entry found in target state file for NETWORK=$NETWORK, ENVIRONMENT=$ENVIRONMENT, CONTRACT=$FACET_NAME >> no deployment needed" - else - # deploy LDA facet and add to LDA diamond - deployFacetAndAddToLDADiamond "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$LDA_DIAMOND_CONTRACT_NAME" "$TARGET_VERSION" - fi - fi - done - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA DEX facets part completed" - + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Run LDA health check only" + bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 4 completed" - fi - - # Stage 5: Run LDA health check only - if [[ $START_STAGE -le 5 ]]; then - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Run LDA health check only" - bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" # Pause and ask user if they want to continue with ownership transfer (for production) - if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 5 ]]; then + if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 4 ]]; then echo "" echo "Health check completed. Do you want to continue with ownership transfer to timelock?" echo "This should only be done if the health check shows only diamond ownership errors." - echo "Continue with stage 6 (ownership transfer)? (y/n)" + echo "Continue with stage 5 (ownership transfer)? (y/n)" read -r response if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - echo "Proceeding with stage 6..." + echo "Proceeding with stage 5..." else - echo "Skipping stage 6 - ownership transfer cancelled by user" + echo "Skipping stage 5 - ownership transfer cancelled by user" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" return fi fi fi - # Stage 6: Ownership transfer to timelock (production only) - if [[ $START_STAGE -le 6 ]]; then + # Stage 5: Ownership transfer to timelock (production only) + if [[ $START_STAGE -le 5 ]]; then if [[ "$ENVIRONMENT" == "production" ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Ownership transfer to timelock (production only)" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Ownership transfer to timelock (production only)" - # make sure SAFE_ADDRESS is available (if starting in stage 6 it's not available yet) + # make sure SAFE_ADDRESS is available (if starting in stage 5 it's not available yet) SAFE_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.safeAddress") if [[ -z "$SAFE_ADDRESS" || "$SAFE_ADDRESS" == "null" ]]; then echo "SAFE address not found in networks.json. Cannot prepare ownership transfer to Timelock" @@ -287,10 +230,10 @@ deployAllLDAContracts() { echo "" # ------------------------------------------------------------ else - echo "Stage 6 skipped - ownership transfer to timelock is only for production environment" + echo "Stage 5 skipped - ownership transfer to timelock is only for production environment" fi - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 6 completed" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" fi echo "" diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index 8de8482da..5fda51651 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -2,12 +2,38 @@ pragma solidity ^0.8.17; import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; contract DeployScript is DeployLDAScriptBase { + using stdJson for string; + constructor() DeployLDAScriptBase("CoreRouteFacet") {} - function run() public returns (CoreRouteFacet deployed) { + function run() + public + returns (CoreRouteFacet deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); } + + function getConstructorArgs() internal override returns (bytes memory) { + // get path of global config file + string memory globalConfigPath = string.concat( + root, + "/config/global.json" + ); + + // read file into json variable + string memory globalConfigJson = vm.readFile(globalConfigPath); + + // extract refundWallet address as owner + address refundWalletAddress = globalConfigJson.readAddress( + ".refundWallet" + ); + + return abi.encode(refundWalletAddress); + } } diff --git a/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol b/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol index 404842927..f87e8393a 100644 --- a/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateAlgebraFacet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateCurveFacet.s.sol b/script/deploy/facets/LDA/UpdateCurveFacet.s.sol index 3e272e614..95c45540a 100644 --- a/script/deploy/facets/LDA/UpdateCurveFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateCurveFacet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol b/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol index 6adc89493..e8171b128 100644 --- a/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol +++ b/script/deploy/facets/LDA/UpdateIzumiV3Facet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol index 61fc6cd4b..2c52caa13 100644 --- a/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol index 6446fb028..2c033f696 100644 --- a/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol b/script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol index a25a05e60..a20fde375 100644 --- a/script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateNativeWrapperFacet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol index 267816659..8660a5dec 100644 --- a/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol +++ b/script/deploy/facets/LDA/UpdateSyncSwapV2Facet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol index b456dd43e..8d27a2436 100644 --- a/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateUniV2StyleFacet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol index 75e04f861..23d245103 100644 --- a/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol +++ b/script/deploy/facets/LDA/UpdateUniV3StyleFacet.s.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -contract DeployScript is UpdateScriptBase { +contract DeployScript is UpdateLDAScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index 2b81ed17c..499468c67 100644 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -48,9 +48,14 @@ const main = defineCommand({ description: 'EVM network to check', required: true, }, + environment: { + type: 'string', + description: 'Environment to check (staging or production)', + default: 'production', + }, }, async run({ args }) { - const { network } = args + const { network, environment } = args // Skip tronshasta testnet but allow tron mainnet if (network.toLowerCase() === 'tronshasta') { @@ -68,8 +73,16 @@ const main = defineCommand({ process.exit(0) } - // Load LDA-specific deployments (production only) - const ldaDeploymentFile = `../../deployments/${network.toLowerCase()}.lda.production.json` + // Validate environment + if (environment !== 'staging' && environment !== 'production') { + consola.error( + `Invalid environment: ${environment}. Must be 'staging' or 'production'.` + ) + process.exit(1) + } + + // Load LDA-specific deployments + const ldaDeploymentFile = `../../deployments/${network.toLowerCase()}.lda.${environment}.json` let ldaDeployedContracts: Record try { @@ -78,22 +91,45 @@ const main = defineCommand({ } catch (error) { consola.error(`Failed to load LDA deployment file: ${ldaDeploymentFile}`) consola.error( - 'Please ensure LDA contracts are deployed to production first.' + `Please ensure LDA contracts are deployed to ${environment} first.` ) process.exit(1) } // Load main deployment file for shared infrastructure (like LiFiTimelockController) - const mainDeploymentFile = `../../deployments/${network.toLowerCase()}.json` + // For staging, try environment-specific file first, then fallback to main let mainDeployedContracts: Record = {} + const mainDeploymentFile = `../../deployments/${network.toLowerCase()}.json` - try { - const { default: contracts } = await import(mainDeploymentFile) - mainDeployedContracts = contracts - } catch (error) { - consola.warn(`Failed to load main deployment file: ${mainDeploymentFile}`) - consola.warn('Some shared infrastructure checks will be skipped.') - } + if (environment === 'staging') { + const stagingFile = `../../deployments/${network.toLowerCase()}.staging.json` + try { + const { default: contracts } = await import(stagingFile) + mainDeployedContracts = contracts + } catch (error) { + // Fallback to main deployment file for staging + try { + const { default: contracts } = await import(mainDeploymentFile) + mainDeployedContracts = contracts + } catch (fallbackError) { + consola.warn( + `Failed to load deployment files: ${stagingFile} and ${mainDeploymentFile}` + ) + consola.warn('Some shared infrastructure checks will be skipped.') + } + } + } else + // Production - use main deployment file + try { + const { default: contracts } = await import(mainDeploymentFile) + mainDeployedContracts = contracts + } catch (error) { + consola.warn( + `Failed to load main deployment file: ${mainDeploymentFile}` + ) + consola.warn('Some shared infrastructure checks will be skipped.') + } + // Note: We keep LDA and main contracts separate for clarity @@ -112,7 +148,9 @@ const main = defineCommand({ process.exit(1) } - consola.info('Running LDA Diamond post deployment checks...\n') + consola.info( + `Running LDA Diamond post deployment checks for ${environment}...\n` + ) // ╭─────────────────────────────────────────────────────────╮ // │ Check LDA Diamond Contract │ @@ -208,14 +246,32 @@ const main = defineCommand({ const timelockAddress = mainDeployedContracts['LiFiTimelockController'] if (timelockAddress) { consola.info(`Found LiFiTimelockController at: ${timelockAddress}`) - if (owner.toLowerCase() === timelockAddress.toLowerCase()) + if (owner.toLowerCase() === timelockAddress.toLowerCase()) consola.success('LDADiamond is owned by LiFiTimelockController') - else - logError(`LDADiamond owner is ${owner}, expected ${timelockAddress}`) + else + if (environment === 'production') + consola.error( + `LDADiamond owner is ${owner}, expected ${timelockAddress}` + ) + else { + consola.warn( + `LDADiamond owner is ${owner}, expected ${timelockAddress} for production` + ) + consola.info( + 'For staging environment, ownership transfer to timelock is typically done later' + ) + } + } else { - consola.error( - 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' - ) + if (environment === 'production') + consola.error( + 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' + ) + else + consola.warn( + 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' + ) + consola.info( 'Note: LiFiTimelockController should be deployed as shared infrastructure before LDA deployment' ) diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index 524316308..73bb9ddab 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -124,10 +124,7 @@ scriptMaster() { "11) Update diamond log(s)" \ "12) Propose upgrade TX to Gnosis SAFE" \ "13) Remove facets or periphery from diamond" \ - "14) Deploy LDA Diamond with core facets to one network" \ - "15) Deploy all LDA contracts to one selected network (=new network)" \ - "16) Deploy one LDA facet to one network" \ - "17) Update LDA diamond log(s)" \ + "14) Deploy all LDA diamond with core contracts to one selected network" \ ) #--------------------------------------------------------------------------------------------------------------------- @@ -597,35 +594,10 @@ scriptMaster() { bunx tsx script/tasks/cleanUpProdDiamond.ts #--------------------------------------------------------------------------------------------------------------------- - # use case 14: Deploy LDA Diamond with core facets to one network + # use case 15: Deploy all LDA diamond with core contracts to one selected network elif [[ "$SELECTION" == "14)"* ]]; then echo "" - echo "[info] selected use case: Deploy LDA Diamond with core facets to one network" - - checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" - # get user-selected network from list - local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") - echo "[info] selected network: $NETWORK" - - # get deployer wallet balance - BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") - echo "[info] deployer wallet balance in this network: $BALANCE" - echo "" - checkRequiredVariablesInDotEnv "$NETWORK" - - # call LDA deploy script - deployLDADiamondWithCoreFacets "$NETWORK" "$ENVIRONMENT" - - # check if last command was executed successfully, otherwise exit script with error message - checkFailure $? "deploy LDA Diamond with core facets to network $NETWORK" - - playNotificationSound - - #--------------------------------------------------------------------------------------------------------------------- - # use case 15: Deploy all LDA contracts to one selected network (=new network) - elif [[ "$SELECTION" == "15)"* ]]; then - echo "" - echo "[info] selected use case: Deploy all LDA contracts to one selected network (=new network)" + echo "[info] selected use case: Deploy all LDA diamond with core contracts to one selected network" checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" # get user-selected network from list @@ -641,89 +613,6 @@ scriptMaster() { playNotificationSound #--------------------------------------------------------------------------------------------------------------------- - # use case 16: Deploy one LDA facet to one network - elif [[ "$SELECTION" == "16)"* ]]; then - echo "" - echo "[info] selected use case: Deploy one LDA facet to one network" - - checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" - # get user-selected network from list - local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") - - echo "[info] selected network: $NETWORK" - echo "[info] loading deployer wallet balance..." - - # get deployer wallet balance - BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") - - echo "[info] deployer wallet balance in this network: $BALANCE" - echo "" - checkRequiredVariablesInDotEnv "$NETWORK" - - # get user-selected LDA deploy script and contract from list - SCRIPT=$(ls -1 "script/deploy/facets/LDA/" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "LDA Deploy Script") - CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') - - # check if new contract should be added to LDA diamond after deployment - if [[ ! "$CONTRACT" == "LDADiamond"* ]]; then - echo "" - echo "Do you want to add this LDA contract to LDADiamond after deployment?" - ADD_TO_DIAMOND=$( - gum choose \ - "yes - to LDADiamond" \ - " no - do not update any diamond" - ) - fi - - # get current contract version - local VERSION=$(getCurrentContractVersion "$CONTRACT") - - # check if contract should be added after deployment - if [[ "$ADD_TO_DIAMOND" == "yes"* ]]; then - echo "[info] selected option: $ADD_TO_DIAMOND" - deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LDADiamond" "$VERSION" - else - # just deploy the LDA contract - deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "true" - fi - - # check if last command was executed successfully, otherwise exit script with error message - checkFailure $? "deploy LDA contract $CONTRACT to network $NETWORK" - - #--------------------------------------------------------------------------------------------------------------------- - # use case 17: Update LDA diamond log(s) - elif [[ "$SELECTION" == "17)"* ]]; then - # ask user if logs should be updated only for one network or for all networks - echo "Would you like to update LDA logs for all networks or one specific network?" - SELECTION_NETWORK=$( - gum choose \ - "1) All networks" \ - "2) One specific network (selection in next screen)" - ) - echo "[info] selected option: $SELECTION_NETWORK" - - if [[ "$SELECTION_NETWORK" == "1)"* ]]; then - # call update LDA diamond log function for all networks - updateLDADiamondLogs "$ENVIRONMENT" - else - checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" - # get user-selected network from list - local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") - - echo "[info] selected network: $NETWORK" - echo "[info] loading deployer wallet balance..." - - # get deployer wallet balance - BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") - - echo "[info] deployer wallet balance in this network: $BALANCE" - echo "" - checkRequiredVariablesInDotEnv "$NETWORK" - - # call update LDA diamond log function for specific network - updateLDADiamondLogs "$ENVIRONMENT" "$NETWORK" - fi - else error "invalid use case selected ('$SELECTION') - exiting script" cleanup diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh index 9e38f6689..b8960f33f 100644 --- a/script/tasks/ldaDiamondUpdateFacet.sh +++ b/script/tasks/ldaDiamondUpdateFacet.sh @@ -84,6 +84,9 @@ function ldaDiamondUpdateFacet() { return 1 fi + # set flag for LDA diamond (always false since LDADiamond is not the default diamond) + USE_LDA_DIAMOND=false + if [[ -z "$GAS_ESTIMATE_MULTIPLIER" ]]; then GAS_ESTIMATE_MULTIPLIER=130 # this is foundry's default value fi @@ -99,6 +102,7 @@ function ldaDiamondUpdateFacet() { echoDebug "LDA_UPDATE_SCRIPT_PATH=$LDA_UPDATE_SCRIPT_PATH" echoDebug "DIAMOND_ADDRESS=$DIAMOND_ADDRESS" echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echoDebug "USE_LDA_DIAMOND=$USE_LDA_DIAMOND" echoDebug "GAS_ESTIMATE_MULTIPLIER=$GAS_ESTIMATE_MULTIPLIER (default value: 130, set in .env for example to 200 for doubling Foundry's estimate)" echo "" fi @@ -113,7 +117,7 @@ function ldaDiamondUpdateFacet() { doNotContinueUnlessGasIsBelowThreshold "$NETWORK" # try to execute call - RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") + RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") local RETURN_CODE=$? From 07495564d8907e8445ff78f47ac2769c16a3f008 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 12:08:10 +0200 Subject: [PATCH 140/220] Add zksync deployment configurations and scripts for LDA facets, including AlgebraFacet, CoreRouteFacet, and others. Update deployment logs and introduce utility scripts for managing contract selectors and deployments. --- deployments/_deployments_log_file.json | 105 +++++++++ deployments/zksync.lda.staging.json | 9 + .../LDA/DeployAlgebraFacet.zksync.s.sol | 13 ++ .../LDA/DeployCoreRouteFacet.zksync.s.sol | 39 ++++ .../zksync/LDA/DeployCurveFacet.zksync.s.sol | 13 ++ .../LDA/DeployIzumiV3Facet.zksync.s.sol | 13 ++ .../zksync/LDA/DeployLDADiamond.zksync.s.sol | 56 +++++ .../LDA/DeployLDADiamondCutFacet.zksync.s.sol | 15 ++ .../DeployLDADiamondLoupeFacet.zksync.s.sol | 15 ++ .../DeployLDAEmergencyPauseFacet.zksync.s.sol | 32 +++ .../LDA/DeployLDAOwnershipFacet.zksync.s.sol | 15 ++ ...ployLDAPeripheryRegistryFacet.zksync.s.sol | 15 ++ .../LDA/DeployNativeWrapperFacet.zksync.s.sol | 15 ++ .../LDA/DeploySyncSwapV2Facet.zksync.s.sol | 13 ++ .../LDA/DeployUniV2StyleFacet.zksync.s.sol | 13 ++ .../LDA/DeployUniV3StyleFacet.zksync.s.sol | 13 ++ .../LDA/DeployVelodromeV2Facet.zksync.s.sol | 15 ++ .../LDA/UpdateAlgebraFacet.zksync.s.sol | 13 ++ .../zksync/LDA/UpdateCurveFacet.zksync.s.sol | 13 ++ .../LDA/UpdateIzumiV3Facet.zksync.s.sol | 13 ++ .../LDA/UpdateLDACoreFacets.zksync.s.sol | 151 +++++++++++++ .../UpdateLDAEmergencyPauseFacet.zksync.s.sol | 13 ++ .../LDA/UpdateLDAOwnershipFacet.zksync.s.sol | 13 ++ .../LDA/UpdateNativeWrapperFacet.zksync.s.sol | 13 ++ .../LDA/UpdateSyncSwapV2Facet.zksync.s.sol | 13 ++ .../LDA/UpdateUniV2StyleFacet.zksync.s.sol | 13 ++ .../LDA/UpdateUniV3StyleFacet.zksync.s.sol | 13 ++ .../zksync/LDA/utils/DeployLDAScriptBase.sol | 80 +++++++ .../deploy/zksync/LDA/utils/LDAScriptBase.sol | 45 ++++ .../zksync/LDA/utils/UpdateLDAScriptBase.sol | 211 ++++++++++++++++++ .../zksync/LDA/utils/contract-selectors.sh | 11 + 31 files changed, 1024 insertions(+) create mode 100644 deployments/zksync.lda.staging.json create mode 100644 script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateAlgebraFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateCurveFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateIzumiV3Facet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateNativeWrapperFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateSyncSwapV2Facet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateUniV2StyleFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateUniV3StyleFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol create mode 100644 script/deploy/zksync/LDA/utils/LDAScriptBase.sol create mode 100644 script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol create mode 100755 script/deploy/zksync/LDA/utils/contract-selectors.sh diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 406880c47..167b6871f 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39749,6 +39749,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x8Bbe822959F60D0d6a882486928C7d851bf52adc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:48:42", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "LDADiamondLoupeFacet": { @@ -39781,6 +39796,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x63144D19F86EeeF3722af56a6804098DD1b58640", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:50:53", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "LDAOwnershipFacet": { @@ -39813,6 +39843,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x53303BCE53bafFCD764501D0B4B3dcC798998b47", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:53:56", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "LDAEmergencyPauseFacet": { @@ -39845,6 +39890,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.2": [ + { + "ADDRESS": "0x0000000000000000000000000000000000000000", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:56:04", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "LDAPeripheryRegistryFacet": { @@ -39877,6 +39937,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xBbA05be3150Eafa7F0Ac3F57FEEFF0e57Da14F12", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 19:58:15", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "CoreRouteFacet": { @@ -39909,6 +39984,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x0000000000000000000000000000000000000000", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 20:00:24", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "UniV3StyleFacet": { @@ -40037,6 +40127,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x0000000000000000000000000000000000000000", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-01 20:02:32", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "IzumiV3Facet": { diff --git a/deployments/zksync.lda.staging.json b/deployments/zksync.lda.staging.json new file mode 100644 index 000000000..227bc53bf --- /dev/null +++ b/deployments/zksync.lda.staging.json @@ -0,0 +1,9 @@ +{ + "LDADiamondCutFacet": "0x8Bbe822959F60D0d6a882486928C7d851bf52adc", + "LDADiamondLoupeFacet": "0x63144D19F86EeeF3722af56a6804098DD1b58640", + "LDAOwnershipFacet": "0x53303BCE53bafFCD764501D0B4B3dcC798998b47", + "LDAEmergencyPauseFacet": "0x0000000000000000000000000000000000000000", + "LDAPeripheryRegistryFacet": "0xBbA05be3150Eafa7F0Ac3F57FEEFF0e57Da14F12", + "CoreRouteFacet": "0x0000000000000000000000000000000000000000", + "NativeWrapperFacet": "0x0000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol new file mode 100644 index 000000000..2ee324755 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("AlgebraFacet") {} + + function run() public returns (AlgebraFacet deployed) { + deployed = AlgebraFacet(deploy(type(AlgebraFacet).creationCode)); + } +} diff --git a/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol new file mode 100644 index 000000000..5fda51651 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + using stdJson for string; + + constructor() DeployLDAScriptBase("CoreRouteFacet") {} + + function run() + public + returns (CoreRouteFacet deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + + deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); + } + + function getConstructorArgs() internal override returns (bytes memory) { + // get path of global config file + string memory globalConfigPath = string.concat( + root, + "/config/global.json" + ); + + // read file into json variable + string memory globalConfigJson = vm.readFile(globalConfigPath); + + // extract refundWallet address as owner + address refundWalletAddress = globalConfigJson.readAddress( + ".refundWallet" + ); + + return abi.encode(refundWalletAddress); + } +} diff --git a/script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol new file mode 100644 index 000000000..9f723542a --- /dev/null +++ b/script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("CurveFacet") {} + + function run() public returns (CurveFacet deployed) { + deployed = CurveFacet(deploy(type(CurveFacet).creationCode)); + } +} diff --git a/script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol new file mode 100644 index 000000000..201f91926 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("IzumiV3Facet") {} + + function run() public returns (IzumiV3Facet deployed) { + deployed = IzumiV3Facet(deploy(type(IzumiV3Facet).creationCode)); + } +} diff --git a/script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol new file mode 100644 index 000000000..f60eeb285 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; + +contract DeployScript is DeployLDAScriptBase { + using stdJson for string; + + constructor() DeployLDAScriptBase("LDADiamond") {} + + function run() + public + returns (LDADiamond deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + deployed = LDADiamond(deploy(type(LDADiamond).creationCode)); + } + + function getConstructorArgs() internal override returns (bytes memory) { + // Check if fileSuffix already contains "lda." to avoid double prefix + string memory ldaPrefix = ""; + bytes memory fileSuffixBytes = bytes(fileSuffix); + bool hasLdaPrefix = false; + + // Check if fileSuffix starts with "lda." + if (fileSuffixBytes.length >= 4) { + hasLdaPrefix = (fileSuffixBytes[0] == "l" && + fileSuffixBytes[1] == "d" && + fileSuffixBytes[2] == "a" && + fileSuffixBytes[3] == "."); + } + + if (!hasLdaPrefix) { + ldaPrefix = ".lda."; + } else { + ldaPrefix = "."; + } + + string memory path = string.concat( + root, + "/deployments/", + network, + ldaPrefix, + fileSuffix, + "json" + ); + address diamondCut = _getConfigContractAddress( + path, + ".LDADiamondCutFacet" + ); + + return abi.encode(deployerAddress, diamondCut); + } +} diff --git a/script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol new file mode 100644 index 000000000..1294ed540 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDADiamondCutFacet") {} + + function run() public returns (LDADiamondCutFacet deployed) { + deployed = LDADiamondCutFacet( + deploy(type(LDADiamondCutFacet).creationCode) + ); + } +} diff --git a/script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol new file mode 100644 index 000000000..7963855d9 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDADiamondLoupeFacet") {} + + function run() public returns (LDADiamondLoupeFacet deployed) { + deployed = LDADiamondLoupeFacet( + deploy(type(LDADiamondLoupeFacet).creationCode) + ); + } +} diff --git a/script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol new file mode 100644 index 000000000..ced775bde --- /dev/null +++ b/script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + using stdJson for string; + + constructor() DeployLDAScriptBase("LDAEmergencyPauseFacet") {} + + function run() + public + returns (LDAEmergencyPauseFacet deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + + deployed = LDAEmergencyPauseFacet( + deploy(type(LDAEmergencyPauseFacet).creationCode) + ); + } + + function getConstructorArgs() internal override returns (bytes memory) { + string memory path = string.concat(root, "/config/global.json"); + string memory json = vm.readFile(path); + + address pauserWallet = json.readAddress(".pauserWallet"); + + return abi.encode(pauserWallet); + } +} diff --git a/script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol new file mode 100644 index 000000000..2e86201a2 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDAOwnershipFacet") {} + + function run() public returns (LDAOwnershipFacet deployed) { + deployed = LDAOwnershipFacet( + deploy(type(LDAOwnershipFacet).creationCode) + ); + } +} diff --git a/script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol new file mode 100644 index 000000000..7ae636c20 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("LDAPeripheryRegistryFacet") {} + + function run() public returns (LDAPeripheryRegistryFacet deployed) { + deployed = LDAPeripheryRegistryFacet( + deploy(type(LDAPeripheryRegistryFacet).creationCode) + ); + } +} diff --git a/script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol new file mode 100644 index 000000000..f6b87788c --- /dev/null +++ b/script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("NativeWrapperFacet") {} + + function run() public returns (NativeWrapperFacet deployed) { + deployed = NativeWrapperFacet( + deploy(type(NativeWrapperFacet).creationCode) + ); + } +} diff --git a/script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol new file mode 100644 index 000000000..f5ba26cbd --- /dev/null +++ b/script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("SyncSwapV2Facet") {} + + function run() public returns (SyncSwapV2Facet deployed) { + deployed = SyncSwapV2Facet(deploy(type(SyncSwapV2Facet).creationCode)); + } +} diff --git a/script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol new file mode 100644 index 000000000..88982d4ea --- /dev/null +++ b/script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("UniV2StyleFacet") {} + + function run() public returns (UniV2StyleFacet deployed) { + deployed = UniV2StyleFacet(deploy(type(UniV2StyleFacet).creationCode)); + } +} diff --git a/script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol new file mode 100644 index 000000000..fe5e99377 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("UniV3StyleFacet") {} + + function run() public returns (UniV3StyleFacet deployed) { + deployed = UniV3StyleFacet(deploy(type(UniV3StyleFacet).creationCode)); + } +} diff --git a/script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol new file mode 100644 index 000000000..82491b8d1 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("VelodromeV2Facet") {} + + function run() public returns (VelodromeV2Facet deployed) { + deployed = VelodromeV2Facet( + deploy(type(VelodromeV2Facet).creationCode) + ); + } +} diff --git a/script/deploy/zksync/LDA/UpdateAlgebraFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateAlgebraFacet.zksync.s.sol new file mode 100644 index 000000000..f87e8393a --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateAlgebraFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("AlgebraFacet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateCurveFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateCurveFacet.zksync.s.sol new file mode 100644 index 000000000..95c45540a --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateCurveFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("CurveFacet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateIzumiV3Facet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateIzumiV3Facet.zksync.s.sol new file mode 100644 index 000000000..e8171b128 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateIzumiV3Facet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("IzumiV3Facet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol new file mode 100644 index 000000000..87b55f810 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { IERC173 } from "lifi/Interfaces/IERC173.sol"; +import { TransferrableOwnership } from "lifi/Helpers/TransferrableOwnership.sol"; + +contract UpdateLDACoreFacets is UpdateLDAScriptBase { + using stdJson for string; + + error FailedToReadLDACoreFacetsFromConfig(); + + /// @notice Returns function selectors to exclude for specific facets + /// @param facetName The name of the facet being processed + function getExcludes( + string memory facetName + ) internal pure returns (bytes4[] memory) { + // Exclude ownership function selectors from CoreRouteFacet to avoid collision with LDAOwnershipFacet + if ( + keccak256(bytes(facetName)) == keccak256(bytes("CoreRouteFacet")) + ) { + bytes4[] memory excludes = new bytes4[](5); + excludes[0] = IERC173.transferOwnership.selector; + excludes[1] = TransferrableOwnership + .cancelOwnershipTransfer + .selector; + excludes[2] = TransferrableOwnership + .confirmOwnershipTransfer + .selector; + excludes[3] = IERC173.owner.selector; + excludes[4] = bytes4(keccak256("pendingOwner()")); // public state variable not a function + return excludes; + } + + // No exclusions for other facets + bytes4[] memory emptyExcludes = new bytes4[](0); + return emptyExcludes; + } + + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + // Read LDA core facets dynamically from lda-global.json config + string memory ldaGlobalConfigPath = string.concat( + vm.projectRoot(), + "/config/global.json" + ); + string memory ldaGlobalConfig = vm.readFile(ldaGlobalConfigPath); + string[] memory ldaCoreFacets = ldaGlobalConfig.readStringArray( + ".ldaCoreFacets" + ); + + emit log("LDA core facets found in config/global.json: "); + emit log_uint(ldaCoreFacets.length); + + // Check if the LDA loupe was already added to the diamond + bool loupeExists; + try loupe.facetAddresses() returns (address[] memory) { + // If call was successful, loupe exists on LDA diamond already + emit log("LDA Loupe exists on diamond already"); + loupeExists = true; + } catch { + // No need to do anything, just making sure that the flow continues in both cases with try/catch + } + + // Handle LDADiamondLoupeFacet separately as it needs special treatment + if (!loupeExists) { + emit log("LDA Loupe does not exist on diamond yet"); + address ldaDiamondLoupeAddress = _getConfigContractAddress( + path, + ".LDADiamondLoupeFacet" + ); + bytes4[] memory loupeSelectors = getSelectors( + "LDADiamondLoupeFacet", + getExcludes("LDADiamondLoupeFacet") + ); + + buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + // Reset diamond cut variable to remove LDA diamondLoupe information + delete cut; + } + + // Process all LDA core facets dynamically + for (uint256 i = 0; i < ldaCoreFacets.length; i++) { + string memory facetName = ldaCoreFacets[i]; + + // Skip LDADiamondCutFacet and LDADiamondLoupeFacet as they were already handled + if ( + keccak256(bytes(facetName)) == + keccak256(bytes("LDADiamondLoupeFacet")) + ) { + continue; + } + // Skip LDADiamondCutFacet as it was already handled during LDA diamond deployment + if ( + keccak256(bytes(facetName)) == + keccak256(bytes("LDADiamondCutFacet")) + ) { + continue; + } + + emit log("Now adding LDA facet: "); + emit log(facetName); + // Use _getConfigContractAddress which validates the contract exists + address facetAddress = _getConfigContractAddress( + path, + string.concat(".", facetName) + ); + bytes4[] memory selectors = getSelectors( + facetName, + getExcludes(facetName) + ); + + // at this point we know for sure that LDA diamond loupe exists on diamond + buildDiamondCut(selectors, facetAddress); + } + + // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + LDADiamondCutFacet.diamondCut.selector, + cut, + address(0), + "" + ); + } + emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); + emit log_bytes(cutData); + emit log("=== END CALLDATA ==="); + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + facets = loupe.facetAddresses(); + } +} diff --git a/script/deploy/zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol new file mode 100644 index 000000000..2c52caa13 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("LDAEmergencyPauseFacet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol new file mode 100644 index 000000000..2c033f696 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("LDAOwnershipFacet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateNativeWrapperFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateNativeWrapperFacet.zksync.s.sol new file mode 100644 index 000000000..a20fde375 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateNativeWrapperFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("NativeWrapperFacet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateSyncSwapV2Facet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateSyncSwapV2Facet.zksync.s.sol new file mode 100644 index 000000000..8660a5dec --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateSyncSwapV2Facet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("SyncSwapV2Facet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateUniV2StyleFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateUniV2StyleFacet.zksync.s.sol new file mode 100644 index 000000000..8d27a2436 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateUniV2StyleFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("UniV2StyleFacet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateUniV3StyleFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateUniV3StyleFacet.zksync.s.sol new file mode 100644 index 000000000..23d245103 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateUniV3StyleFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("UniV3StyleFacet"); + } +} diff --git a/script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol new file mode 100644 index 000000000..2b8aec63b --- /dev/null +++ b/script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { LDAScriptBase } from "./LDAScriptBase.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { stdJson } from "forge-std/Script.sol"; + +interface IContractDeployer { + function getNewAddressCreate2( + address _sender, + bytes32 _bytecodeHash, + bytes32 _salt, + bytes calldata _input + ) external view returns (address newAddress); +} + +contract DeployLDAScriptBase is LDAScriptBase { + using stdJson for string; + + /// @dev The prefix used to create CREATE2 addresses. + bytes32 internal salt; + string internal contractName; + address internal constant DEPLOYER_CONTRACT_ADDRESS = + 0x0000000000000000000000000000000000008006; + + constructor(string memory _contractName) { + contractName = _contractName; + string memory saltPrefix = vm.envString("DEPLOYSALT"); + salt = keccak256(abi.encodePacked(saltPrefix, contractName)); + } + + function getConstructorArgs() internal virtual returns (bytes memory) {} + + function deploy( + bytes memory creationCode + ) internal virtual returns (address payable deployed) { + bytes memory constructorArgs = getConstructorArgs(); + + string memory path = string.concat( + root, + "/zkout/", + contractName, + ".sol/", + contractName, + ".json" + ); + string memory json = vm.readFile(path); + bytes32 bytecodeHash = json.readBytes32(".hash"); + bytes memory deploymentBytecode = bytes.concat( + creationCode, + constructorArgs + ); + vm.startBroadcast(deployerPrivateKey); + + address predicted = IContractDeployer(DEPLOYER_CONTRACT_ADDRESS) + .getNewAddressCreate2( + deployerAddress, + salt, + bytecodeHash, + constructorArgs + ); + + emit log_named_address("LI.FI: Predicted Address: ", predicted); + + if (LibAsset.isContract(predicted)) { + emit log("LI.FI: Contract is already deployed"); + + return payable(predicted); + } + + // Deploy a contract using the CREATE2 opcode for deterministic addr + assembly { + let len := mload(deploymentBytecode) + let data := add(deploymentBytecode, 0x20) + deployed := create2(0, data, len, sload(salt.slot)) + } + + vm.stopBroadcast(); + } +} diff --git a/script/deploy/zksync/LDA/utils/LDAScriptBase.sol b/script/deploy/zksync/LDA/utils/LDAScriptBase.sol new file mode 100644 index 000000000..9544baa5b --- /dev/null +++ b/script/deploy/zksync/LDA/utils/LDAScriptBase.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { Script } from "forge-std/Script.sol"; +import { DSTest } from "ds-test/test.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { stdJson } from "forge-std/Script.sol"; + +contract LDAScriptBase is Script, DSTest { + using stdJson for string; + + error NotAContract(string key); + + uint256 internal deployerPrivateKey; + address internal deployerAddress; + string internal root; + string internal network; + string internal fileSuffix; + + constructor() { + deployerPrivateKey = uint256(vm.envBytes32("PRIVATE_KEY")); + deployerAddress = vm.addr(deployerPrivateKey); + root = vm.projectRoot(); + network = vm.envString("NETWORK"); + fileSuffix = vm.envString("FILE_SUFFIX"); + } + + // reads an address from a config file and makes sure that the address contains code + function _getConfigContractAddress( + string memory path, + string memory key + ) internal returns (address contractAddress) { + // load json file + string memory json = vm.readFile(path); + + // read address + contractAddress = json.readAddress(key); + + // check if address contains code + if (!LibAsset.isContract(contractAddress)) + revert( + string.concat(key, " in file ", path, " is not a contract") + ); + } +} diff --git a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol new file mode 100644 index 000000000..1349310dd --- /dev/null +++ b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { LDAScriptBase } from "./LDAScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; + +contract UpdateLDAScriptBase is LDAScriptBase { + using stdJson for string; + + struct FunctionSignature { + string name; + bytes sig; + } + + address internal diamond; + LibDiamond.FacetCut[] internal cut; + bytes4[] internal selectorsToReplace; + bytes4[] internal selectorsToRemove; + bytes4[] internal selectorsToAdd; + LDADiamondCutFacet internal cutter; + LDADiamondLoupeFacet internal loupe; + string internal path; + string internal json; + bool internal noBroadcast = false; + bool internal useDefaultDiamond; + + error FailedToConvert(); + + constructor() { + useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); + noBroadcast = vm.envOr("NO_BROADCAST", false); + + path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); + json = vm.readFile(path); + diamond = json.readAddress(".LDADiamond"); + cutter = LDADiamondCutFacet(diamond); + loupe = LDADiamondLoupeFacet(diamond); + } + + function update( + string memory name + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + address facet = json.readAddress(string.concat(".", name)); + + bytes4[] memory excludes = getExcludes(); + bytes memory callData = getCallData(); + + buildDiamondCut(getSelectors(name, excludes), facet); + + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + LDADiamondCutFacet.diamondCut.selector, + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + + if (cut.length > 0) { + cutter.diamondCut( + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } + + function getExcludes() internal virtual returns (bytes4[] memory) {} + + function getCallData() internal virtual returns (bytes memory) {} + + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/zksync/LDA/utils/contract-selectors.sh"; + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + + function buildDiamondCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + address oldFacet; + + selectorsToAdd = new bytes4[](0); + selectorsToReplace = new bytes4[](0); + selectorsToRemove = new bytes4[](0); + + // Get selectors to add or replace + for (uint256 i; i < newSelectors.length; i++) { + if (loupe.facetAddress(newSelectors[i]) == address(0)) { + selectorsToAdd.push(newSelectors[i]); + // Don't replace if the new facet address is the same as the old facet address + } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { + selectorsToReplace.push(newSelectors[i]); + oldFacet = loupe.facetAddress(newSelectors[i]); + } + } + + // Get selectors to remove + bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); + for (uint256 i; i < oldSelectors.length; i++) { + bool found = false; + for (uint256 j; j < newSelectors.length; j++) { + if (oldSelectors[i] == newSelectors[j]) { + found = true; + break; + } + } + if (!found) { + selectorsToRemove.push(oldSelectors[i]); + } + } + + // Build diamond cut + if (selectorsToReplace.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Replace, + functionSelectors: selectorsToReplace + }) + ); + } + + if (selectorsToRemove.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(0), + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: selectorsToRemove + }) + ); + } + + if (selectorsToAdd.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectorsToAdd + }) + ); + } + } + + function buildInitialCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: newSelectors + }) + ); + } + + function toHexDigit(uint8 d) internal pure returns (bytes1) { + if (0 <= d && d <= 9) { + return bytes1(uint8(bytes1("0")) + d); + } else if (10 <= uint8(d) && uint8(d) <= 15) { + return bytes1(uint8(bytes1("a")) + d - 10); + } + revert FailedToConvert(); + } + + function fromCode(bytes4 code) public pure returns (string memory) { + bytes memory result = new bytes(10); + result[0] = bytes1("0"); + result[1] = bytes1("x"); + for (uint256 i = 0; i < 4; ++i) { + result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); + result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); + } + return string(result); + } +} diff --git a/script/deploy/zksync/LDA/utils/contract-selectors.sh b/script/deploy/zksync/LDA/utils/contract-selectors.sh new file mode 100755 index 000000000..0266173ea --- /dev/null +++ b/script/deploy/zksync/LDA/utils/contract-selectors.sh @@ -0,0 +1,11 @@ +#!/bin/bash +declare -a EXCLUDE +IFS=" " +read -a EXCLUDE <<< $(sed 's/0x//g' <<< "$2") +filter='[]' # Empty JSON array +for x in "${EXCLUDE[@]}"; do + filter=$(jq -n --arg x "$x" --argjson exclude "$filter" '$exclude + [$x]') +done + +SELECTORS=$(jq --argjson exclude "$filter" -r '.methodIdentifiers | . | del(.. | select(. == $exclude[])) | join(",")' ./out/zksync/$1.sol/$1.json) +cast abi-encode "f(bytes4[])" "[$SELECTORS]" From d444ffe1cbc9f94027908b48404b04539550ad29 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 12:24:08 +0200 Subject: [PATCH 141/220] Refactor CoreRouteFacet to remove WithdrawablePeriphery inheritance and update constructor. Adjust tests accordingly to reflect changes in instantiation. --- src/Periphery/LDA/Facets/CoreRouteFacet.sol | 15 ++------------- test/solidity/Periphery/GasZipPeriphery.t.sol | 2 +- .../Periphery/LDA/BaseCoreRouteTest.t.sol | 2 +- .../Periphery/LDA/Facets/CoreRouteFacet.t.sol | 17 ----------------- 4 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/Periphery/LDA/Facets/CoreRouteFacet.sol b/src/Periphery/LDA/Facets/CoreRouteFacet.sol index 2e3a56dd3..96d404307 100644 --- a/src/Periphery/LDA/Facets/CoreRouteFacet.sol +++ b/src/Periphery/LDA/Facets/CoreRouteFacet.sol @@ -8,8 +8,7 @@ import { LibUtil } from "lifi/Libraries/LibUtil.sol"; import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { ReentrancyGuard } from "lifi/Helpers/ReentrancyGuard.sol"; -import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol"; -import { InvalidConfig, InvalidReceiver } from "lifi/Errors/GenericErrors.sol"; +import { InvalidReceiver } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title CoreRouteFacet @@ -17,11 +16,7 @@ import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @notice Orchestrates LDA route execution using direct function selector dispatch /// @dev Implements selector-based routing where each DEX facet's swap function is called directly via its selector /// @custom:version 1.0.0 -contract CoreRouteFacet is - BaseRouteConstants, - ReentrancyGuard, - WithdrawablePeriphery -{ +contract CoreRouteFacet is BaseRouteConstants, ReentrancyGuard { using SafeERC20 for IERC20; using SafeERC20 for IERC20Permit; using LibPackedStream for uint256; @@ -47,12 +42,6 @@ contract CoreRouteFacet is error SwapFailed(); error UnknownSelector(); - /// @notice Constructor - /// @param _owner The address of the contract owner - constructor(address _owner) WithdrawablePeriphery(_owner) { - if (_owner == address(0)) revert InvalidConfig(); - } - // ==== External Functions ==== /// @notice Process a route encoded with function selectors for direct DEX facet dispatch /// @param tokenIn The input token address (address(0) for native) diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index c2403db60..0c65fb34a 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -513,7 +513,7 @@ contract GasZipPeripheryTest is TestBase { function _wireLDARouteFacets() internal { bytes4[] memory selectors; - CoreRouteFacet core = new CoreRouteFacet(USER_DIAMOND_OWNER); + CoreRouteFacet core = new CoreRouteFacet(); selectors = new bytes4[](1); selectors[0] = CoreRouteFacet.processRoute.selector; addFacet(address(ldaDiamond), address(core), selectors); diff --git a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol index 429dc5cd8..18d46f459 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol @@ -127,7 +127,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @notice Internal helper to deploy CoreRouteFacet and add its `processRoute` selector. /// @dev Sets `coreRouteFacet` to the diamond proxy after cut. function _addCoreRouteFacet() internal { - coreRouteFacet = new CoreRouteFacet(USER_LDA_DIAMOND_OWNER); + coreRouteFacet = new CoreRouteFacet(); bytes4[] memory selectors = new bytes4[](1); selectors[0] = CoreRouteFacet.processRoute.selector; addFacet(address(ldaDiamond), address(coreRouteFacet), selectors); diff --git a/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol index 131567c02..afbb38c0f 100644 --- a/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol @@ -6,7 +6,6 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; import { Vm } from "forge-std/Vm.sol"; @@ -95,22 +94,6 @@ contract CoreRouteFacetTest is BaseCoreRouteTest { // ==== Test Cases ==== - /// @notice Sanity-checks deployment wiring and ownership. - function test_ContractIsSetUpCorrectly() public { - // Test that owner is set correctly - assertEq( - coreRouteFacet.owner(), - USER_LDA_DIAMOND_OWNER, - "owner not set correctly" - ); - } - - /// @notice Constructor must revert on zero owner; verifies InvalidConfig. - function testRevert_WhenConstructedWithZeroAddress() public { - vm.expectRevert(InvalidConfig.selector); - new CoreRouteFacet(address(0)); - } - /// @notice Verifies DistributeNative command passes ETH to receiver and emits Route with exact out. /// @dev Builds a route with a mock native handler and funds USER_SENDER with 1 ETH. function test_DistributeNativeCommandSendsEthToReceiver() public { From 9e70242d0cd2e729385d95ee120bc2d3a144dc58 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 12:59:59 +0200 Subject: [PATCH 142/220] Remove LDA related copied scripts and related deployment scripts, tests, and documentation from the project. --- docs/LDAPeripheryRegistryFacet.md | 27 - .../facets/LDA/DeployLDADiamondCutFacet.s.sol | 15 - .../LDA/DeployLDADiamondLoupeFacet.s.sol | 15 - .../LDA/DeployLDAEmergencyPauseFacet.s.sol | 32 -- .../facets/LDA/DeployLDAOwnershipFacet.s.sol | 15 - .../LDA/DeployLDAPeripheryRegistryFacet.s.sol | 15 - .../LDA/UpdateLDAEmergencyPauseFacet.s.sol | 13 - .../facets/LDA/UpdateLDAOwnershipFacet.s.sol | 13 - .../LDA/Facets/LDADiamondCutFacet.sol | 26 - .../LDA/Facets/LDADiamondLoupeFacet.sol | 89 ---- .../LDA/Facets/LDAEmergencyPauseFacet.sol | 247 --------- .../LDA/Facets/LDAOwnershipFacet.sol | 92 ---- .../LDA/Facets/LDAPeripheryRegistryFacet.sol | 57 --- .../LDAEmergencyPauseFacet.fork.t.sol | 283 ---------- .../LDAEmergencyPauseFacet.local.t.sol | 483 ------------------ .../LDA/Facets/LDAOwnershipFacet.t.sol | 136 ----- .../Periphery/LDA/utils/LDADiamondTest.sol | 59 +-- .../utils/TestBaseRandomConstants.sol | 1 - 18 files changed, 11 insertions(+), 1607 deletions(-) delete mode 100644 docs/LDAPeripheryRegistryFacet.md delete mode 100644 script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol delete mode 100644 script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol delete mode 100644 script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol delete mode 100644 script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol delete mode 100644 script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol delete mode 100644 script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol delete mode 100644 script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol delete mode 100644 src/Periphery/LDA/Facets/LDADiamondCutFacet.sol delete mode 100644 src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol delete mode 100644 src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol delete mode 100644 src/Periphery/LDA/Facets/LDAOwnershipFacet.sol delete mode 100644 src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol delete mode 100644 test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol delete mode 100644 test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol delete mode 100644 test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol diff --git a/docs/LDAPeripheryRegistryFacet.md b/docs/LDAPeripheryRegistryFacet.md deleted file mode 100644 index 23ecabe61..000000000 --- a/docs/LDAPeripheryRegistryFacet.md +++ /dev/null @@ -1,27 +0,0 @@ -# Periphery Registry Facet - -## Description - -A simple facet for registering and keeping track of periphery contracts in LDA - -## How To Use - -This contract contract has two simple methods. One for registering a contract address with key -and another for retrieving that address by its key. - -Registering - -```solidity -/// @notice Registers a periphery contract address with a specified name -/// @param _name the name to register the contract address under -/// @param _contractAddress the address of the contract to register -function registerPeripheryContract(string calldata _name, address _contractAddress) -``` - -Retrieving - -```solidity -/// @notice Returns the registered contract address by its name -/// @param _name the registered name of the contract -function getPeripheryContract(string calldata _name) -``` diff --git a/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol deleted file mode 100644 index 26218a487..000000000 --- a/script/deploy/facets/LDA/DeployLDADiamondCutFacet.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; - -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDADiamondCutFacet") {} - - function run() public returns (LDADiamondCutFacet deployed) { - deployed = LDADiamondCutFacet( - deploy(type(LDADiamondCutFacet).creationCode) - ); - } -} diff --git a/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol b/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol deleted file mode 100644 index a736dffe3..000000000 --- a/script/deploy/facets/LDA/DeployLDADiamondLoupeFacet.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; - -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDADiamondLoupeFacet") {} - - function run() public returns (LDADiamondLoupeFacet deployed) { - deployed = LDADiamondLoupeFacet( - deploy(type(LDADiamondLoupeFacet).creationCode) - ); - } -} diff --git a/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol deleted file mode 100644 index d248d5420..000000000 --- a/script/deploy/facets/LDA/DeployLDAEmergencyPauseFacet.s.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { stdJson } from "forge-std/Script.sol"; -import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; - -contract DeployScript is DeployScriptBase { - using stdJson for string; - - constructor() DeployScriptBase("LDAEmergencyPauseFacet") {} - - function run() - public - returns (LDAEmergencyPauseFacet deployed, bytes memory constructorArgs) - { - constructorArgs = getConstructorArgs(); - - deployed = LDAEmergencyPauseFacet( - deploy(type(LDAEmergencyPauseFacet).creationCode) - ); - } - - function getConstructorArgs() internal override returns (bytes memory) { - string memory path = string.concat(root, "/config/global.json"); - string memory json = vm.readFile(path); - - address pauserWallet = json.readAddress(".pauserWallet"); - - return abi.encode(pauserWallet); - } -} diff --git a/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol deleted file mode 100644 index 4bd1aeef6..000000000 --- a/script/deploy/facets/LDA/DeployLDAOwnershipFacet.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; - -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDAOwnershipFacet") {} - - function run() public returns (LDAOwnershipFacet deployed) { - deployed = LDAOwnershipFacet( - deploy(type(LDAOwnershipFacet).creationCode) - ); - } -} diff --git a/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol b/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol deleted file mode 100644 index 9e348a348..000000000 --- a/script/deploy/facets/LDA/DeployLDAPeripheryRegistryFacet.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; - -contract DeployScript is DeployScriptBase { - constructor() DeployScriptBase("LDAPeripheryRegistryFacet") {} - - function run() public returns (LDAPeripheryRegistryFacet deployed) { - deployed = LDAPeripheryRegistryFacet( - deploy(type(LDAPeripheryRegistryFacet).creationCode) - ); - } -} diff --git a/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol deleted file mode 100644 index 61fc6cd4b..000000000 --- a/script/deploy/facets/LDA/UpdateLDAEmergencyPauseFacet.s.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; - -contract DeployScript is UpdateScriptBase { - function run() - public - returns (address[] memory facets, bytes memory cutData) - { - return update("LDAEmergencyPauseFacet"); - } -} diff --git a/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol b/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol deleted file mode 100644 index 6446fb028..000000000 --- a/script/deploy/facets/LDA/UpdateLDAOwnershipFacet.s.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { UpdateScriptBase } from "../utils/UpdateScriptBase.sol"; - -contract DeployScript is UpdateScriptBase { - function run() - public - returns (address[] memory facets, bytes memory cutData) - { - return update("LDAOwnershipFacet"); - } -} diff --git a/src/Periphery/LDA/Facets/LDADiamondCutFacet.sol b/src/Periphery/LDA/Facets/LDADiamondCutFacet.sol deleted file mode 100644 index e641f6551..000000000 --- a/src/Periphery/LDA/Facets/LDADiamondCutFacet.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { IDiamondCut } from "lifi/Interfaces/IDiamondCut.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; - -/// @title LDADiamondCutFacet -/// @author LI.FI (https://li.fi) -/// @notice Core EIP-2535 Facet for upgrading Diamond Proxies. -/// @custom:version 1.0.0 -contract LDADiamondCutFacet is IDiamondCut { - /// @notice Add/replace/remove any number of functions and optionally execute - /// a function with delegatecall - /// @param _diamondCut Contains the facet addresses and function selectors - /// @param _init The address of the contract or facet to execute _calldata - /// @param _calldata A function call, including function selector and arguments - /// _calldata is executed with delegatecall on _init - function diamondCut( - LibDiamond.FacetCut[] calldata _diamondCut, - address _init, - bytes calldata _calldata - ) external { - LibDiamond.enforceIsContractOwner(); - LibDiamond.diamondCut(_diamondCut, _init, _calldata); - } -} diff --git a/src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol b/src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol deleted file mode 100644 index faf7bcf3f..000000000 --- a/src/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; -import { IERC165 } from "lifi/Interfaces/IERC165.sol"; - -/// @title LDADiamondLoupeFacet -/// @author LI.FI (https://li.fi) -/// @notice Core EIP-2535 Facet for inspecting Diamond Proxies. -/// @custom:version 1.0.0 -contract LDADiamondLoupeFacet is IDiamondLoupe, IERC165 { - // Diamond Loupe Functions - //////////////////////////////////////////////////////////////////// - /// These functions are expected to be called frequently by tools. - // - // struct Facet { - // address facetAddress; - // bytes4[] functionSelectors; - // } - - /// @notice Gets all facets and their selectors. - /// @return facets_ Facet - function facets() external view override returns (Facet[] memory facets_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - uint256 numFacets = ds.facetAddresses.length; - facets_ = new Facet[](numFacets); - for (uint256 i = 0; i < numFacets; ) { - address facetAddress_ = ds.facetAddresses[i]; - facets_[i].facetAddress = facetAddress_; - facets_[i].functionSelectors = ds - .facetFunctionSelectors[facetAddress_] - .functionSelectors; - unchecked { - ++i; - } - } - } - - /// @notice Gets all the function selectors provided by a facet. - /// @param _facet The facet address. - /// @return facetFunctionSelectors_ - function facetFunctionSelectors( - address _facet - ) - external - view - override - returns (bytes4[] memory facetFunctionSelectors_) - { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - facetFunctionSelectors_ = ds - .facetFunctionSelectors[_facet] - .functionSelectors; - } - - /// @notice Get all the facet addresses used by a diamond. - /// @return facetAddresses_ - function facetAddresses() - external - view - override - returns (address[] memory facetAddresses_) - { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - facetAddresses_ = ds.facetAddresses; - } - - /// @notice Gets the facet that supports the given selector. - /// @dev If facet is not found return address(0). - /// @param _functionSelector The function selector. - /// @return facetAddress_ The facet address. - function facetAddress( - bytes4 _functionSelector - ) external view override returns (address facetAddress_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - facetAddress_ = ds - .selectorToFacetAndPosition[_functionSelector] - .facetAddress; - } - - // This implements ERC-165. - function supportsInterface( - bytes4 _interfaceId - ) external view override returns (bool) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - return ds.supportedInterfaces[_interfaceId]; - } -} diff --git a/src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol b/src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol deleted file mode 100644 index 6013a0768..000000000 --- a/src/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { LibDiamondLoupe } from "lifi/Libraries/LibDiamondLoupe.sol"; -import { UnAuthorized, InvalidCallData, DiamondIsPaused, InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; - -/// @title LDAEmergencyPauseFacet (Admin only) -/// @author LI.FI (https://li.fi) -/// @notice Allows a LI.FI-owned and -controlled, non-multisig "PauserWallet" to remove a facet -/// or pause the diamond in case of emergency -/// @custom:version 1.0.2 -/// @dev Admin-Facet for emergency purposes only -contract LDAEmergencyPauseFacet { - /// Events /// - event EmergencyFacetRemoved( - address indexed facetAddress, - address indexed msgSender - ); - event EmergencyPaused(address indexed msgSender); - event EmergencyUnpaused(address indexed msgSender); - - /// Errors /// - error FacetIsNotRegistered(); - error NoFacetToPause(); - - /// Storage /// - // solhint-disable-next-line immutable-vars-naming - address public immutable pauserWallet; - // solhint-disable-next-line immutable-vars-naming - bytes32 internal constant NAMESPACE = - keccak256("com.lifi.facets.emergencyPauseFacet"); - // solhint-disable-next-line immutable-vars-naming - address internal immutable _emergencyPauseFacetAddress; - - struct Storage { - IDiamondLoupe.Facet[] facets; - } - - /// Modifiers /// - modifier OnlyPauserWalletOrOwner() { - if ( - msg.sender != pauserWallet && - msg.sender != LibDiamond.contractOwner() - ) revert UnAuthorized(); - _; - } - - /// Constructor /// - /// @param _pauserWallet The address of the wallet that can execute emergency facet removal actions - constructor(address _pauserWallet) { - if (_pauserWallet == address(0)) revert InvalidConfig(); - pauserWallet = _pauserWallet; - _emergencyPauseFacetAddress = address(this); - } - - /// External Methods /// - - /// @notice Removes the given facet from the diamond - /// @param _facetAddress The address of the facet that should be removed - /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner - function removeFacet( - address _facetAddress - ) external OnlyPauserWalletOrOwner { - // make sure that the EmergencyPauseFacet itself cannot be removed through this function - if (_facetAddress == _emergencyPauseFacetAddress) - revert InvalidCallData(); - - // get function selectors for this facet - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - bytes4[] memory functionSelectors = ds - .facetFunctionSelectors[_facetAddress] - .functionSelectors; - - // do not continue if no registered function selectors were found - if (functionSelectors.length == 0) revert FacetIsNotRegistered(); - - // make sure that DiamondCutFacet cannot be removed - if (functionSelectors[0] == DiamondCutFacet.diamondCut.selector) - revert InvalidCallData(); - - // remove facet - LibDiamond.removeFunctions(address(0), functionSelectors); - - emit EmergencyFacetRemoved(_facetAddress, msg.sender); - } - - /// @notice Effectively pauses the diamond contract by overwriting the facetAddress-to-function-selector - /// mappings in storage for all facets and redirecting all function selectors to the - /// EmergencyPauseFacet (this will remain as the only registered facet) so that - /// a meaningful error message will be returned when third parties try to call the diamond - /// @dev can only be executed by pauserWallet (non-multisig for fast response time) or by the diamond owner - /// @dev This function could potentially run out of gas if too many facets/function selectors are involved. - /// We mitigate this issue by having a test on forked mainnet (which has most facets) - /// that checks if the diamond can be paused - /// @dev forked mainnet (which has most facets) that checks if the diamond can be paused - function pauseDiamond() external OnlyPauserWalletOrOwner { - Storage storage s = getStorage(); - - // get a list of all facets that need to be removed (=all facets except EmergencyPauseFacet) - IDiamondLoupe.Facet[] - memory facets = _getAllFacetFunctionSelectorsToBeRemoved(); - - // prevent invalid contract state - if (facets.length == 0) revert NoFacetToPause(); - - // go through all facets - for (uint256 i; i < facets.length; ) { - // redirect all function selectors to this facet (i.e. to its fallback function - // with the DiamondIsPaused() error message) - LibDiamond.replaceFunctions( - _emergencyPauseFacetAddress, - facets[i].functionSelectors - ); - - // write facet information to storage (so it can be easily reactivated later on) - s.facets.push(facets[i]); - - // gas-efficient way to increase loop counter - unchecked { - ++i; - } - } - - emit EmergencyPaused(msg.sender); - } - - /// @notice Unpauses the diamond contract by re-adding all facetAddress-to-function-selector mappings to storage - /// @dev can only be executed by diamond owner (multisig) - /// @param _blacklist The address(es) of facet(s) that should not be reactivated - function unpauseDiamond(address[] calldata _blacklist) external { - // make sure this function can only be called by the owner - LibDiamond.enforceIsContractOwner(); - - // get all facets from storage - Storage storage s = getStorage(); - - // iterate through all facets and reinstate the facet with its function selectors - for (uint256 i; i < s.facets.length; ) { - LibDiamond.replaceFunctions( - s.facets[i].facetAddress, - s.facets[i].functionSelectors - ); - - // gas-efficient way to increase loop counter - unchecked { - ++i; - } - } - - // go through blacklist and overwrite all function selectors with zero address - // It would be easier to not reinstate these facets in the first place but - // a) that would leave their function selectors associated with address of EmergencyPauseFacet - // (=> throws 'DiamondIsPaused() error when called) - // b) it consumes a lot of gas to check every facet address if it's part of the blacklist - bytes4[] memory currentSelectors; - for (uint256 i; i < _blacklist.length; ) { - currentSelectors = LibDiamondLoupe.facetFunctionSelectors( - _blacklist[i] - ); - - // make sure that the DiamondCutFacet cannot be removed as this would make the diamond immutable - if (currentSelectors[0] == DiamondCutFacet.diamondCut.selector) - continue; - - // build FacetCut parameter - LibDiamond.FacetCut[] memory facetCut = new LibDiamond.FacetCut[]( - 1 - ); - facetCut[0] = LibDiamond.FacetCut({ - facetAddress: address(0), // needs to be address(0) for removals - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: currentSelectors - }); - - // remove facet and its selectors from diamond - LibDiamond.diamondCut(facetCut, address(0), ""); - - // gas-efficient way to increase loop counter - unchecked { - ++i; - } - } - - // free storage - delete s.facets; - - emit EmergencyUnpaused(msg.sender); - } - - /// INTERNAL HELPER FUNCTIONS - - function _getAllFacetFunctionSelectorsToBeRemoved() - internal - view - returns (IDiamondLoupe.Facet[] memory toBeRemoved) - { - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory allFacets = LibDiamondLoupe.facets(); - - // initiate return variable with allFacets length - 1 (since we will not remove the EmergencyPauseFacet) - toBeRemoved = new IDiamondLoupe.Facet[](allFacets.length - 1); - - // iterate through facets, copy every facet but EmergencyPauseFacet - uint256 toBeRemovedCounter; - for (uint256 i; i < allFacets.length; ) { - // if its not the EmergencyPauseFacet, copy to the return value variable - if (allFacets[i].facetAddress != _emergencyPauseFacetAddress) { - toBeRemoved[toBeRemovedCounter].facetAddress = allFacets[i] - .facetAddress; - toBeRemoved[toBeRemovedCounter].functionSelectors = allFacets[ - i - ].functionSelectors; - - // gas-efficient way to increase counter - unchecked { - ++toBeRemovedCounter; - } - } - - // gas-efficient way to increase loop counter - unchecked { - ++i; - } - } - } - - /// @dev fetch local storage - function getStorage() private pure returns (Storage storage s) { - bytes32 namespace = NAMESPACE; - // solhint-disable-next-line no-inline-assembly - assembly { - s.slot := namespace - } - } - - // this function will be called when the diamond is paused to return a meaningful error message - // instead of "FunctionDoesNotExist" - fallback() external payable { - revert DiamondIsPaused(); - } - - // only added to silence compiler warnings that arose after adding the fallback function - receive() external payable {} -} diff --git a/src/Periphery/LDA/Facets/LDAOwnershipFacet.sol b/src/Periphery/LDA/Facets/LDAOwnershipFacet.sol deleted file mode 100644 index 89177242e..000000000 --- a/src/Periphery/LDA/Facets/LDAOwnershipFacet.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { IERC173 } from "lifi/Interfaces/IERC173.sol"; -import { LibUtil } from "lifi/Libraries/LibUtil.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; - -/// @title LDAOwnershipFacet -/// @author LI.FI (https://li.fi) -/// @notice Manages ownership of the LiFi Diamond contract for admin purposes -/// @custom:version 1.0.0 -contract LDAOwnershipFacet is IERC173 { - /// Storage /// - - bytes32 internal constant NAMESPACE = - keccak256("com.lifi.facets.ownership"); - - /// Types /// - - struct Storage { - address newOwner; - } - - /// Errors /// - - error NoNullOwner(); - error NewOwnerMustNotBeSelf(); - error NoPendingOwnershipTransfer(); - error NotPendingOwner(); - - /// Events /// - - event OwnershipTransferRequested( - address indexed _from, - address indexed _to - ); - - /// External Methods /// - - /// @notice Initiates transfer of ownership to a new address - /// @param _newOwner the address to transfer ownership to - function transferOwnership(address _newOwner) external override { - LibDiamond.enforceIsContractOwner(); - Storage storage s = getStorage(); - - if (LibUtil.isZeroAddress(_newOwner)) revert NoNullOwner(); - - if (_newOwner == LibDiamond.contractOwner()) - revert NewOwnerMustNotBeSelf(); - - s.newOwner = _newOwner; - emit OwnershipTransferRequested(msg.sender, s.newOwner); - } - - /// @notice Cancel transfer of ownership - function cancelOwnershipTransfer() external { - LibDiamond.enforceIsContractOwner(); - Storage storage s = getStorage(); - - if (LibUtil.isZeroAddress(s.newOwner)) - revert NoPendingOwnershipTransfer(); - s.newOwner = address(0); - } - - /// @notice Confirms transfer of ownership to the calling address (msg.sender) - function confirmOwnershipTransfer() external { - Storage storage s = getStorage(); - address _pendingOwner = s.newOwner; - if (msg.sender != _pendingOwner) revert NotPendingOwner(); - emit OwnershipTransferred(LibDiamond.contractOwner(), _pendingOwner); - LibDiamond.setContractOwner(_pendingOwner); - s.newOwner = LibAsset.NULL_ADDRESS; - } - - /// @notice Return the current owner address - /// @return owner_ The current owner address - function owner() external view override returns (address owner_) { - owner_ = LibDiamond.contractOwner(); - } - - /// Private Methods /// - - /// @dev fetch local storage - function getStorage() private pure returns (Storage storage s) { - bytes32 namespace = NAMESPACE; - // solhint-disable-next-line no-inline-assembly - assembly { - s.slot := namespace - } - } -} diff --git a/src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol b/src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol deleted file mode 100644 index f2a9d59d0..000000000 --- a/src/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; - -/// @title Periphery Registry Facet -/// @author LI.FI (https://li.fi) -/// @notice A simple registry to track LIFI periphery contracts -/// @custom:version 1.0.0 -contract LDAPeripheryRegistryFacet { - /// Storage /// - - bytes32 internal constant NAMESPACE = - keccak256("com.lifi.facets.periphery_registry"); - - /// Types /// - - struct Storage { - mapping(string => address) contracts; - } - - /// Events /// - - event PeripheryContractRegistered(string name, address contractAddress); - - /// External Methods /// - - /// @notice Registers a periphery contract address with a specified name - /// @param _name the name to register the contract address under - /// @param _contractAddress the address of the contract to register - function registerPeripheryContract( - string calldata _name, - address _contractAddress - ) external { - LibDiamond.enforceIsContractOwner(); - Storage storage s = getStorage(); - s.contracts[_name] = _contractAddress; - emit PeripheryContractRegistered(_name, _contractAddress); - } - - /// @notice Returns the registered contract address by its name - /// @param _name the registered name of the contract - function getPeripheryContract( - string calldata _name - ) external view returns (address) { - return getStorage().contracts[_name]; - } - - /// @dev fetch local storage - function getStorage() private pure returns (Storage storage s) { - bytes32 namespace = NAMESPACE; - // solhint-disable-next-line no-inline-assembly - assembly { - s.slot := namespace - } - } -} diff --git a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol deleted file mode 100644 index 8e10b10ac..000000000 --- a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.fork.t.sol +++ /dev/null @@ -1,283 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LibAllowList, TestBase } from "../../../../utils/TestBase.sol"; -import { OnlyContractOwner, UnAuthorized, DiamondIsPaused } from "src/Errors/GenericErrors.sol"; -import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; -import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; -import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; - -// Stub EmergencyPauseFacet Contract -contract TestEmergencyPauseFacet is LDAEmergencyPauseFacet { - constructor(address _pauserWallet) LDAEmergencyPauseFacet(_pauserWallet) {} - - function addDex(address _dex) external { - LibAllowList.addAllowedContract(_dex); - } - - function setFunctionApprovalBySignature(bytes4 _signature) external { - LibAllowList.addAllowedSelector(_signature); - } -} - -contract LDAEmergencyPauseFacetPRODTest is TestBase { - // EVENTS - event EmergencyFacetRemoved( - address indexed facetAddress, - address indexed msgSender - ); - event EmergencyPaused(address indexed msgSender); - event EmergencyUnpaused(address indexed msgSender); - - // STORAGE - address internal constant ADDRESS_DIAMOND_MAINNET = - 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE; - address internal constant USER_DIAMOND_OWNER_MAINNET = - 0x37347dD595C49212C5FC2D95EA10d1085896f51E; - TestEmergencyPauseFacet internal emergencyPauseFacet; - address[] internal blacklist = new address[](0); - - function setUp() public override { - // set custom block number for forking - customBlockNumberForForking = 19979843; - - initTestBase(); - - // deploy EmergencyPauseFacet - emergencyPauseFacet = new TestEmergencyPauseFacet(USER_PAUSER); - - // prepare diamondCut - bytes4[] memory functionSelectors = new bytes4[](3); - functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; - functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; - functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; - - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(emergencyPauseFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) - ); - - // add EmergencyPauseFacet to PROD diamond - vm.startPrank(USER_DIAMOND_OWNER_MAINNET); - LDADiamondCutFacet(address(ADDRESS_DIAMOND_MAINNET)).diamondCut( - cut, - address(0), - "" - ); - - // store diamond in local TestEmergencyPauseFacet variable - emergencyPauseFacet = TestEmergencyPauseFacet( - payable(address(ADDRESS_DIAMOND_MAINNET)) - ); - - // set facet address in TestBase - setFacetAddressInTestBase( - address(emergencyPauseFacet), - "EmergencyPauseFacet" - ); - - vm.stopPrank(); - } - - function test_PauserWalletCanPauseDiamond() public { - vm.startPrank(USER_PAUSER); - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyPaused(USER_PAUSER); - // pause the contract - emergencyPauseFacet.pauseDiamond(); - // try to get a list of all registered facets via DiamondLoupe - vm.expectRevert(DiamondIsPaused.selector); - LDADiamondLoupeFacet(address(emergencyPauseFacet)).facets(); - } - - function test_DiamondOwnerCanPauseDiamond() public { - vm.startPrank(USER_DIAMOND_OWNER_MAINNET); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyPaused(USER_DIAMOND_OWNER_MAINNET); - - // pause the contract - emergencyPauseFacet.pauseDiamond(); - - // try to get a list of all registered facets via DiamondLoupe - vm.expectRevert(DiamondIsPaused.selector); - LDADiamondLoupeFacet(address(emergencyPauseFacet)).facets(); - } - - function test_UnauthorizedWalletCannotPauseDiamond() public { - vm.startPrank(USER_SENDER); - vm.expectRevert(UnAuthorized.selector); - // pause the contract - emergencyPauseFacet.pauseDiamond(); - - vm.startPrank(USER_RECEIVER); - vm.expectRevert(UnAuthorized.selector); - // pause the contract - emergencyPauseFacet.pauseDiamond(); - } - - function test_DiamondOwnerCanUnpauseDiamond() public { - IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - // pause diamond first - test_PauserWalletCanPauseDiamond(); - - // unpause diamond as owner - vm.startPrank(USER_DIAMOND_OWNER_MAINNET); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyUnpaused(USER_DIAMOND_OWNER_MAINNET); - - emergencyPauseFacet.unpauseDiamond(blacklist); - - // make sure diamond works normal again and has all facets reinstated - IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - assertTrue(initialFacets.length == finalFacets.length); - } - - function test_UnauthorizedWalletCannotUnpauseDiamond() public { - // pause diamond first - test_PauserWalletCanPauseDiamond(); - - // try to pause the diamond with various wallets - vm.startPrank(USER_PAUSER); - vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacet.unpauseDiamond(blacklist); - - vm.startPrank(USER_RECEIVER); - vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacet.unpauseDiamond(blacklist); - - // make sure diamond is still paused - vm.expectRevert(DiamondIsPaused.selector); - LDADiamondLoupeFacet(address(emergencyPauseFacet)).facets(); - } - - function test_DiamondOwnerCanRemoveFacet() public { - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - // get LDAPeripheryRegistryFacet address - address facetAddress = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facetAddress( - LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) - .registerPeripheryContract - .selector - ); - - // remove facet - vm.startPrank(USER_DIAMOND_OWNER_MAINNET); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER_MAINNET); - - emergencyPauseFacet.removeFacet(facetAddress); - - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - // ensure that one facet less is registered now - assertTrue(initialFacets.length == finalFacets.length + 1); - // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress - assertTrue( - LDADiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( - LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) - .registerPeripheryContract - .selector - ) == address(0) - ); - - vm.stopPrank(); - } - - function test_PauserWalletCanRemoveFacet() public { - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - // get PeripheryRegistryFacet address - address facetAddress = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facetAddress( - LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) - .registerPeripheryContract - .selector - ); - - // remove facet - vm.startPrank(USER_PAUSER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); - - emergencyPauseFacet.removeFacet(facetAddress); - - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - // ensure that one facet less is registered now - assertTrue(initialFacets.length == finalFacets.length + 1); - // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress - assertTrue( - LDADiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( - LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) - .registerPeripheryContract - .selector - ) == address(0) - ); - - vm.stopPrank(); - } - - function test_UnauthorizedWalletCannotRemoveFacet() public { - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - // get LDAPeripheryRegistryFacet address - address facetAddress = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facetAddress( - LDAPeripheryRegistryFacet(address(emergencyPauseFacet)) - .registerPeripheryContract - .selector - ); - - // try to remove facet - vm.startPrank(USER_SENDER); - vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacet.removeFacet(facetAddress); - - vm.startPrank(USER_RECEIVER); - vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacet.removeFacet(facetAddress); - - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( - address(emergencyPauseFacet) - ).facets(); - - // ensure that number of facets remains unchanged - assertTrue(initialFacets.length == finalFacets.length); - } -} diff --git a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol b/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol deleted file mode 100644 index 12b5a0b8b..000000000 --- a/test/solidity/Periphery/LDA/Facets/LDAEmergencyPauseFacet/LDAEmergencyPauseFacet.local.t.sol +++ /dev/null @@ -1,483 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { TestBase } from "../../../../utils/TestBase.sol"; -import { OnlyContractOwner, InvalidCallData, UnAuthorized, DiamondIsPaused, FunctionDoesNotExist, InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; -import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; -import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { IDiamondLoupe } from "lifi/Interfaces/IDiamondLoupe.sol"; -import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; - -contract LDAEmergencyPauseFacetLOCALTest is TestBase { - // EVENTS - event EmergencyFacetRemoved( - address indexed facetAddress, - address indexed msgSender - ); - event EmergencyPaused(address indexed msgSender); - event EmergencyUnpaused(address indexed msgSender); - - error NoFacetToPause(); - - uint256 internal counter; - event DiamondCut( - LibDiamond.FacetCut[] _diamondCut, - address _init, - bytes _calldata - ); - - // STORAGE - LDAEmergencyPauseFacet internal emergencyPauseFacet; - address[] internal blacklist = new address[](0); - - function setUp() public override { - // set custom block number for forking - customBlockNumberForForking = 19979843; - - initTestBase(); - - // // no need to add the facet to the diamond, it's already added in DiamondTest.sol - emergencyPauseFacet = LDAEmergencyPauseFacet( - payable(address(diamond)) - ); - - // set facet address in TestBase - setFacetAddressInTestBase( - address(emergencyPauseFacet), - "EmergencyPauseFacet" - ); - } - - function testRevert_WhenPauserWalletIsZeroAddress() public { - vm.expectRevert(InvalidConfig.selector); - new LDAEmergencyPauseFacet(address(0)); - } - - function test_PauserWalletCanPauseDiamond() public { - vm.startPrank(USER_PAUSER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyPaused(USER_PAUSER); - - // pause the contract - emergencyPauseFacet.pauseDiamond(); - - // try to get a list of all registered facets via DiamondLoupe - vm.expectRevert(DiamondIsPaused.selector); - LDADiamondLoupeFacet(address(diamond)).facets(); - } - - function test_WillRevertWhenTryingToPauseTwice() public { - vm.startPrank(USER_PAUSER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyPaused(USER_PAUSER); - - // pause the contract - emergencyPauseFacet.pauseDiamond(); - - // try to get a list of all registered facets via DiamondLoupe - vm.expectRevert(NoFacetToPause.selector); - // pause the contract - emergencyPauseFacet.pauseDiamond(); - } - - function test_DiamondOwnerCanPauseDiamond() public { - vm.startPrank(USER_DIAMOND_OWNER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyPaused(USER_DIAMOND_OWNER); - - // pause the contract - emergencyPauseFacet.pauseDiamond(); - - // try to get a list of all registered facets via DiamondLoupe - vm.expectRevert(DiamondIsPaused.selector); - LDADiamondLoupeFacet(address(diamond)).facets(); - } - - function test_UnauthorizedWalletCannotPauseDiamond() public { - vm.startPrank(USER_SENDER); - vm.expectRevert(UnAuthorized.selector); - // pause the contract - emergencyPauseFacet.pauseDiamond(); - - vm.startPrank(USER_RECEIVER); - vm.expectRevert(UnAuthorized.selector); - // pause the contract - emergencyPauseFacet.pauseDiamond(); - } - - function test_DiamondOwnerCanUnpauseDiamondWithEmptyBlacklist() public { - // pause diamond first - test_PauserWalletCanPauseDiamond(); - - // unpause diamond as owner - vm.startPrank(USER_DIAMOND_OWNER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyUnpaused(USER_DIAMOND_OWNER); - - emergencyPauseFacet.unpauseDiamond(blacklist); - - // make sure diamond works normal again and has all facets reinstated - IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - assertTrue(allFacets.length == 5); - - // try the same again to make sure commands can be repeatedly executed - test_PauserWalletCanPauseDiamond(); - - // unpause diamond as owner - vm.startPrank(USER_DIAMOND_OWNER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyUnpaused(USER_DIAMOND_OWNER); - - emergencyPauseFacet.unpauseDiamond(blacklist); - - // make sure diamond works normal again and has all facets reinstated - allFacets = LDADiamondLoupeFacet(address(diamond)).facets(); - - assertTrue(allFacets.length == 5); - - // try the same again to make sure commands can be repeatedly executed - test_PauserWalletCanPauseDiamond(); - } - - function test_CanUnpauseDiamondWithSingleBlacklist() public { - address ownershipFacetAddress = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; - - // get function selectors of OwnershipFacet - bytes4[] memory ownershipFunctionSelectors = IDiamondLoupe( - address(diamond) - ).facetFunctionSelectors(ownershipFacetAddress); - - // pause diamond first - test_PauserWalletCanPauseDiamond(); - - // unpause diamond as owner - vm.startPrank(USER_DIAMOND_OWNER); - - // build diamondCut - LibDiamond.FacetCut[] memory facetCut = new LibDiamond.FacetCut[](1); - facetCut[0] = LibDiamond.FacetCut({ - facetAddress: address(0), - // facetAddress: ownershipFacetAddress, - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: ownershipFunctionSelectors - }); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit DiamondCut(facetCut, address(0), ""); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyUnpaused(USER_DIAMOND_OWNER); - - blacklist = new address[](1); - blacklist[0] = ownershipFacetAddress; // OwnershipFacet - - emergencyPauseFacet.unpauseDiamond(blacklist); - - // make sure diamond works normal again and has all facets reinstated - IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - assertTrue(allFacets.length == 4); - - // make sure ownershipFacet is not available anymore - vm.expectRevert(FunctionDoesNotExist.selector); - LDAOwnershipFacet(address(diamond)).owner(); - } - - function test_CanUnpauseDiamondWithMultiBlacklist() public { - // pause diamond first - test_PauserWalletCanPauseDiamond(); - - // unpause diamond as owner - vm.startPrank(USER_DIAMOND_OWNER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyUnpaused(USER_DIAMOND_OWNER); - - blacklist = new address[](2); - blacklist[0] = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; // OwnershipFacet - blacklist[1] = 0xA412555Fa40F6AA4B67a773dB5a7f85983890341; // PeripheryRegistryFacet - - emergencyPauseFacet.unpauseDiamond(blacklist); - - // make sure diamond works normal again and has all facets reinstated - IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - assertTrue(allFacets.length == 3); - - // make sure ownershipFacet is not available anymore - vm.expectRevert(FunctionDoesNotExist.selector); - LDAOwnershipFacet(address(diamond)).owner(); - - // make sure PeripheryRegistryFacet is not available anymore - vm.expectRevert(FunctionDoesNotExist.selector); - LDAPeripheryRegistryFacet(address(diamond)).getPeripheryContract( - "Executor" - ); - } - - function test_UnauthorizedWalletCannotUnpauseDiamond() public { - // pause diamond first - test_PauserWalletCanPauseDiamond(); - - // make sure it's paused - vm.expectRevert(DiamondIsPaused.selector); - IDiamondLoupe.Facet[] memory allFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - vm.startPrank(USER_PAUSER); - vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacet.unpauseDiamond(blacklist); - - vm.startPrank(USER_RECEIVER); - vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacet.unpauseDiamond(blacklist); - - // make sure diamond is still paused - vm.expectRevert(DiamondIsPaused.selector); - allFacets = LDADiamondLoupeFacet(address(diamond)).facets(); - } - - function test_DiamondOwnerCanRemoveFacetAndUnpauseDiamond() public { - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - // get LDAPeripheryRegistryFacet address - address facetAddress = LDADiamondLoupeFacet(address(diamond)) - .facetAddress( - LDAPeripheryRegistryFacet(address(diamond)) - .registerPeripheryContract - .selector - ); - - // remove facet - vm.startPrank(USER_DIAMOND_OWNER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER); - - emergencyPauseFacet.removeFacet(facetAddress); - - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - // ensure that one facet less is registered now - assertTrue(initialFacets.length == finalFacets.length + 1); - // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress - assertTrue( - LDADiamondLoupeFacet(address(diamond)).facetAddress( - LDAPeripheryRegistryFacet(address(diamond)) - .registerPeripheryContract - .selector - ) == address(0) - ); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyUnpaused(USER_DIAMOND_OWNER); - - // unpause diamond with empty blacklist - emergencyPauseFacet.unpauseDiamond(blacklist); - - vm.stopPrank(); - } - - function test_PauserWalletCanRemoveFacet() public { - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - // get LDAPeripheryRegistryFacet address - address facetAddress = LDADiamondLoupeFacet(address(diamond)) - .facetAddress( - LDAPeripheryRegistryFacet(address(diamond)) - .registerPeripheryContract - .selector - ); - - // remove facet - vm.startPrank(USER_PAUSER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); - - emergencyPauseFacet.removeFacet(facetAddress); - - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - // ensure that one facet less is registered now - assertTrue(initialFacets.length == finalFacets.length + 1); - // ensure that LDAPeripheryRegistryFacet function selector is not associated to any facetAddress - assertTrue( - LDADiamondLoupeFacet(address(diamond)).facetAddress( - LDAPeripheryRegistryFacet(address(diamond)) - .registerPeripheryContract - .selector - ) == address(0) - ); - - vm.stopPrank(); - } - - function test_WillRevertWhenTryingToRemoveDiamondCutFacet() public { - vm.startPrank(USER_PAUSER); - - // get address of diamondCutFacet - address diamondCutAddress = LDADiamondLoupeFacet(address(diamond)) - .facetAddress( - LDADiamondCutFacet(address(diamond)).diamondCut.selector - ); - - vm.expectRevert(InvalidCallData.selector); - - // remove facet - emergencyPauseFacet.removeFacet(diamondCutAddress); - - vm.stopPrank(); - } - - function test_WillRevertWhenTryingToRemoveEmergencyPauseFacet() public { - vm.startPrank(USER_PAUSER); - - // get address of EmergencyPauseFacet - address emergencyPauseAddress = LDADiamondLoupeFacet(address(diamond)) - .facetAddress( - LDAEmergencyPauseFacet(payable(address(diamond))) - .pauseDiamond - .selector - ); - - vm.expectRevert(InvalidCallData.selector); - - // remove facet - emergencyPauseFacet.removeFacet(emergencyPauseAddress); - - vm.stopPrank(); - } - - function test_UnauthorizedWalletCannotRemoveFacet() public { - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory initialFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - // get LDAPeripheryRegistryFacet address - address facetAddress = LDADiamondLoupeFacet(address(diamond)) - .facetAddress( - LDAPeripheryRegistryFacet(address(diamond)) - .registerPeripheryContract - .selector - ); - - // try to remove facet - vm.startPrank(USER_SENDER); - vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacet.removeFacet(facetAddress); - - vm.startPrank(USER_RECEIVER); - vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacet.removeFacet(facetAddress); - - // get a list of all registered facet addresses - IDiamondLoupe.Facet[] memory finalFacets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - // ensure that number of facets remains unchanged - assertTrue(initialFacets.length == finalFacets.length); - } - - function test_HowManyFacetsCanWePauseMax() public { - uint256 contractsCount = 500; - // deploy dummy contracts and store their addresses - address[] memory contractAddresses = new address[](contractsCount); - - for (uint256 i; i < contractsCount; i++) { - contractAddresses[i] = address(new DummyContract()); - } - - // build diamondCut data - // Add the diamondCut external function from the diamondCutFacet - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[]( - contractsCount - ); - for (uint256 i; i < contractsCount; i++) { - cut[i] = LibDiamond.FacetCut({ - facetAddress: contractAddresses[i], - action: LibDiamond.FacetCutAction.Add, - functionSelectors: generateRandomBytes4Array() - }); - } - LDADiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); - - // - IDiamondLoupe.Facet[] memory facets = LDADiamondLoupeFacet( - address(diamond) - ).facets(); - - assert(facets.length >= contractsCount); - - // try to pause - - vm.startPrank(USER_PAUSER); - - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); - emit EmergencyPaused(USER_PAUSER); - - // pause the contract - emergencyPauseFacet.pauseDiamond(); - - // try to get a list of all registered facets via DiamondLoupe - vm.expectRevert(DiamondIsPaused.selector); - LDADiamondLoupeFacet(address(diamond)).facets(); - } - - function generateRandomBytes4Array() - public - returns (bytes4[] memory randomValues) - { - randomValues = new bytes4[](3); - - for (uint256 i = 0; i < 3; i++) { - counter++; - randomValues[i] = bytes4( - keccak256( - abi.encodePacked( - block.timestamp, - block.number, - msg.sender, - counter - ) - ) - ); - } - - return randomValues; - } -} - -contract DummyContract { - string internal bla = "I am a dummy contract"; -} diff --git a/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol b/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol deleted file mode 100644 index 63ebaff73..000000000 --- a/test/solidity/Periphery/LDA/Facets/LDAOwnershipFacet.t.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; -import { TestBase } from "../../../utils/TestBase.sol"; -import { OnlyContractOwner } from "lifi/Errors/GenericErrors.sol"; - -contract LDAOwnershipFacetTest is TestBase { - LDAOwnershipFacet internal ownershipFacet; - - error NoNullOwner(); - error NewOwnerMustNotBeSelf(); - error NoPendingOwnershipTransfer(); - error NotPendingOwner(); - - event OwnershipTransferRequested( - address indexed _from, - address indexed _to - ); - - event OwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - - function setUp() public override { - initTestBase(); - - ownershipFacet = LDAOwnershipFacet(address(diamond)); - } - - function test_OwnerCanTransferOwnership() public { - vm.startPrank(USER_DIAMOND_OWNER); - - address newOwner = address(0x1234567890123456789012345678901234567890); - - vm.expectEmit(true, true, true, true, address(ownershipFacet)); - emit OwnershipTransferRequested(address(this), newOwner); - - ownershipFacet.transferOwnership(newOwner); - - assert(ownershipFacet.owner() != newOwner); - - vm.stopPrank(); - vm.startPrank(newOwner); - - vm.expectEmit(true, true, true, true, address(ownershipFacet)); - emit OwnershipTransferred(address(USER_DIAMOND_OWNER), newOwner); - - ownershipFacet.confirmOwnershipTransfer(); - - assert(ownershipFacet.owner() == newOwner); - - vm.stopPrank(); - } - - function testRevert_CannotCancelNonPendingOwnershipTransfer() public { - assert(ownershipFacet.owner() == USER_DIAMOND_OWNER); - vm.startPrank(USER_DIAMOND_OWNER); - - vm.expectRevert(NoPendingOwnershipTransfer.selector); - - ownershipFacet.cancelOwnershipTransfer(); - - assert(ownershipFacet.owner() == USER_DIAMOND_OWNER); - - vm.stopPrank(); - } - - function test_OwnerCanCancelOwnershipTransfer() public { - address newOwner = address(0x1234567890123456789012345678901234567890); - - ownershipFacet.transferOwnership(newOwner); - - assert(ownershipFacet.owner() != newOwner); - - ownershipFacet.cancelOwnershipTransfer(); - - assert(ownershipFacet.owner() != newOwner); - } - - function testRevert_NonOwnerCannotCancelOwnershipTransfer() public { - address newOwner = address(0x1234567890123456789012345678901234567890); - - ownershipFacet.transferOwnership(newOwner); - - assert(ownershipFacet.owner() != newOwner); - - vm.startPrank(newOwner); - - vm.expectRevert(OnlyContractOwner.selector); - - ownershipFacet.cancelOwnershipTransfer(); - - assert(ownershipFacet.owner() != newOwner); - - vm.stopPrank(); - } - - function testRevert_NonOwnerCannotTransferOwnership() public { - address newOwner = address(0x1234567890123456789012345678901234567890); - assert(ownershipFacet.owner() != newOwner); - vm.prank(newOwner); - - vm.expectRevert(OnlyContractOwner.selector); - - ownershipFacet.transferOwnership(newOwner); - } - - function testRevert_CannotTransferOnwershipToNullAddr() public { - address newOwner = address(0); - - vm.expectRevert(NoNullOwner.selector); - - ownershipFacet.transferOwnership(newOwner); - } - - function testRevert_PendingOwnershipTransferCannotBeConfirmedByNonNewOwner() - public - { - address newOwner = address(0x1234567890123456789012345678901234567890); - ownershipFacet.transferOwnership(newOwner); - - vm.expectRevert(NotPendingOwner.selector); - - ownershipFacet.confirmOwnershipTransfer(); - } - - function testRevert_CannotTransferOwnershipToSelf() public { - address newOwner = address(this); - - vm.expectRevert(NewOwnerMustNotBeSelf.selector); - - ownershipFacet.transferOwnership(newOwner); - } -} diff --git a/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol b/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol index 986c8ac36..e0a064020 100644 --- a/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol +++ b/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol @@ -2,11 +2,9 @@ pragma solidity ^0.8.17; import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; -import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; -import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; -import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; -import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; @@ -22,29 +20,23 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. function setUp() public virtual { - ldaDiamond = createLDADiamond(USER_LDA_DIAMOND_OWNER, USER_LDA_PAUSER); + ldaDiamond = createLDADiamond(USER_LDA_DIAMOND_OWNER); } /// @notice Creates an LDA diamond and wires up Loupe, Ownership and EmergencyPause facets. /// @param _diamondOwner Owner address for the diamond. - /// @param _pauserWallet Pauser address for the emergency pause facet. /// @return diamond The newly created diamond instance. function createLDADiamond( - address _diamondOwner, - address _pauserWallet + address _diamondOwner ) internal returns (LDADiamond) { vm.startPrank(_diamondOwner); - LDADiamondCutFacet diamondCut = new LDADiamondCutFacet(); - LDADiamondLoupeFacet diamondLoupe = new LDADiamondLoupeFacet(); - LDAOwnershipFacet ownership = new LDAOwnershipFacet(); - LDAEmergencyPauseFacet emergencyPause = new LDAEmergencyPauseFacet( - _pauserWallet - ); + DiamondCutFacet diamondCut = new DiamondCutFacet(); + DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); + OwnershipFacet ownership = new OwnershipFacet(); LDADiamond diamond = new LDADiamond( _diamondOwner, address(diamondCut) ); - LDAPeripheryRegistryFacet periphery = new LDAPeripheryRegistryFacet(); // Add Diamond Loupe _addDiamondLoupeSelectors(address(diamondLoupe)); @@ -52,36 +44,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { // Add Ownership _addOwnershipSelectors(address(ownership)); - // Add PeripheryRegistry - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = LDAPeripheryRegistryFacet - .registerPeripheryContract - .selector; - functionSelectors[1] = LDAPeripheryRegistryFacet - .getPeripheryContract - .selector; - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(periphery), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) - ); - - // Add EmergencyPause - functionSelectors = new bytes4[](3); - functionSelectors[0] = emergencyPause.removeFacet.selector; - functionSelectors[1] = emergencyPause.pauseDiamond.selector; - functionSelectors[2] = emergencyPause.unpauseDiamond.selector; - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(emergencyPause), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) - ); - - LDADiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); + DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); delete cut; vm.stopPrank(); return diamond; @@ -89,7 +52,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @notice Tests that diamond creation fails when owner address is zero function testRevert_CannotDeployDiamondWithZeroOwner() public { - address diamondCutFacet = address(new LDADiamondCutFacet()); + address diamondCutFacet = address(new DiamondCutFacet()); vm.expectRevert(InvalidConfig.selector); new LDADiamond( @@ -124,7 +87,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { function testRevert_CannotCallUnregisteredSelector() public { // Use a real function selector that exists but hasn't been registered yet bytes memory unregisteredCalldata = abi.encodeWithSelector( - LDADiamondCutFacet.diamondCut.selector, // Valid selector but not registered yet + DiamondCutFacet.diamondCut.selector, // Valid selector but not registered yet new LibDiamond.FacetCut[](0), address(0), "" diff --git a/test/solidity/utils/TestBaseRandomConstants.sol b/test/solidity/utils/TestBaseRandomConstants.sol index 1599fc789..c2ae61fec 100644 --- a/test/solidity/utils/TestBaseRandomConstants.sol +++ b/test/solidity/utils/TestBaseRandomConstants.sol @@ -9,5 +9,4 @@ abstract contract TestBaseRandomConstants { address internal constant USER_DIAMOND_OWNER = 0x5042255A3F3FD7727e419CeA387cAFDfad3C3aF8; address internal constant USER_LDA_DIAMOND_OWNER = address(0x123457); - address internal constant USER_LDA_PAUSER = address(0x123458); } From a501d3ac8a8c906d998d85109da3630341e2ebdf Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 13:05:51 +0200 Subject: [PATCH 143/220] Rename LDADiamond to LiFiDEXAggregatorDiamond in conventions.md and update related deployment scripts, tests, and utility files to reflect the new naming convention. --- conventions.md | 4 ++-- ...l => DeployLiFiDEXAggregatorDiamond.s.sol} | 13 +++++++++---- script/removeDuplicateEventsFromABI.ts | 2 -- ...amond.sol => LiFiDEXAggregatorDiamond.sol} | 4 ++-- test/solidity/Periphery/GasZipPeriphery.t.sol | 4 ++-- .../Periphery/LDA/BaseCoreRouteTest.t.sol | 9 ++++++--- ...t.sol => LiFiDEXAggregatorDiamondTest.sol} | 19 +++++++++++-------- test/solidity/utils/TestBase.sol | 8 ++++---- 8 files changed, 36 insertions(+), 27 deletions(-) rename script/deploy/facets/LDA/{DeployLDADiamond.s.sol => DeployLiFiDEXAggregatorDiamond.s.sol} (66%) rename src/Periphery/LDA/{LDADiamond.sol => LiFiDEXAggregatorDiamond.sol} (97%) rename test/solidity/Periphery/LDA/utils/{LDADiamondTest.sol => LiFiDEXAggregatorDiamondTest.sol} (89%) diff --git a/conventions.md b/conventions.md index 9c20f31e0..3e8b1150d 100644 --- a/conventions.md +++ b/conventions.md @@ -387,7 +387,7 @@ The LDA (LiFi DEX Aggregator) is a specialized component within the LI.FI ecosys #### Core Components -- **LDADiamond.sol**: Base EIP-2535 Diamond Proxy Contract for LDA +- **LiFiDEXAggregatorDiamond.sol**: Base EIP-2535 Diamond Proxy Contract for LDA - **CoreRouteFacet.sol**: Orchestrates route execution using direct function selector dispatch - **BaseRouteConstants.sol**: Shared constants across DEX facets - **PoolCallbackAuthenticated.sol**: Abstract contract providing pool callback authentication @@ -396,7 +396,7 @@ The LDA (LiFi DEX Aggregator) is a specialized component within the LI.FI ecosys ``` src/Periphery/LDA/ -├── LDADiamond.sol # LDA Diamond proxy implementation +├── LiFiDEXAggregatorDiamond.sol # LiFiDEXAggregatorDiamond Diamond proxy implementation ├── BaseRouteConstants.sol # Common constants for DEX facets ├── PoolCallbackAuthenticated.sol # Callback authentication base ├── Facets/ # LDA-specific facets diff --git a/script/deploy/facets/LDA/DeployLDADiamond.s.sol b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol similarity index 66% rename from script/deploy/facets/LDA/DeployLDADiamond.s.sol rename to script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol index 7b2efc356..d3c909070 100644 --- a/script/deploy/facets/LDA/DeployLDADiamond.s.sol +++ b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol @@ -3,19 +3,24 @@ pragma solidity ^0.8.17; import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; -import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; +import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; contract DeployScript is DeployScriptBase { using stdJson for string; - constructor() DeployScriptBase("LDADiamond") {} + constructor() DeployScriptBase("LiFiDEXAggregatorDiamond") {} function run() public - returns (LDADiamond deployed, bytes memory constructorArgs) + returns ( + LiFiDEXAggregatorDiamond deployed, + bytes memory constructorArgs + ) { constructorArgs = getConstructorArgs(); - deployed = LDADiamond(deploy(type(LDADiamond).creationCode)); + deployed = LiFiDEXAggregatorDiamond( + deploy(type(LiFiDEXAggregatorDiamond).creationCode) + ); } function getConstructorArgs() internal override returns (bytes memory) { diff --git a/script/removeDuplicateEventsFromABI.ts b/script/removeDuplicateEventsFromABI.ts index 082b1fd33..acd08cd96 100644 --- a/script/removeDuplicateEventsFromABI.ts +++ b/script/removeDuplicateEventsFromABI.ts @@ -49,9 +49,7 @@ async function removeDuplicateEventsFromABI() { // Files that we know have duplicate events const filesToClean = [ 'out/OwnershipFacet.sol/OwnershipFacet.json', - 'out/LDAOwnershipFacet.sol/LDAOwnershipFacet.json', 'out/DiamondCutFacet.sol/DiamondCutFacet.json', - 'out/LDADiamondCutFacet.sol/LDADiamondCutFacet.json', ] for (const file of filesToClean) { diff --git a/src/Periphery/LDA/LDADiamond.sol b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol similarity index 97% rename from src/Periphery/LDA/LDADiamond.sol rename to src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol index 60d490213..bfd394a49 100644 --- a/src/Periphery/LDA/LDADiamond.sol +++ b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol @@ -5,11 +5,11 @@ import { LibDiamond } from "../../Libraries/LibDiamond.sol"; import { IDiamondCut } from "../../Interfaces/IDiamondCut.sol"; import { InvalidConfig } from "../../Errors/GenericErrors.sol"; -/// @title LDADiamond +/// @title LiFiDEXAggregatorDiamond /// @author LI.FI (https://li.fi) /// @notice Base EIP-2535 Diamond Proxy Contract for LDA (LiFi DEX Aggregator). /// @custom:version 1.0.0 -contract LDADiamond { +contract LiFiDEXAggregatorDiamond { constructor(address _contractOwner, address _diamondCutFacet) payable { if (_contractOwner == address(0) || _diamondCutFacet == address(0)) { revert InvalidConfig(); diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 0c65fb34a..07cec5d46 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,7 +10,7 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LDADiamondTest } from "./LDA/utils/LDADiamondTest.sol"; +import { LiFiDEXAggregatorDiamondTest } from "./LDA/utils/LiFiDEXAggregatorDiamondTest.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; @@ -61,7 +61,7 @@ contract GasZipPeripheryTest is TestBase { function setUp() public override { customBlockNumberForForking = 22566858; initTestBase(); - LDADiamondTest.setUp(); + LiFiDEXAggregatorDiamondTest.setUp(); // deploy contracts gasZipPeriphery = new TestGasZipPeriphery( diff --git a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol index 18d46f459..cf21e4508 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LDADiamondTest } from "./utils/LDADiamondTest.sol"; +import { LiFiDEXAggregatorDiamondTest } from "./utils/LiFiDEXAggregatorDiamondTest.sol"; /// @title BaseCoreRouteTest /// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. @@ -15,7 +15,10 @@ import { LDADiamondTest } from "./utils/LDADiamondTest.sol"; /// - Event expectations helpers /// - Overloads of `_executeAndVerifySwap` including revert path /// Concrete tests compose these helpers to succinctly define swap scenarios. -abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { +abstract contract BaseCoreRouteTest is + LiFiDEXAggregatorDiamondTest, + TestHelpers +{ using SafeERC20 for IERC20; // ==== Types ==== @@ -120,7 +123,7 @@ abstract contract BaseCoreRouteTest is LDADiamondTest, TestHelpers { /// @notice Deploys and attaches `CoreRouteFacet` to the diamond under test. /// @dev Invoked from `setUp` of child tests via inheritance chain. function setUp() public virtual override { - LDADiamondTest.setUp(); + LiFiDEXAggregatorDiamondTest.setUp(); _addCoreRouteFacet(); } diff --git a/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol b/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol similarity index 89% rename from test/solidity/Periphery/LDA/utils/LDADiamondTest.sol rename to test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol index e0a064020..247b37111 100644 --- a/test/solidity/Periphery/LDA/utils/LDADiamondTest.sol +++ b/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; +import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; @@ -10,12 +10,15 @@ import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; -/// @title LDADiamondTest +/// @title LiFiDEXAggregatorDiamondTest /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. /// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. -contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { +contract LiFiDEXAggregatorDiamondTest is + BaseDiamondTest, + TestBaseRandomConstants +{ /// @notice The diamond proxy under test. - LDADiamond internal ldaDiamond; + LiFiDEXAggregatorDiamond internal ldaDiamond; /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. @@ -28,12 +31,12 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { /// @return diamond The newly created diamond instance. function createLDADiamond( address _diamondOwner - ) internal returns (LDADiamond) { + ) internal returns (LiFiDEXAggregatorDiamond) { vm.startPrank(_diamondOwner); DiamondCutFacet diamondCut = new DiamondCutFacet(); DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); OwnershipFacet ownership = new OwnershipFacet(); - LDADiamond diamond = new LDADiamond( + LiFiDEXAggregatorDiamond diamond = new LiFiDEXAggregatorDiamond( _diamondOwner, address(diamondCut) ); @@ -55,7 +58,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { address diamondCutFacet = address(new DiamondCutFacet()); vm.expectRevert(InvalidConfig.selector); - new LDADiamond( + new LiFiDEXAggregatorDiamond( address(0), // Zero owner address diamondCutFacet ); @@ -63,7 +66,7 @@ contract LDADiamondTest is BaseDiamondTest, TestBaseRandomConstants { function testRevert_CannotDeployDiamondWithZeroDiamondCut() public { vm.expectRevert(InvalidConfig.selector); - new LDADiamond( + new LiFiDEXAggregatorDiamond( USER_DIAMOND_OWNER, address(0) // Zero diamondCut address ); diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 817a22424..839ad9b4b 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -15,7 +15,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LDADiamondTest } from "../Periphery/LDA/utils/LDADiamondTest.sol"; +import { LiFiDEXAggregatorDiamondTest } from "../Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol"; using stdJson for string; @@ -99,7 +99,7 @@ abstract contract TestBase is TestBaseRandomConstants, TestHelpers, DiamondTest, - LDADiamondTest, + LiFiDEXAggregatorDiamondTest, ILiFi { address internal _facetTestContractAddress; @@ -243,8 +243,8 @@ abstract contract TestBase is // deploy & configure diamond diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); - // deploy & configure ldaDiamond - LDADiamondTest.setUp(); + // deploy & configure LiFiDEXAggregatorDiamond + LiFiDEXAggregatorDiamondTest.setUp(); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From a210154eb8901719b0a78040baed4179624e9645 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 13:30:50 +0200 Subject: [PATCH 144/220] Add detailed documentation for token swap functions in ICurve and ICurveV2 interfaces --- src/Interfaces/ICurve.sol | 7 +++++++ src/Interfaces/ICurveV2.sol | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/Interfaces/ICurve.sol b/src/Interfaces/ICurve.sol index 99ba0dc06..1e9c5dad4 100644 --- a/src/Interfaces/ICurve.sol +++ b/src/Interfaces/ICurve.sol @@ -6,6 +6,13 @@ pragma solidity ^0.8.17; /// @notice Minimal Curve pool interface for exchange operations /// @custom:version 1.0.0 interface ICurve { + /// @notice Performs a token swap on a Curve pool. + /// @dev This function is a minimal interface for the `exchange` function found on various Curve pools. + /// It is marked `payable` to allow for swaps involving native tokens (e.g., ETH) in some pools. + /// @param i The index of the token to sell. + /// @param j The index of the token to buy. + /// @param dx The amount of the input token to sell. + /// @param min_dy The minimum amount of the output token to receive (slippage control). function exchange( int128 i, int128 j, diff --git a/src/Interfaces/ICurveV2.sol b/src/Interfaces/ICurveV2.sol index 779d7ff2e..ef891bbcc 100644 --- a/src/Interfaces/ICurveV2.sol +++ b/src/Interfaces/ICurveV2.sol @@ -6,6 +6,14 @@ pragma solidity ^0.8.17; /// @notice Minimal Curve V2 pool interface for exchange operations /// @custom:version 1.0.0 interface ICurveV2 { + /// @notice Performs a token swap on a Curve V2 pool. + /// @dev This function is used to swap tokens in a Curve V2 pool and specifies the recipient of the swapped tokens. + /// It is not `payable` and assumes that the input token has already been approved for transfer by the caller. + /// @param i The index of the token to sell. + /// @param j The index of the token to buy. + /// @param dx The amount of the input token to sell. + /// @param min_dy The minimum amount of the output token to receive (slippage control). + /// @param receiver The address to which the swapped tokens will be sent. function exchange( int128 i, int128 j, @@ -14,6 +22,11 @@ interface ICurveV2 { address receiver ) external; + /// @param i The index of the token to sell. + /// @param j The index of the token to buy. + /// @param dx The amount of the input token to sell. + /// @param min_dy The minimum amount of the output token to receive (slippage control). + /// @param receiver The address to which the swapped tokens will be sent. function exchange_received( int128 i, int128 j, From 34afee37dca555d8d14697551df95895a2ea4a66 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 13:32:25 +0200 Subject: [PATCH 145/220] add blank line --- src/Periphery/LDA/BaseRouteConstants.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Periphery/LDA/BaseRouteConstants.sol b/src/Periphery/LDA/BaseRouteConstants.sol index e0dc875eb..0b742c57d 100644 --- a/src/Periphery/LDA/BaseRouteConstants.sol +++ b/src/Periphery/LDA/BaseRouteConstants.sol @@ -9,6 +9,7 @@ pragma solidity ^0.8.17; abstract contract BaseRouteConstants { /// @dev Constant indicating swap direction from token0 to token1 uint8 internal constant DIRECTION_TOKEN0_TO_TOKEN1 = 1; + /// @dev A sentinel address (address(1)) used in the `from` parameter of a swap. /// It signals that the input tokens for the swap are already held by the /// receiving contract (e.g., from a previous swap in a multi-step route). From 6d5c2e4ea5aaa34cc6d15f63bbaa9b471376f040 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 13:36:25 +0200 Subject: [PATCH 146/220] Update LDA error handling conventions: rename error file to LiFiDEXAggregatorErrors.sol and adjust imports across multiple facets and tests accordingly. --- conventions.md | 4 ++-- src/Periphery/LDA/Facets/AlgebraFacet.sol | 2 +- src/Periphery/LDA/Facets/IzumiV3Facet.sol | 2 +- src/Periphery/LDA/Facets/UniV2StyleFacet.sol | 2 +- src/Periphery/LDA/Facets/UniV3StyleFacet.sol | 2 +- src/Periphery/LDA/Facets/VelodromeV2Facet.sol | 2 +- .../LDA/{Errors/Errors.sol => LiFiDEXAggregatorErrors.sol} | 3 --- test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol | 2 +- test/solidity/Periphery/LDA/Facets/AlgebraFacet.t.sol | 2 +- test/solidity/Periphery/LDA/Facets/PancakeV2.t.sol | 2 +- test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol | 2 +- 11 files changed, 11 insertions(+), 14 deletions(-) rename src/Periphery/LDA/{Errors/Errors.sol => LiFiDEXAggregatorErrors.sol} (58%) diff --git a/conventions.md b/conventions.md index 3e8b1150d..e93ca4c3d 100644 --- a/conventions.md +++ b/conventions.md @@ -154,7 +154,7 @@ We use Foundry as our primary development and testing framework. Foundry provide - **Error Handling:** - **Generic errors** must be defined in `src/Errors/GenericErrors.sol` - - LDA-specific errors should be defined in `src/Periphery/LDA/Errors/Errors.sol` + - LDA-specific errors should be defined in `src/Periphery/LDA/LiFiDEXAggregatorErrors.sol` - Use for common validation errors that apply across multiple contracts - When adding new generic errors, increment the version in `@custom:version` comment - Examples: `InvalidAmount()`, `InvalidCallData()`, `UnAuthorized()` @@ -464,7 +464,7 @@ When integrating a new DEX, follow this decision tree: #### Error Handling -- **LDA-specific errors**: Define in `src/Periphery/LDA/Errors/Errors.sol` +- **LDA-specific errors**: Define in `src/Periphery/LDA/LiFiDEXAggregatorErrors.sol` - **Generic errors**: Use existing errors from `src/Errors/GenericErrors.sol` ### LDA Testing Conventions diff --git a/src/Periphery/LDA/Facets/AlgebraFacet.sol b/src/Periphery/LDA/Facets/AlgebraFacet.sol index 86e8a40a1..b36f31ca4 100644 --- a/src/Periphery/LDA/Facets/AlgebraFacet.sol +++ b/src/Periphery/LDA/Facets/AlgebraFacet.sol @@ -8,7 +8,7 @@ import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticato import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; diff --git a/src/Periphery/LDA/Facets/IzumiV3Facet.sol b/src/Periphery/LDA/Facets/IzumiV3Facet.sol index a74694b46..6bfc20e0f 100644 --- a/src/Periphery/LDA/Facets/IzumiV3Facet.sol +++ b/src/Periphery/LDA/Facets/IzumiV3Facet.sol @@ -6,7 +6,7 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; diff --git a/src/Periphery/LDA/Facets/UniV2StyleFacet.sol b/src/Periphery/LDA/Facets/UniV2StyleFacet.sol index 1ab5403c0..ec7f13e09 100644 --- a/src/Periphery/LDA/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/LDA/Facets/UniV2StyleFacet.sol @@ -7,7 +7,7 @@ import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; -import { WrongPoolReserves } from "../Errors/Errors.sol"; +import { WrongPoolReserves } from "../LiFiDEXAggregatorErrors.sol"; /// @title UniV2StyleFacet /// @author LI.FI (https://li.fi) diff --git a/src/Periphery/LDA/Facets/UniV3StyleFacet.sol b/src/Periphery/LDA/Facets/UniV3StyleFacet.sol index ba932a730..7205e6e8e 100644 --- a/src/Periphery/LDA/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/LDA/Facets/UniV3StyleFacet.sol @@ -7,7 +7,7 @@ import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticato import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; diff --git a/src/Periphery/LDA/Facets/VelodromeV2Facet.sol b/src/Periphery/LDA/Facets/VelodromeV2Facet.sol index 45839ce05..e3dbcc130 100644 --- a/src/Periphery/LDA/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/LDA/Facets/VelodromeV2Facet.sol @@ -7,7 +7,7 @@ import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { IVelodromeV2Pool } from "lifi/Interfaces/IVelodromeV2Pool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; -import { WrongPoolReserves } from "../Errors/Errors.sol"; +import { WrongPoolReserves } from "../LiFiDEXAggregatorErrors.sol"; /// @title VelodromeV2Facet /// @author LI.FI (https://li.fi) diff --git a/src/Periphery/LDA/Errors/Errors.sol b/src/Periphery/LDA/LiFiDEXAggregatorErrors.sol similarity index 58% rename from src/Periphery/LDA/Errors/Errors.sol rename to src/Periphery/LDA/LiFiDEXAggregatorErrors.sol index 409873ec4..09c66fe7d 100644 --- a/src/Periphery/LDA/Errors/Errors.sol +++ b/src/Periphery/LDA/LiFiDEXAggregatorErrors.sol @@ -1,7 +1,4 @@ // SPDX-License-Identifier: LGPL-3.0-only -/// @title LDA Error Definitions -/// @author LI.FI (https://li.fi) -/// @notice Custom errors for LDA contracts /// @custom:version 1.0.0 pragma solidity ^0.8.17; diff --git a/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol b/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol index 9acb87378..1097e149b 100644 --- a/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol +++ b/test/solidity/Periphery/LDA/BaseDEXFacetWithCallback.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; import { BaseDEXFacetTest } from "./BaseDEXFacet.t.sol"; import { MockNoCallbackPool } from "../../utils/MockNoCallbackPool.sol"; diff --git a/test/solidity/Periphery/LDA/Facets/AlgebraFacet.t.sol b/test/solidity/Periphery/LDA/Facets/AlgebraFacet.t.sol index 4f33355e9..83e2f41f4 100644 --- a/test/solidity/Periphery/LDA/Facets/AlgebraFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/AlgebraFacet.t.sol @@ -8,7 +8,7 @@ import { IAlgebraFactory } from "lifi/Interfaces/IAlgebraFactory.sol"; import { IAlgebraQuoter } from "lifi/Interfaces/IAlgebraQuoter.sol"; import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; import { TestToken as ERC20 } from "../../../utils/TestToken.sol"; import { MockFeeOnTransferToken } from "../../../utils/MockTokenFeeOnTransfer.sol"; import { BaseDEXFacetWithCallbackTest } from "../BaseDEXFacetWithCallback.t.sol"; diff --git a/test/solidity/Periphery/LDA/Facets/PancakeV2.t.sol b/test/solidity/Periphery/LDA/Facets/PancakeV2.t.sol index 31c96fe57..1048b3f48 100644 --- a/test/solidity/Periphery/LDA/Facets/PancakeV2.t.sol +++ b/test/solidity/Periphery/LDA/Facets/PancakeV2.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IUniV2StylePool } from "lifi/Interfaces/IUniV2StylePool.sol"; import { BaseUniV2StyleDEXFacetTest } from "../BaseUniV2StyleDEXFacet.t.sol"; -import { WrongPoolReserves } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { WrongPoolReserves } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; /// @title PancakeV2FacetTest /// @notice Fork-based UniV2-style tests for PancakeV2 integration. diff --git a/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol b/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol index bbe5fed69..9d497136a 100644 --- a/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/VelodromeV2Facet.t.sol @@ -8,7 +8,7 @@ import { IVelodromeV2PoolFactory } from "lifi/Interfaces/IVelodromeV2PoolFactory import { IVelodromeV2Router } from "lifi/Interfaces/IVelodromeV2Router.sol"; import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { WrongPoolReserves } from "lifi/Periphery/LDA/Errors/Errors.sol"; +import { WrongPoolReserves } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; import { BaseDEXFacetTest } from "../BaseDEXFacet.t.sol"; /// @title VelodromeV2FacetTest From ee259cc4373a9fb341dd413d405bab42b07a8956 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 13:45:01 +0200 Subject: [PATCH 147/220] changed cur to stream --- src/Periphery/LDA/Facets/CoreRouteFacet.sol | 83 +++++++++++---------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/src/Periphery/LDA/Facets/CoreRouteFacet.sol b/src/Periphery/LDA/Facets/CoreRouteFacet.sol index 96d404307..180623a65 100644 --- a/src/Periphery/LDA/Facets/CoreRouteFacet.sol +++ b/src/Periphery/LDA/Facets/CoreRouteFacet.sol @@ -262,24 +262,24 @@ contract CoreRouteFacet is BaseRouteConstants, ReentrancyGuard { realAmountIn = declaredAmountIn; uint256 step = 0; - uint256 cur = LibPackedStream.createStream(route); + uint256 stream = LibPackedStream.createStream(route); // Iterate until the packed route stream is fully consumed. // `isNotEmpty()` returns true while there are unread bytes left in the stream. - while (cur.isNotEmpty()) { + while (stream.isNotEmpty()) { // Read the next command byte that specifies how to handle tokens in this step - uint8 routeCommand = cur.readUint8(); + uint8 routeCommand = stream.readUint8(); if (routeCommand == 1) { - uint256 used = _distributeSelfERC20(cur); + uint256 used = _distributeSelfERC20(stream); if (step == 0) realAmountIn = used; } else if (routeCommand == 2) { - _distributeUserERC20(cur, declaredAmountIn); + _distributeUserERC20(stream, declaredAmountIn); } else if (routeCommand == 3) { - uint256 usedNative = _distributeNative(cur); + uint256 usedNative = _distributeNative(stream); if (step == 0) realAmountIn = usedNative; } else if (routeCommand == 4) { - _dispatchSinglePoolSwap(cur); + _dispatchSinglePoolSwap(stream); } else if (routeCommand == 5) { - _applyPermit(tokenIn, cur); + _applyPermit(tokenIn, stream); } else { revert UnknownCommandCode(); } @@ -294,13 +294,13 @@ contract CoreRouteFacet is BaseRouteConstants, ReentrancyGuard { /// @notice Applies ERC20 permit for token approval /// @dev Reads permit parameters from the stream and calls permit on the token /// @param tokenIn The token to approve - /// @param cur The current position in the byte stream - function _applyPermit(address tokenIn, uint256 cur) private { - uint256 value = cur.readUint256(); - uint256 deadline = cur.readUint256(); - uint8 v = cur.readUint8(); - bytes32 r = cur.readBytes32(); - bytes32 s = cur.readBytes32(); + /// @param stream The byte stream to read from + function _applyPermit(address tokenIn, uint256 stream) private { + uint256 value = stream.readUint256(); + uint256 deadline = stream.readUint256(); + uint8 v = stream.readUint8(); + bytes32 r = stream.readBytes32(); + bytes32 s = stream.readBytes32(); IERC20Permit(tokenIn).safePermit( msg.sender, address(this), @@ -314,21 +314,28 @@ contract CoreRouteFacet is BaseRouteConstants, ReentrancyGuard { /// @notice Distributes native ETH held by this contract across legs and dispatches swaps /// @dev Assumes ETH is already present on the contract - /// @param cur The current position in the byte stream + /// @param stream The byte stream to read from /// @return total The total amount of ETH to process - function _distributeNative(uint256 cur) private returns (uint256 total) { + function _distributeNative( + uint256 stream + ) private returns (uint256 total) { total = address(this).balance; - _distributeAndSwap(cur, address(this), LibAsset.NULL_ADDRESS, total); + _distributeAndSwap( + stream, + address(this), + LibAsset.NULL_ADDRESS, + total + ); } /// @notice Distributes ERC20 tokens already on this contract /// @dev Includes protection against full balance draining - /// @param cur The current position in the byte stream + /// @param stream The byte stream to read from /// @return total The total amount of tokens to process function _distributeSelfERC20( - uint256 cur + uint256 stream ) private returns (uint256 total) { - address token = cur.readAddress(); + address token = stream.readAddress(); total = IERC20(token).balanceOf(address(this)); unchecked { // Prevent swaps with uselessly small amounts (like 1 wei) that could: @@ -337,22 +344,22 @@ contract CoreRouteFacet is BaseRouteConstants, ReentrancyGuard { // By subtracting 1 from any positive balance, we ensure a balance of 1 becomes a swap amount of 0 (effectively skipping the swap) if (total > 0) total -= 1; } - _distributeAndSwap(cur, address(this), token, total); + _distributeAndSwap(stream, address(this), token, total); } /// @notice Distributes ERC20 tokens from the caller - /// @param cur The current position in the byte stream + /// @param stream The byte stream to read from /// @param total The declared total to distribute from msg.sender - function _distributeUserERC20(uint256 cur, uint256 total) private { - address token = cur.readAddress(); - _distributeAndSwap(cur, msg.sender, token, total); + function _distributeUserERC20(uint256 stream, uint256 total) private { + address token = stream.readAddress(); + _distributeAndSwap(stream, msg.sender, token, total); } /// @notice Dispatches a single swap using tokens already in the pool - /// @param cur The current position in the byte stream - function _dispatchSinglePoolSwap(uint256 cur) private { - address token = cur.readAddress(); - _dispatchSwap(cur, FUNDS_IN_RECEIVER, token, 0); + /// @param stream The byte stream to read from + function _dispatchSinglePoolSwap(uint256 stream) private { + address token = stream.readAddress(); + _dispatchSwap(stream, FUNDS_IN_RECEIVER, token, 0); } /// @notice Distributes tokens across multiple pools based on share ratios @@ -362,23 +369,23 @@ contract CoreRouteFacet is BaseRouteConstants, ReentrancyGuard { /// - The last leg gets all remaining tokens to handle rounding errors /// - Example: 60/40 split would use shares [39321, 26214] since: /// 39321/65535 ≈ 0.6 and 26214/65535 ≈ 0.4 - /// @param cur The current position in the byte stream + /// @param stream The byte stream to read from /// @param from The source address for tokens /// @param tokenIn The token being distributed /// @param total The total amount to distribute across all legs function _distributeAndSwap( - uint256 cur, + uint256 stream, address from, address tokenIn, uint256 total ) private { // Read number of swap legs from the stream - uint8 n = cur.readUint8(); + uint8 n = stream.readUint8(); unchecked { uint256 remaining = total; for (uint256 i = 0; i < n; ++i) { // Read the proportional share for this leg (0-65535 scale) - uint16 share = cur.readUint16(); + uint16 share = stream.readUint16(); // Calculate amount for this leg: // - For intermediate legs: proportional amount based on share @@ -394,24 +401,24 @@ contract CoreRouteFacet is BaseRouteConstants, ReentrancyGuard { remaining -= legAmount; // Execute the swap for this leg with calculated amount - _dispatchSwap(cur, from, tokenIn, legAmount); + _dispatchSwap(stream, from, tokenIn, legAmount); } } } /// @notice Dispatches a swap call to the appropriate DEX facet - /// @param cur The current position in the byte stream + /// @param stream The byte stream to read from /// @param from The source address for tokens /// @param tokenIn The input token address /// @param amountIn The amount of tokens to swap function _dispatchSwap( - uint256 cur, + uint256 stream, address from, address tokenIn, uint256 amountIn ) private { // Read [selector | payload] blob for the specific DEX facet - bytes memory data = cur.readBytesWithLength(); + bytes memory data = stream.readBytesWithLength(); // Extract function selector (first 4 bytes of data) bytes4 selector = _readSelector(data); From d822cb47b9168a2b46d39b83ebd0bb495a0b1c34 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 14:08:14 +0200 Subject: [PATCH 148/220] Refactor NativeWrapperFacet to optimize destination address retrieval using assembly and remove redundant zero address checks in tests. --- .../LDA/Facets/NativeWrapperFacet.sol | 13 ++-- .../LDA/Facets/NativeWrapperFacet.t.sol | 63 ------------------- 2 files changed, 6 insertions(+), 70 deletions(-) diff --git a/src/Periphery/LDA/Facets/NativeWrapperFacet.sol b/src/Periphery/LDA/Facets/NativeWrapperFacet.sol index 082dc71bd..8dce37bdb 100644 --- a/src/Periphery/LDA/Facets/NativeWrapperFacet.sol +++ b/src/Periphery/LDA/Facets/NativeWrapperFacet.sol @@ -28,12 +28,11 @@ contract NativeWrapperFacet is BaseRouteConstants { address tokenIn, uint256 amountIn ) external { - uint256 stream = LibPackedStream.createStream(swapData); - - address destinationAddress = stream.readAddress(); - - if (destinationAddress == address(0)) { - revert InvalidCallData(); + address destinationAddress; + assembly { + // swapData layout: [length (32 bytes)][data...] + // We want the first 20 bytes of data, right-shifted to get address + destinationAddress := shr(96, mload(add(swapData, 32))) } if (from == msg.sender) { @@ -69,7 +68,7 @@ contract NativeWrapperFacet is BaseRouteConstants { address wrappedNative = stream.readAddress(); address destinationAddress = stream.readAddress(); - if (wrappedNative == address(0) || destinationAddress == address(0)) { + if (wrappedNative == address(0)) { revert InvalidCallData(); } diff --git a/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol index d1d95674e..61d60d6b0 100644 --- a/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol @@ -219,69 +219,6 @@ contract NativeWrapperFacetTest is BaseCoreRouteTest { vm.stopPrank(); } - /// @notice Tests that unwrapNative reverts with zero destination address - function testRevert_UnwrapNative_ZeroDestinationAddress() public { - uint256 amountIn = 1 ether; - - // Fund user with WETH - deal(address(weth), USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildUnwrapSwapData( - UnwrapParams({ - destinationAddress: address(0) // Invalid destination - }) - ); - - SwapTestParams memory params = SwapTestParams({ - tokenIn: address(weth), - tokenOut: address(0), - amountIn: amountIn, - minOut: 0, - sender: USER_SENDER, - destinationAddress: USER_RECEIVER, - commandType: CommandType.DistributeUserERC20 - }); - - bytes memory route = _buildBaseRoute(params, swapData); - _executeAndVerifySwap(params, route, InvalidCallData.selector); - - vm.stopPrank(); - } - - /// @notice Tests that wrapNative reverts with zero destination address - function testRevert_WrapNative_ZeroDestinationAddress() public { - uint256 amountIn = 1 ether; - - // Fund user with ETH (not aggregator, since this is DistributeNative) - vm.deal(USER_SENDER, amountIn); - - vm.startPrank(USER_SENDER); - - bytes memory swapData = _buildWrapSwapData( - WrapParams({ - wrappedNative: address(weth), - destinationAddress: address(0) // Invalid destination - }) - ); - - SwapTestParams memory params = SwapTestParams({ - tokenIn: address(0), - tokenOut: address(weth), - amountIn: amountIn, - minOut: 0, - sender: USER_SENDER, - destinationAddress: USER_RECEIVER, - commandType: CommandType.DistributeNative - }); - - bytes memory route = _buildBaseRoute(params, swapData); - _executeAndVerifySwap(params, route, InvalidCallData.selector); - - vm.stopPrank(); - } - /// @notice Tests that wrapNative reverts with zero wrapped native address function testRevert_WrapNative_ZeroWrappedNative() public { uint256 amountIn = 1 ether; From cc4d5b59197813dc7281422c87686bee097373a5 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 14:52:25 +0200 Subject: [PATCH 149/220] Refactor LiFiDEXAggregatorDiamond to inherit from LiFiDiamond --- .../LDA/LiFiDEXAggregatorDiamond.sol | 68 ++----------------- .../utils/LiFiDEXAggregatorDiamondTest.sol | 8 --- 2 files changed, 7 insertions(+), 69 deletions(-) diff --git a/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol index bfd394a49..4991b425c 100644 --- a/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol +++ b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol @@ -1,74 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LibDiamond } from "../../Libraries/LibDiamond.sol"; -import { IDiamondCut } from "../../Interfaces/IDiamondCut.sol"; +import { LiFiDiamond } from "../../LiFiDiamond.sol"; import { InvalidConfig } from "../../Errors/GenericErrors.sol"; /// @title LiFiDEXAggregatorDiamond /// @author LI.FI (https://li.fi) /// @notice Base EIP-2535 Diamond Proxy Contract for LDA (LiFi DEX Aggregator). /// @custom:version 1.0.0 -contract LiFiDEXAggregatorDiamond { - constructor(address _contractOwner, address _diamondCutFacet) payable { - if (_contractOwner == address(0) || _diamondCutFacet == address(0)) { +contract LiFiDEXAggregatorDiamond is LiFiDiamond { + constructor( + address _contractOwner, + address _diamondCutFacet + ) LiFiDiamond(_contractOwner, _diamondCutFacet) { + if (_contractOwner == address(0)) { revert InvalidConfig(); } - LibDiamond.setContractOwner(_contractOwner); - - // Add the diamondCut external function from the diamondCutFacet - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = IDiamondCut.diamondCut.selector; - cut[0] = LibDiamond.FacetCut({ - facetAddress: _diamondCutFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }); - LibDiamond.diamondCut(cut, address(0), ""); } - - // Find facet for function that is called and execute the - // function if a facet is found and return any value. - // solhint-disable-next-line no-complex-fallback - fallback() external payable { - LibDiamond.DiamondStorage storage ds; - bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; - - // get diamond storage - // solhint-disable-next-line no-inline-assembly - assembly { - ds.slot := position - } - - // get facet from function selector - address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; - - if (facet == address(0)) { - revert LibDiamond.FunctionDoesNotExist(); - } - - // Execute external function from facet using delegatecall and return any value. - // solhint-disable-next-line no-inline-assembly - assembly { - // copy function selector and any arguments - calldatacopy(0, 0, calldatasize()) - // execute function call using the facet - let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) - // get any return value - returndatacopy(0, 0, returndatasize()) - // return any return value or error back to the caller - switch result - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } - - // Able to receive ether - // solhint-disable-next-line no-empty-blocks - receive() external payable {} } diff --git a/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol b/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol index 247b37111..0ee582819 100644 --- a/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol +++ b/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol @@ -64,14 +64,6 @@ contract LiFiDEXAggregatorDiamondTest is ); } - function testRevert_CannotDeployDiamondWithZeroDiamondCut() public { - vm.expectRevert(InvalidConfig.selector); - new LiFiDEXAggregatorDiamond( - USER_DIAMOND_OWNER, - address(0) // Zero diamondCut address - ); - } - function testRevert_CannotCallNonExistentFunction() public { // Create arbitrary calldata with non-existent selector bytes memory nonExistentCalldata = abi.encodeWithSelector( From fdb278673a936a2d6d1e36c71f2fb494cf804861 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 14:56:14 +0200 Subject: [PATCH 150/220] Rename PoolCallbackAuthenticated to PoolCallbackAuthenticator --- conventions.md | 6 +++--- src/Periphery/LDA/Facets/AlgebraFacet.sol | 4 ++-- src/Periphery/LDA/Facets/IzumiV3Facet.sol | 4 ++-- src/Periphery/LDA/Facets/UniV3StyleFacet.sol | 4 ++-- ...lbackAuthenticated.sol => PoolCallbackAuthenticator.sol} | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) rename src/Periphery/LDA/{PoolCallbackAuthenticated.sol => PoolCallbackAuthenticator.sol} (87%) diff --git a/conventions.md b/conventions.md index e93ca4c3d..e6f98402e 100644 --- a/conventions.md +++ b/conventions.md @@ -390,7 +390,7 @@ The LDA (LiFi DEX Aggregator) is a specialized component within the LI.FI ecosys - **LiFiDEXAggregatorDiamond.sol**: Base EIP-2535 Diamond Proxy Contract for LDA - **CoreRouteFacet.sol**: Orchestrates route execution using direct function selector dispatch - **BaseRouteConstants.sol**: Shared constants across DEX facets -- **PoolCallbackAuthenticated.sol**: Abstract contract providing pool callback authentication +- **PoolCallbackAuthenticator.sol**: Abstract contract providing pool callback authentication #### Location Structure @@ -398,7 +398,7 @@ The LDA (LiFi DEX Aggregator) is a specialized component within the LI.FI ecosys src/Periphery/LDA/ ├── LiFiDEXAggregatorDiamond.sol # LiFiDEXAggregatorDiamond Diamond proxy implementation ├── BaseRouteConstants.sol # Common constants for DEX facets -├── PoolCallbackAuthenticated.sol # Callback authentication base +├── PoolCallbackAuthenticator.sol # Callback authentication base ├── Facets/ # LDA-specific facets │ ├── CoreRouteFacet.sol # Route orchestration │ ├── UniV3StyleFacet.sol # UniV3-style DEX integrations @@ -439,7 +439,7 @@ When integrating a new DEX, follow this decision tree: #### Required Inheritance - **BaseRouteConstants**: For common constants (`DIRECTION_TOKEN0_TO_TOKEN1`, `FUNDS_IN_RECEIVER`) -- **PoolCallbackAuthenticated**: For facets requiring callback verification +- **PoolCallbackAuthenticator**: For facets requiring callback verification - **No ReentrancyGuard**: CoreRouteFacet handles reentrancy protection #### Function Patterns diff --git a/src/Periphery/LDA/Facets/AlgebraFacet.sol b/src/Periphery/LDA/Facets/AlgebraFacet.sol index b36f31ca4..7e06b112a 100644 --- a/src/Periphery/LDA/Facets/AlgebraFacet.sol +++ b/src/Periphery/LDA/Facets/AlgebraFacet.sol @@ -9,14 +9,14 @@ import { LibUniV3Logic } from "lifi/Libraries/LibUniV3Logic.sol"; import { IAlgebraPool } from "lifi/Interfaces/IAlgebraPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; +import { PoolCallbackAuthenticator } from "lifi/Periphery/LDA/PoolCallbackAuthenticator.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title AlgebraFacet /// @author LI.FI (https://li.fi) /// @notice Handles Algebra swaps with callback management /// @custom:version 1.0.0 -contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticated { +contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticator { using LibPackedStream for uint256; using SafeERC20 for IERC20; diff --git a/src/Periphery/LDA/Facets/IzumiV3Facet.sol b/src/Periphery/LDA/Facets/IzumiV3Facet.sol index 6bfc20e0f..5c831e455 100644 --- a/src/Periphery/LDA/Facets/IzumiV3Facet.sol +++ b/src/Periphery/LDA/Facets/IzumiV3Facet.sol @@ -7,14 +7,14 @@ import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticato import { IiZiSwapPool } from "lifi/Interfaces/IiZiSwapPool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; +import { PoolCallbackAuthenticator } from "lifi/Periphery/LDA/PoolCallbackAuthenticator.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title IzumiV3Facet /// @author LI.FI (https://li.fi) /// @notice Handles IzumiV3 swaps with callback management /// @custom:version 1.0.0 -contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticated { +contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticator { using LibPackedStream for uint256; using LibCallbackAuthenticator for *; diff --git a/src/Periphery/LDA/Facets/UniV3StyleFacet.sol b/src/Periphery/LDA/Facets/UniV3StyleFacet.sol index 7205e6e8e..0107bd401 100644 --- a/src/Periphery/LDA/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/LDA/Facets/UniV3StyleFacet.sol @@ -8,14 +8,14 @@ import { LibPackedStream } from "lifi/Libraries/LibPackedStream.sol"; import { IUniV3StylePool } from "lifi/Interfaces/IUniV3StylePool.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; import { SwapCallbackNotExecuted } from "lifi/Periphery/LDA/LiFiDEXAggregatorErrors.sol"; -import { PoolCallbackAuthenticated } from "lifi/Periphery/LDA/PoolCallbackAuthenticated.sol"; +import { PoolCallbackAuthenticator } from "lifi/Periphery/LDA/PoolCallbackAuthenticator.sol"; import { BaseRouteConstants } from "../BaseRouteConstants.sol"; /// @title UniV3StyleFacet /// @author LI.FI (https://li.fi) /// @notice Handles Uniswap V3 style swaps with callback verification /// @custom:version 1.0.0 -contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticated { +contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticator { using LibCallbackAuthenticator for *; using LibPackedStream for uint256; diff --git a/src/Periphery/LDA/PoolCallbackAuthenticated.sol b/src/Periphery/LDA/PoolCallbackAuthenticator.sol similarity index 87% rename from src/Periphery/LDA/PoolCallbackAuthenticated.sol rename to src/Periphery/LDA/PoolCallbackAuthenticator.sol index 707f8f2ad..49c11f8d5 100644 --- a/src/Periphery/LDA/PoolCallbackAuthenticated.sol +++ b/src/Periphery/LDA/PoolCallbackAuthenticator.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.17; import { LibCallbackAuthenticator } from "lifi/Libraries/LibCallbackAuthenticator.sol"; -/// @title PoolCallbackAuthenticated +/// @title PoolCallbackAuthenticator /// @author LI.FI (https://li.fi) /// @notice Abstract contract providing pool callback authentication functionality /// @custom:version 1.0.0 -abstract contract PoolCallbackAuthenticated { +abstract contract PoolCallbackAuthenticator { using LibCallbackAuthenticator for *; /// @dev Ensures callback is from expected pool and cleans up after callback From 86fdc512e0cd58f47d6a03a043d43d1e4c0e2395 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 2 Sep 2025 22:59:51 +0200 Subject: [PATCH 151/220] Refactor test files to improve structure and consistency: rename BaseCoreRouteTest to BaseCoreRoute, update imports across multiple test files, and enhance setup functions for better clarity --- conventions.md | 2 +- test/solidity/Facets/AcrossFacetPacked.t.sol | 5 +- .../solidity/Facets/AcrossFacetPackedV3.t.sol | 4 +- .../Facets/CBridge/CBridgeFacetPacked.t.sol | 4 +- .../Facets/CBridge/CBridgeRefund.t.sol | 3 +- .../Facets/CalldataVerificationFacet.t.sol | 2 +- test/solidity/Facets/DexManagerFacet.t.sol | 7 +- .../EmergencyPauseFacet.fork.t.sol | 125 +++++++++++------- .../EmergencyPauseFacet.local.t.sol | 14 +- test/solidity/Facets/OwnershipFacet.t.sol | 7 - test/solidity/Periphery/GasZipPeriphery.t.sol | 2 +- ...oreRouteTest.t.sol => BaseCoreRoute.t.sol} | 2 +- .../solidity/Periphery/LDA/BaseDEXFacet.t.sol | 2 +- .../Periphery/LDA/Facets/CoreRouteFacet.t.sol | 2 +- .../LDA/Facets/NativeWrapperFacet.t.sol | 2 +- .../LDA/LiFiDEXAggregatorDiamond.t.sol | 110 +++++++++++++++ .../utils/LiFiDEXAggregatorDiamondTest.sol | 99 -------------- .../LidoWrapper/LidoWrapperSON.t.sol | 1 + .../LidoWrapper/LidoWrapperUNI.t.sol | 1 + test/solidity/Periphery/Permit2Proxy.t.sol | 2 +- test/solidity/utils/BaseDiamondTest.sol | 3 +- test/solidity/utils/DiamondTest.sol | 49 ++++--- test/solidity/utils/TestBase.sol | 9 +- 23 files changed, 246 insertions(+), 211 deletions(-) rename test/solidity/Periphery/LDA/{BaseCoreRouteTest.t.sol => BaseCoreRoute.t.sol} (99%) create mode 100644 test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol delete mode 100644 test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol diff --git a/conventions.md b/conventions.md index e6f98402e..9062aa3f9 100644 --- a/conventions.md +++ b/conventions.md @@ -473,7 +473,7 @@ When integrating a new DEX, follow this decision tree: ``` test/solidity/Periphery/LDA/ -├── BaseCoreRouteTest.t.sol # Base route testing functionality +├── BaseCoreRoute.t.sol # Base route testing functionality ├── BaseDEXFacet.t.sol # Base for custom DEX tests ├── BaseDEXFacetWithCallback.t.sol # Base for callback-enabled DEX tests ├── BaseUniV3StyleDEXFacet.t.sol # Base for UniV3-style DEX tests diff --git a/test/solidity/Facets/AcrossFacetPacked.t.sol b/test/solidity/Facets/AcrossFacetPacked.t.sol index 003827880..9ffdbd2c6 100644 --- a/test/solidity/Facets/AcrossFacetPacked.t.sol +++ b/test/solidity/Facets/AcrossFacetPacked.t.sol @@ -1,16 +1,15 @@ // // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { AcrossFacet } from "lifi/Facets/AcrossFacet.sol"; import { AcrossFacetPacked } from "lifi/Facets/AcrossFacetPacked.sol"; import { IAcrossSpokePool } from "lifi/Interfaces/IAcrossSpokePool.sol"; import { LibAsset, IERC20 } from "lifi/Libraries/LibAsset.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { UnAuthorized } from "lifi/Errors/GenericErrors.sol"; import { TestBase } from "../utils/TestBase.sol"; import { MockFailingContract } from "../utils/MockFailingContract.sol"; -import { UnAuthorized } from "src/Errors/GenericErrors.sol"; - contract TestClaimContract { using SafeERC20 for IERC20; diff --git a/test/solidity/Facets/AcrossFacetPackedV3.t.sol b/test/solidity/Facets/AcrossFacetPackedV3.t.sol index 93ff569f9..2bec15070 100644 --- a/test/solidity/Facets/AcrossFacetPackedV3.t.sol +++ b/test/solidity/Facets/AcrossFacetPackedV3.t.sol @@ -1,14 +1,14 @@ // // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { AcrossFacetV3 } from "lifi/Facets/AcrossFacetV3.sol"; import { AcrossFacetPackedV3 } from "lifi/Facets/AcrossFacetPackedV3.sol"; import { IAcrossSpokePool } from "lifi/Interfaces/IAcrossSpokePool.sol"; import { LibAsset, IERC20 } from "lifi/Libraries/LibAsset.sol"; import { LibUtil } from "lifi/Libraries/LibUtil.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { UnAuthorized } from "lifi/Errors/GenericErrors.sol"; import { TestBase } from "../utils/TestBase.sol"; -import { UnAuthorized } from "src/Errors/GenericErrors.sol"; contract TestClaimContract { using SafeERC20 for IERC20; diff --git a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol index f6a1e434f..3bbdc503e 100644 --- a/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeFacetPacked.t.sol @@ -1,14 +1,14 @@ // // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { ERC20 } from "solmate/tokens/ERC20.sol"; import { ICBridge } from "lifi/Interfaces/ICBridge.sol"; import { CBridgeFacet } from "lifi/Facets/CBridgeFacet.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; import { CBridgeFacetPacked } from "lifi/Facets/CBridgeFacetPacked.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; +import { ContractCallNotAllowed, ExternalCallFailed, UnAuthorized } from "lifi/Errors/GenericErrors.sol"; import { TestBase } from "../../utils/TestBase.sol"; import { MockLiquidityBridge } from "../../utils/MockLiquidityBridge.sol"; -import { ContractCallNotAllowed, ExternalCallFailed, UnAuthorized } from "lifi/Errors/GenericErrors.sol"; contract CBridgeFacetPackedTest is TestBase { address internal constant CBRIDGE_ROUTER = diff --git a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol index 227bb1854..5512dbf4e 100644 --- a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol @@ -30,7 +30,6 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { bytes internal validCalldata; - LiFiDiamond internal diamond; WithdrawFacet internal withdrawFacet; ///@notice Init calldata for extra call. @@ -83,7 +82,7 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { /// @notice Setup contracts for test. /// @dev It adds selector of new function(executeCallAndWithdraw). /// And initialize calldata for extra call. - function setUp() public { + function setUp() public override { fork(); diamond = LiFiDiamond(payable(LIFI_ADDRESS)); diff --git a/test/solidity/Facets/CalldataVerificationFacet.t.sol b/test/solidity/Facets/CalldataVerificationFacet.t.sol index 93c86552a..a81b003ff 100644 --- a/test/solidity/Facets/CalldataVerificationFacet.t.sol +++ b/test/solidity/Facets/CalldataVerificationFacet.t.sol @@ -58,7 +58,7 @@ contract CalldataVerificationFacetTest is TestBase { ); } - function test_DeploysWithoutErrors() public { + function test_DeploysWithoutErrors() public virtual override { calldataVerificationFacet = new CalldataVerificationFacet(); } diff --git a/test/solidity/Facets/DexManagerFacet.t.sol b/test/solidity/Facets/DexManagerFacet.t.sol index 405755beb..14e034cb4 100644 --- a/test/solidity/Facets/DexManagerFacet.t.sol +++ b/test/solidity/Facets/DexManagerFacet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DSTest } from "ds-test/test.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; +import { DiamondTest } from "../utils/DiamondTest.sol"; import { DexManagerFacet } from "lifi/Facets/DexManagerFacet.sol"; import { AccessManagerFacet } from "lifi/Facets/AccessManagerFacet.sol"; import { InvalidContract, CannotAuthoriseSelf, UnAuthorized } from "lifi/Errors/GenericErrors.sol"; @@ -10,11 +10,8 @@ import { InvalidContract, CannotAuthoriseSelf, UnAuthorized } from "lifi/Errors/ contract Foo {} contract DexManagerFacetTest is DSTest, DiamondTest { - address internal constant USER_PAUSER = address(0xdeadbeef); - address internal constant USER_DIAMOND_OWNER = address(0x123456); address internal constant NOT_DIAMOND_OWNER = address(0xabc123456); - LiFiDiamond internal diamond; DexManagerFacet internal dexMgr; AccessManagerFacet internal accessMgr; Foo internal c1; @@ -28,7 +25,7 @@ contract DexManagerFacetTest is DSTest, DiamondTest { bool indexed approved ); - function setUp() public { + function setUp() public override { diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); dexMgr = new DexManagerFacet(); c1 = new Foo(); diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol index 7271948ee..cdb093b70 100644 --- a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol @@ -37,7 +37,7 @@ contract EmergencyPauseFacetPRODTest is TestBase { 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE; address internal constant USER_DIAMOND_OWNER_MAINNET = 0x37347dD595C49212C5FC2D95EA10d1085896f51E; - TestEmergencyPauseFacet internal emergencyPauseFacet; + TestEmergencyPauseFacet internal emergencyPauseFacetTest; address[] internal blacklist = new address[](0); function setUp() public override { @@ -47,17 +47,17 @@ contract EmergencyPauseFacetPRODTest is TestBase { initTestBase(); // deploy EmergencyPauseFacet - emergencyPauseFacet = new TestEmergencyPauseFacet(USER_PAUSER); + emergencyPauseFacetTest = new TestEmergencyPauseFacet(USER_PAUSER); // prepare diamondCut bytes4[] memory functionSelectors = new bytes4[](3); - functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; - functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; - functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; + functionSelectors[0] = emergencyPauseFacetTest.removeFacet.selector; + functionSelectors[1] = emergencyPauseFacetTest.pauseDiamond.selector; + functionSelectors[2] = emergencyPauseFacetTest.unpauseDiamond.selector; cut.push( LibDiamond.FacetCut({ - facetAddress: address(emergencyPauseFacet), + facetAddress: address(emergencyPauseFacetTest), action: LibDiamond.FacetCutAction.Add, functionSelectors: functionSelectors }) @@ -72,13 +72,13 @@ contract EmergencyPauseFacetPRODTest is TestBase { ); // store diamond in local TestEmergencyPauseFacet variable - emergencyPauseFacet = TestEmergencyPauseFacet( + emergencyPauseFacetTest = TestEmergencyPauseFacet( payable(address(ADDRESS_DIAMOND_MAINNET)) ); // set facet address in TestBase setFacetAddressInTestBase( - address(emergencyPauseFacet), + address(emergencyPauseFacetTest), "EmergencyPauseFacet" ); @@ -87,44 +87,56 @@ contract EmergencyPauseFacetPRODTest is TestBase { function test_PauserWalletCanPauseDiamond() public { vm.startPrank(USER_PAUSER); - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + vm.expectEmit( + true, + true, + true, + true, + address(emergencyPauseFacetTest) + ); emit EmergencyPaused(USER_PAUSER); // pause the contract - emergencyPauseFacet.pauseDiamond(); + emergencyPauseFacetTest.pauseDiamond(); // try to get a list of all registered facets via DiamondLoupe vm.expectRevert(DiamondIsPaused.selector); - DiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + DiamondLoupeFacet(address(emergencyPauseFacetTest)).facets(); } function test_DiamondOwnerCanPauseDiamond() public { vm.startPrank(USER_DIAMOND_OWNER_MAINNET); - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + vm.expectEmit( + true, + true, + true, + true, + address(emergencyPauseFacetTest) + ); emit EmergencyPaused(USER_DIAMOND_OWNER_MAINNET); // pause the contract - emergencyPauseFacet.pauseDiamond(); + emergencyPauseFacetTest.pauseDiamond(); // try to get a list of all registered facets via DiamondLoupe vm.expectRevert(DiamondIsPaused.selector); - DiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + DiamondLoupeFacet(address(emergencyPauseFacetTest)).facets(); } function test_UnauthorizedWalletCannotPauseDiamond() public { vm.startPrank(USER_SENDER); vm.expectRevert(UnAuthorized.selector); // pause the contract - emergencyPauseFacet.pauseDiamond(); + emergencyPauseFacetTest.pauseDiamond(); vm.startPrank(USER_RECEIVER); vm.expectRevert(UnAuthorized.selector); // pause the contract - emergencyPauseFacet.pauseDiamond(); + emergencyPauseFacetTest.pauseDiamond(); } function test_DiamondOwnerCanUnpauseDiamond() public { IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); // pause diamond first @@ -133,14 +145,20 @@ contract EmergencyPauseFacetPRODTest is TestBase { // unpause diamond as owner vm.startPrank(USER_DIAMOND_OWNER_MAINNET); - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + vm.expectEmit( + true, + true, + true, + true, + address(emergencyPauseFacetTest) + ); emit EmergencyUnpaused(USER_DIAMOND_OWNER_MAINNET); - emergencyPauseFacet.unpauseDiamond(blacklist); + emergencyPauseFacetTest.unpauseDiamond(blacklist); // make sure diamond works normal again and has all facets reinstated IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); assertTrue(initialFacets.length == finalFacets.length); @@ -153,26 +171,27 @@ contract EmergencyPauseFacetPRODTest is TestBase { // try to pause the diamond with various wallets vm.startPrank(USER_PAUSER); vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacet.unpauseDiamond(blacklist); + emergencyPauseFacetTest.unpauseDiamond(blacklist); vm.startPrank(USER_RECEIVER); vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacet.unpauseDiamond(blacklist); + emergencyPauseFacetTest.unpauseDiamond(blacklist); // make sure diamond is still paused vm.expectRevert(DiamondIsPaused.selector); - DiamondLoupeFacet(address(emergencyPauseFacet)).facets(); + DiamondLoupeFacet(address(emergencyPauseFacetTest)).facets(); } function test_DiamondOwnerCanRemoveFacet() public { // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); // get PeripheryRegistryFacet address - address facetAddress = DiamondLoupeFacet(address(emergencyPauseFacet)) - .facetAddress( + address facetAddress = DiamondLoupeFacet( + address(emergencyPauseFacetTest) + ).facetAddress( PeripheryRegistryFacet(address(emergencyPauseFacet)) .registerPeripheryContract .selector @@ -181,21 +200,27 @@ contract EmergencyPauseFacetPRODTest is TestBase { // remove facet vm.startPrank(USER_DIAMOND_OWNER_MAINNET); - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + vm.expectEmit( + true, + true, + true, + true, + address(emergencyPauseFacetTest) + ); emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER_MAINNET); - emergencyPauseFacet.removeFacet(facetAddress); + emergencyPauseFacetTest.removeFacet(facetAddress); // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); // ensure that one facet less is registered now assertTrue(initialFacets.length == finalFacets.length + 1); // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress assertTrue( - DiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( + DiamondLoupeFacet(address(emergencyPauseFacetTest)).facetAddress( PeripheryRegistryFacet(address(emergencyPauseFacet)) .registerPeripheryContract .selector @@ -208,13 +233,14 @@ contract EmergencyPauseFacetPRODTest is TestBase { function test_PauserWalletCanRemoveFacet() public { // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); // get PeripheryRegistryFacet address - address facetAddress = DiamondLoupeFacet(address(emergencyPauseFacet)) - .facetAddress( - PeripheryRegistryFacet(address(emergencyPauseFacet)) + address facetAddress = DiamondLoupeFacet( + address(emergencyPauseFacetTest) + ).facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacetTest)) .registerPeripheryContract .selector ); @@ -222,22 +248,28 @@ contract EmergencyPauseFacetPRODTest is TestBase { // remove facet vm.startPrank(USER_PAUSER); - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); + vm.expectEmit( + true, + true, + true, + true, + address(emergencyPauseFacetTest) + ); emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); - emergencyPauseFacet.removeFacet(facetAddress); + emergencyPauseFacetTest.removeFacet(facetAddress); // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); // ensure that one facet less is registered now assertTrue(initialFacets.length == finalFacets.length + 1); // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress assertTrue( - DiamondLoupeFacet(address(emergencyPauseFacet)).facetAddress( - PeripheryRegistryFacet(address(emergencyPauseFacet)) + DiamondLoupeFacet(address(emergencyPauseFacetTest)).facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacetTest)) .registerPeripheryContract .selector ) == address(0) @@ -249,13 +281,14 @@ contract EmergencyPauseFacetPRODTest is TestBase { function test_UnauthorizedWalletCannotRemoveFacet() public { // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); // get PeripheryRegistryFacet address - address facetAddress = DiamondLoupeFacet(address(emergencyPauseFacet)) - .facetAddress( - PeripheryRegistryFacet(address(emergencyPauseFacet)) + address facetAddress = DiamondLoupeFacet( + address(emergencyPauseFacetTest) + ).facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacetTest)) .registerPeripheryContract .selector ); @@ -263,15 +296,15 @@ contract EmergencyPauseFacetPRODTest is TestBase { // try to remove facet vm.startPrank(USER_SENDER); vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacet.removeFacet(facetAddress); + emergencyPauseFacetTest.removeFacet(facetAddress); vm.startPrank(USER_RECEIVER); vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacet.removeFacet(facetAddress); + emergencyPauseFacetTest.removeFacet(facetAddress); // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacet) + address(emergencyPauseFacetTest) ).facets(); // ensure that number of facets remains unchanged diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol index 09996e8f2..ef85e796e 100644 --- a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol @@ -23,14 +23,8 @@ contract EmergencyPauseFacetLOCALTest is TestBase { error NoFacetToPause(); uint256 internal counter; - event DiamondCut( - LibDiamond.FacetCut[] _diamondCut, - address _init, - bytes _calldata - ); // STORAGE - EmergencyPauseFacet internal emergencyPauseFacet; address[] internal blacklist = new address[](0); function setUp() public override { @@ -39,7 +33,7 @@ contract EmergencyPauseFacetLOCALTest is TestBase { initTestBase(); - // // no need to add the facet to the diamond, it's already added in DiamondTest.sol + // no need to add the facet to the diamond, it's already added in DiamondTest.sol emergencyPauseFacet = EmergencyPauseFacet(payable(address(diamond))); // set facet address in TestBase @@ -149,7 +143,7 @@ contract EmergencyPauseFacetLOCALTest is TestBase { } function test_CanUnpauseDiamondWithSingleBlacklist() public { - address ownershipFacetAddress = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; + address ownershipFacetAddress = address(ownershipFacet); // get function selectors of OwnershipFacet bytes4[] memory ownershipFunctionSelectors = IDiamondLoupe( @@ -205,8 +199,8 @@ contract EmergencyPauseFacetLOCALTest is TestBase { emit EmergencyUnpaused(USER_DIAMOND_OWNER); blacklist = new address[](2); - blacklist[0] = 0xB021CCbe1bd1EF2af8221A79E89dD3145947A082; // OwnershipFacet - blacklist[1] = 0xA412555Fa40F6AA4B67a773dB5a7f85983890341; // PeripheryRegistryFacet + blacklist[0] = address(ownershipFacet); // OwnershipFacet + blacklist[1] = address(peripheryFacet); // PeripheryRegistryFacet emergencyPauseFacet.unpauseDiamond(blacklist); diff --git a/test/solidity/Facets/OwnershipFacet.t.sol b/test/solidity/Facets/OwnershipFacet.t.sol index f8bea896f..5637ed2b3 100644 --- a/test/solidity/Facets/OwnershipFacet.t.sol +++ b/test/solidity/Facets/OwnershipFacet.t.sol @@ -6,8 +6,6 @@ import { TestBase } from "../utils/TestBase.sol"; import { OnlyContractOwner } from "lifi/Errors/GenericErrors.sol"; contract OwnershipFacetTest is TestBase { - OwnershipFacet internal ownershipFacet; - error NoNullOwner(); error NewOwnerMustNotBeSelf(); error NoPendingOwnershipTransfer(); @@ -18,11 +16,6 @@ contract OwnershipFacetTest is TestBase { address indexed _to ); - event OwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - function setUp() public override { initTestBase(); diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 07cec5d46..1a874bb2d 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,7 +10,7 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LiFiDEXAggregatorDiamondTest } from "./LDA/utils/LiFiDEXAggregatorDiamondTest.sol"; +import { LiFiDEXAggregatorDiamondTest } from "./LDA/LiFiDEXAggregatorDiamond.t.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; diff --git a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol similarity index 99% rename from test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol rename to test/solidity/Periphery/LDA/BaseCoreRoute.t.sol index cf21e4508..83165a588 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRouteTest.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LiFiDEXAggregatorDiamondTest } from "./utils/LiFiDEXAggregatorDiamondTest.sol"; +import { LiFiDEXAggregatorDiamondTest } from "./LiFiDEXAggregatorDiamond.t.sol"; /// @title BaseCoreRouteTest /// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. diff --git a/test/solidity/Periphery/LDA/BaseDEXFacet.t.sol b/test/solidity/Periphery/LDA/BaseDEXFacet.t.sol index cc24948ff..afc5cf185 100644 --- a/test/solidity/Periphery/LDA/BaseDEXFacet.t.sol +++ b/test/solidity/Periphery/LDA/BaseDEXFacet.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { BaseCoreRouteTest } from "./BaseCoreRouteTest.t.sol"; +import { BaseCoreRouteTest } from "./BaseCoreRoute.t.sol"; import { stdJson } from "forge-std/StdJson.sol"; /// @title BaseDEXFacetTest diff --git a/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol b/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol index afbb38c0f..2eae7749e 100644 --- a/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/CoreRouteFacet.t.sol @@ -6,7 +6,7 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; import { ERC20PermitMock } from "lib/Permit2/lib/openzeppelin-contracts/contracts/mocks/ERC20PermitMock.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; +import { BaseCoreRouteTest } from "../BaseCoreRoute.t.sol"; import { Vm } from "forge-std/Vm.sol"; /// @title CoreRouteFacetTest diff --git a/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol b/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol index 61d60d6b0..1153b7d23 100644 --- a/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol +++ b/test/solidity/Periphery/LDA/Facets/NativeWrapperFacet.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { IWETH } from "lifi/Interfaces/IWETH.sol"; -import { BaseCoreRouteTest } from "../BaseCoreRouteTest.t.sol"; +import { BaseCoreRouteTest } from "../BaseCoreRoute.t.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; diff --git a/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol new file mode 100644 index 000000000..125cdb277 --- /dev/null +++ b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { DiamondTest } from "../../utils/DiamondTest.sol"; + +/// @title LiFiDEXAggregatorDiamondTest +/// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. +/// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. +contract LiFiDEXAggregatorDiamondTest is DiamondTest { + LiFiDEXAggregatorDiamond public ldaDiamond; + + event DiamondCut( + LibDiamond.FacetCut[] _diamondCut, + address _init, + bytes _calldata + ); + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + error FunctionDoesNotExist(); + error ShouldNotReachThisCode(); + error InvalidDiamondSetup(); + error ExternalCallFailed(); + + function setUp() public virtual override { + super.setUp(); + + ldaDiamond = new LiFiDEXAggregatorDiamond( + USER_LDA_DIAMOND_OWNER, + address(diamondCutFacet) + ); + // prepare function selector for diamondCut (OwnershipFacet) + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = ownershipFacet.owner.selector; + + // prepare parameters for diamondCut (OwnershipFacet) + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + cut[0] = LibDiamond.FacetCut({ + facetAddress: address(ownershipFacet), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + + vm.prank(USER_LDA_DIAMOND_OWNER); + DiamondCutFacet(address(ldaDiamond)).diamondCut(cut, address(0), ""); + } + + function test_DeploysWithoutErrors() public virtual { + ldaDiamond = new LiFiDEXAggregatorDiamond( + USER_LDA_DIAMOND_OWNER, + address(diamondCutFacet) + ); + } + + function test_ForwardsCallsViaDelegateCall() public { + // only one facet with one selector is registered (diamondCut) + vm.startPrank(USER_LDA_DIAMOND_OWNER); + + DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); + + // make sure that this call fails (without ending the test) + bool failed = false; + try DiamondLoupeFacet(address(ldaDiamond)).facetAddresses() returns ( + address[] memory + ) {} catch { + failed = true; + } + if (!failed) revert InvalidDiamondSetup(); + + // prepare function selectors + bytes4[] memory functionSelectors = new bytes4[](4); + functionSelectors[0] = diamondLoupe.facets.selector; + functionSelectors[1] = diamondLoupe.facetFunctionSelectors.selector; + functionSelectors[2] = diamondLoupe.facetAddresses.selector; + functionSelectors[3] = diamondLoupe.facetAddress.selector; + + // prepare diamondCut + LibDiamond.FacetCut[] memory cuts = new LibDiamond.FacetCut[](1); + cuts[0] = LibDiamond.FacetCut({ + facetAddress: address(diamondLoupe), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + + DiamondCutFacet(address(ldaDiamond)).diamondCut(cuts, address(0), ""); + } + + function test_RevertsOnUnknownFunctionSelector() public { + // call random function selectors + bytes memory callData = hex"a516f0f3"; // getPeripheryContract(string) + + vm.expectRevert(FunctionDoesNotExist.selector); + (bool success, ) = address(ldaDiamond).call(callData); + if (!success) revert ShouldNotReachThisCode(); // was only added to silence a compiler warning + } + + function test_CanReceiveETH() public { + (bool success, ) = address(ldaDiamond).call{ value: 1 ether }(""); + if (!success) revert ExternalCallFailed(); + + assertEq(address(ldaDiamond).balance, 1 ether); + } +} diff --git a/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol b/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol deleted file mode 100644 index 0ee582819..000000000 --- a/test/solidity/Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -import { BaseDiamondTest } from "../../../utils/BaseDiamondTest.sol"; -import { TestBaseRandomConstants } from "../../../utils/TestBaseRandomConstants.sol"; - -/// @title LiFiDEXAggregatorDiamondTest -/// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. -/// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. -contract LiFiDEXAggregatorDiamondTest is - BaseDiamondTest, - TestBaseRandomConstants -{ - /// @notice The diamond proxy under test. - LiFiDEXAggregatorDiamond internal ldaDiamond; - - /// @notice Deploys a clean LDA diamond with base facets and sets owner/pauser. - /// @dev This runs before higher-level test setup in BaseCoreRouteTest/BaseDEXFacetTest. - function setUp() public virtual { - ldaDiamond = createLDADiamond(USER_LDA_DIAMOND_OWNER); - } - - /// @notice Creates an LDA diamond and wires up Loupe, Ownership and EmergencyPause facets. - /// @param _diamondOwner Owner address for the diamond. - /// @return diamond The newly created diamond instance. - function createLDADiamond( - address _diamondOwner - ) internal returns (LiFiDEXAggregatorDiamond) { - vm.startPrank(_diamondOwner); - DiamondCutFacet diamondCut = new DiamondCutFacet(); - DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); - OwnershipFacet ownership = new OwnershipFacet(); - LiFiDEXAggregatorDiamond diamond = new LiFiDEXAggregatorDiamond( - _diamondOwner, - address(diamondCut) - ); - - // Add Diamond Loupe - _addDiamondLoupeSelectors(address(diamondLoupe)); - - // Add Ownership - _addOwnershipSelectors(address(ownership)); - - DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); - delete cut; - vm.stopPrank(); - return diamond; - } - - /// @notice Tests that diamond creation fails when owner address is zero - function testRevert_CannotDeployDiamondWithZeroOwner() public { - address diamondCutFacet = address(new DiamondCutFacet()); - - vm.expectRevert(InvalidConfig.selector); - new LiFiDEXAggregatorDiamond( - address(0), // Zero owner address - diamondCutFacet - ); - } - - function testRevert_CannotCallNonExistentFunction() public { - // Create arbitrary calldata with non-existent selector - bytes memory nonExistentCalldata = abi.encodeWithSelector( - bytes4(keccak256("nonExistentFunction()")), - "" - ); - - vm.expectRevert(LibDiamond.FunctionDoesNotExist.selector); - (bool success, bytes memory returnData) = address(ldaDiamond).call( - nonExistentCalldata - ); - success; // silence unused variable warning - returnData; // silence unused variable warning - } - - function testRevert_CannotCallUnregisteredSelector() public { - // Use a real function selector that exists but hasn't been registered yet - bytes memory unregisteredCalldata = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, // Valid selector but not registered yet - new LibDiamond.FacetCut[](0), - address(0), - "" - ); - - vm.expectRevert(LibDiamond.FunctionDoesNotExist.selector); - // solhint-disable-next-line unused-return - (bool success, bytes memory returnData) = address(ldaDiamond).call( - unregisteredCalldata - ); - success; // silence unused variable warning - returnData; // silence unused variable warning - } -} diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol index 8d40fbafd..8d33e3357 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapperSON.t.sol @@ -15,6 +15,7 @@ contract LidoWrapperTestSON is TestBase { 0xB67FB1422ACa6F017BFdF1c40b372dA9eEdD03BF; function setUp() public override { + super.setUp(); vm.label(ST_ETH_ADDRESS, "stETH"); vm.label(WST_ETH_ADDRESS, "wstETH"); diff --git a/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol b/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol index d03357314..f66c7b83d 100644 --- a/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol +++ b/test/solidity/Periphery/LidoWrapper/LidoWrapperUNI.t.sol @@ -17,6 +17,7 @@ contract LidoWrapperTestUNI is TestBase { uint256 private whaleBalance; function setUp() public override { + super.setUp(); vm.label(ST_ETH_ADDRESS, "stETH"); vm.label(WST_ETH_ADDRESS, "wstETH"); diff --git a/test/solidity/Periphery/Permit2Proxy.t.sol b/test/solidity/Periphery/Permit2Proxy.t.sol index 077903447..059939f8d 100644 --- a/test/solidity/Periphery/Permit2Proxy.t.sol +++ b/test/solidity/Periphery/Permit2Proxy.t.sol @@ -293,7 +293,7 @@ contract Permit2ProxyTest is TestBase { ADDRESS_USDC, defaultUSDCAmount, testdata.deadline, - testdata.v + 1, // invalid v value + 30, // invalid v value (valid values are 27, 28) testdata.r, testdata.s, testdata.diamondCalldata diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol index 0228827c4..3cf330cb0 100644 --- a/test/solidity/utils/BaseDiamondTest.sol +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -6,12 +6,13 @@ import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { Test } from "forge-std/Test.sol"; +import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; /// @title BaseDiamondTest /// @notice Minimal helper to compose a test Diamond and add facets/selectors for test scenarios. /// @dev Provides overloads to add facets with or without init calldata. /// This contract is used by higher-level LDA test scaffolding to assemble the test Diamond. -abstract contract BaseDiamondTest is Test { +abstract contract BaseDiamondTest is Test, TestBaseRandomConstants { LibDiamond.FacetCut[] internal cut; /// @notice Adds standard Diamond Loupe selectors to the `cut` buffer. diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/utils/DiamondTest.sol index 4a940b378..07cb169b2 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/utils/DiamondTest.sol @@ -11,28 +11,39 @@ import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { BaseDiamondTest } from "./BaseDiamondTest.sol"; contract DiamondTest is BaseDiamondTest { + LiFiDiamond internal diamond; + DiamondCutFacet internal diamondCutFacet; + DiamondLoupeFacet internal diamondLoupeFacet; + OwnershipFacet internal ownershipFacet; + PeripheryRegistryFacet internal peripheryFacet; + EmergencyPauseFacet internal emergencyPauseFacet; + + function setUp() public virtual { + diamondCutFacet = new DiamondCutFacet(); + diamondLoupeFacet = new DiamondLoupeFacet(); + ownershipFacet = new OwnershipFacet(); + peripheryFacet = new PeripheryRegistryFacet(); + emergencyPauseFacet = new EmergencyPauseFacet(USER_PAUSER); + createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + } + function createDiamond( address _diamondOwner, address _pauserWallet ) internal returns (LiFiDiamond) { - vm.startPrank(_diamondOwner); - DiamondCutFacet diamondCut = new DiamondCutFacet(); - DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); - OwnershipFacet ownership = new OwnershipFacet(); - PeripheryRegistryFacet periphery = new PeripheryRegistryFacet(); - EmergencyPauseFacet emergencyPause = new EmergencyPauseFacet( - _pauserWallet - ); - LiFiDiamond diamond = new LiFiDiamond( - _diamondOwner, - address(diamondCut) - ); + vm.startPrank(USER_DIAMOND_OWNER); + diamondCutFacet = new DiamondCutFacet(); + diamondLoupeFacet = new DiamondLoupeFacet(); + ownershipFacet = new OwnershipFacet(); + peripheryFacet = new PeripheryRegistryFacet(); + emergencyPauseFacet = new EmergencyPauseFacet(_pauserWallet); + diamond = new LiFiDiamond(_diamondOwner, address(diamondCutFacet)); // Add Diamond Loupe - _addDiamondLoupeSelectors(address(diamondLoupe)); + _addDiamondLoupeSelectors(address(diamondLoupeFacet)); // Add Ownership - _addOwnershipSelectors(address(ownership)); + _addOwnershipSelectors(address(ownershipFacet)); // Add PeripheryRegistry bytes4[] memory functionSelectors = new bytes4[](2); @@ -44,7 +55,7 @@ contract DiamondTest is BaseDiamondTest { .selector; cut.push( LibDiamond.FacetCut({ - facetAddress: address(periphery), + facetAddress: address(peripheryFacet), action: LibDiamond.FacetCutAction.Add, functionSelectors: functionSelectors }) @@ -52,12 +63,12 @@ contract DiamondTest is BaseDiamondTest { // Add EmergencyPause functionSelectors = new bytes4[](3); - functionSelectors[0] = emergencyPause.removeFacet.selector; - functionSelectors[1] = emergencyPause.pauseDiamond.selector; - functionSelectors[2] = emergencyPause.unpauseDiamond.selector; + functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; + functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; + functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; cut.push( LibDiamond.FacetCut({ - facetAddress: address(emergencyPause), + facetAddress: address(emergencyPauseFacet), action: LibDiamond.FacetCutAction.Add, functionSelectors: functionSelectors }) diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 839ad9b4b..42bb28d4d 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -6,7 +6,6 @@ import { DSTest } from "ds-test/test.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; import { LibSwap } from "lifi/Libraries/LibSwap.sol"; import { UniswapV2Router02 } from "../utils/Interfaces.sol"; -import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; @@ -15,7 +14,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LiFiDEXAggregatorDiamondTest } from "../Periphery/LDA/utils/LiFiDEXAggregatorDiamondTest.sol"; +import { LiFiDEXAggregatorDiamondTest } from "../Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol"; using stdJson for string; @@ -98,7 +97,6 @@ abstract contract TestBase is TestBaseForksConstants, TestBaseRandomConstants, TestHelpers, - DiamondTest, LiFiDEXAggregatorDiamondTest, ILiFi { @@ -110,7 +108,6 @@ abstract contract TestBase is ERC20 internal usdt; ERC20 internal dai; ERC20 internal weth; - LiFiDiamond internal diamond; FeeCollector internal feeCollector; ILiFi.BridgeData internal bridgeData; LibSwap.SwapData[] internal swapData; @@ -241,9 +238,7 @@ abstract contract TestBase is dai = ERC20(ADDRESS_DAI); weth = ERC20(ADDRESS_WRAPPED_NATIVE); - // deploy & configure diamond - diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); - // deploy & configure LiFiDEXAggregatorDiamond + // deploy & configure LiFiDiamond and LiFiDEXAggregatorDiamond LiFiDEXAggregatorDiamondTest.setUp(); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From 2495a3481cc6f82a6240342c211cff2d345c438f Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 3 Sep 2025 16:43:16 +0200 Subject: [PATCH 152/220] Refactor test structure by removing LiFiDiamondTest and LiFiDEXAggregatorDiamondTest files, consolidating common functionality into CommonDiamondTest, and updating related imports across multiple test files for improved clarity and maintainability --- .../Facets/CBridge/CBridgeRefund.t.sol | 9 +- .../EmergencyPauseFacet.local.t.sol | 2 - test/solidity/LiFiDiamond.t.sol | 108 --------- test/solidity/Periphery/GasZipPeriphery.t.sol | 2 +- .../Periphery/LDA/BaseCoreRoute.t.sol | 2 +- .../LDA/LiFiDEXAggregatorDiamond.t.sol | 110 ---------- test/solidity/utils/CommonDiamondTest.sol | 207 ++++++++++++++++++ test/solidity/utils/DiamondTest.sol | 83 ++----- .../utils/LiFiDEXAggregatorDiamondTest.sol | 46 ++++ test/solidity/utils/TestBase.sol | 2 +- 10 files changed, 274 insertions(+), 297 deletions(-) delete mode 100644 test/solidity/LiFiDiamond.t.sol delete mode 100644 test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol create mode 100644 test/solidity/utils/CommonDiamondTest.sol create mode 100644 test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol diff --git a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol index 5512dbf4e..5d55053cb 100644 --- a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.17; import { DSTest } from "ds-test/test.sol"; -import { DiamondTest, LiFiDiamond } from "../../utils/DiamondTest.sol"; +import { BaseDiamondTest } from "../../utils/BaseDiamondTest.sol"; +import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { WithdrawFacet } from "lifi/Facets/WithdrawFacet.sol"; import { UnAuthorized, NotAContract } from "lifi/Errors/GenericErrors.sol"; @@ -11,7 +12,7 @@ import { UnAuthorized, NotAContract } from "lifi/Errors/GenericErrors.sol"; // Actual refund was processed at 25085299(Feb-18-2022 03:24:09 PM +UTC) // Run `forge test --match-path test\solidity\Facets\CBridgeRefund.t.sol --fork-url POLYGON_RPC_URL --fork-block-number 25085298` // or `forge test --match-contract CBridgeRefundTest --fork-url POLYGON_RPC_URL --fork-block-number 25085298` -contract CBridgeRefundTestPolygon is DSTest, DiamondTest { +contract CBridgeRefundTestPolygon is DSTest, BaseDiamondTest { address internal constant CBRIDGE_ADDRESS = 0x88DCDC47D2f83a99CF0000FDF667A468bB958a78; address internal constant LIFI_ADDRESS = @@ -29,7 +30,7 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { error WithdrawFailed(); bytes internal validCalldata; - + LiFiDiamond internal diamond; WithdrawFacet internal withdrawFacet; ///@notice Init calldata for extra call. @@ -82,7 +83,7 @@ contract CBridgeRefundTestPolygon is DSTest, DiamondTest { /// @notice Setup contracts for test. /// @dev It adds selector of new function(executeCallAndWithdraw). /// And initialize calldata for extra call. - function setUp() public override { + function setUp() public { fork(); diamond = LiFiDiamond(payable(LIFI_ADDRESS)); diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol index ef85e796e..99dd5f793 100644 --- a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.local.t.sol @@ -50,7 +50,6 @@ contract EmergencyPauseFacetLOCALTest is TestBase { function test_PauserWalletCanPauseDiamond() public { vm.startPrank(USER_PAUSER); - vm.expectEmit(true, true, true, true, address(emergencyPauseFacet)); emit EmergencyPaused(USER_PAUSER); @@ -149,7 +148,6 @@ contract EmergencyPauseFacetLOCALTest is TestBase { bytes4[] memory ownershipFunctionSelectors = IDiamondLoupe( address(diamond) ).facetFunctionSelectors(ownershipFacetAddress); - // pause diamond first test_PauserWalletCanPauseDiamond(); diff --git a/test/solidity/LiFiDiamond.t.sol b/test/solidity/LiFiDiamond.t.sol deleted file mode 100644 index 13414fb89..000000000 --- a/test/solidity/LiFiDiamond.t.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: Unlicensed -pragma solidity ^0.8.17; - -import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; -import { DSTest } from "ds-test/test.sol"; -import { Vm } from "forge-std/Vm.sol"; - -contract LiFiDiamondTest is DSTest { - // solhint-disable immutable-vars-naming - Vm internal immutable vm = Vm(HEVM_ADDRESS); - LiFiDiamond public diamond; - DiamondCutFacet public diamondCutFacet; - OwnershipFacet public ownershipFacet; - address public diamondOwner; - - event DiamondCut( - LibDiamond.FacetCut[] _diamondCut, - address _init, - bytes _calldata - ); - - event OwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - - error FunctionDoesNotExist(); - error ShouldNotReachThisCode(); - error InvalidDiamondSetup(); - error ExternalCallFailed(); - - function setUp() public { - diamondOwner = address(123456); - diamondCutFacet = new DiamondCutFacet(); - ownershipFacet = new OwnershipFacet(); - - // prepare function selector for diamondCut (OwnershipFacet) - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = ownershipFacet.owner.selector; - - // prepare parameters for diamondCut (OwnershipFacet) - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - cut[0] = LibDiamond.FacetCut({ - facetAddress: address(ownershipFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }); - - diamond = new LiFiDiamond(diamondOwner, address(diamondCutFacet)); - } - - function test_DeploysWithoutErrors() public { - diamond = new LiFiDiamond(diamondOwner, address(diamondCutFacet)); - } - - function test_ForwardsCallsViaDelegateCall() public { - // only one facet with one selector is registered (diamondCut) - vm.startPrank(diamondOwner); - - DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); - - // make sure that this call fails (without ending the test) - bool failed = false; - try DiamondLoupeFacet(address(diamond)).facetAddresses() returns ( - address[] memory - ) {} catch { - failed = true; - } - if (!failed) revert InvalidDiamondSetup(); - - // prepare function selectors - bytes4[] memory functionSelectors = new bytes4[](4); - functionSelectors[0] = diamondLoupe.facets.selector; - functionSelectors[1] = diamondLoupe.facetFunctionSelectors.selector; - functionSelectors[2] = diamondLoupe.facetAddresses.selector; - functionSelectors[3] = diamondLoupe.facetAddress.selector; - - // prepare diamondCut - LibDiamond.FacetCut[] memory cuts = new LibDiamond.FacetCut[](1); - cuts[0] = LibDiamond.FacetCut({ - facetAddress: address(diamondLoupe), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }); - - DiamondCutFacet(address(diamond)).diamondCut(cuts, address(0), ""); - } - - function test_RevertsOnUnknownFunctionSelector() public { - // call random function selectors - bytes memory callData = hex"a516f0f3"; // getPeripheryContract(string) - - vm.expectRevert(FunctionDoesNotExist.selector); - (bool success, ) = address(diamond).call(callData); - if (!success) revert ShouldNotReachThisCode(); // was only added to silence a compiler warning - } - - function test_CanReceiveETH() public { - (bool success, ) = address(diamond).call{ value: 1 ether }(""); - if (!success) revert ExternalCallFailed(); - - assertEq(address(diamond).balance, 1 ether); - } -} diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 1a874bb2d..a966f02ba 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,7 +10,7 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LiFiDEXAggregatorDiamondTest } from "./LDA/LiFiDEXAggregatorDiamond.t.sol"; +import { LiFiDEXAggregatorDiamondTest } from "../utils/LiFiDEXAggregatorDiamondTest.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; diff --git a/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol index 83165a588..8eea70290 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LiFiDEXAggregatorDiamondTest } from "./LiFiDEXAggregatorDiamond.t.sol"; +import { LiFiDEXAggregatorDiamondTest } from "../../utils/LiFiDEXAggregatorDiamondTest.sol"; /// @title BaseCoreRouteTest /// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. diff --git a/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol deleted file mode 100644 index 125cdb277..000000000 --- a/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { DiamondTest } from "../../utils/DiamondTest.sol"; - -/// @title LiFiDEXAggregatorDiamondTest -/// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. -/// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. -contract LiFiDEXAggregatorDiamondTest is DiamondTest { - LiFiDEXAggregatorDiamond public ldaDiamond; - - event DiamondCut( - LibDiamond.FacetCut[] _diamondCut, - address _init, - bytes _calldata - ); - - event OwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - - error FunctionDoesNotExist(); - error ShouldNotReachThisCode(); - error InvalidDiamondSetup(); - error ExternalCallFailed(); - - function setUp() public virtual override { - super.setUp(); - - ldaDiamond = new LiFiDEXAggregatorDiamond( - USER_LDA_DIAMOND_OWNER, - address(diamondCutFacet) - ); - // prepare function selector for diamondCut (OwnershipFacet) - bytes4[] memory functionSelectors = new bytes4[](1); - functionSelectors[0] = ownershipFacet.owner.selector; - - // prepare parameters for diamondCut (OwnershipFacet) - LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); - cut[0] = LibDiamond.FacetCut({ - facetAddress: address(ownershipFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }); - - vm.prank(USER_LDA_DIAMOND_OWNER); - DiamondCutFacet(address(ldaDiamond)).diamondCut(cut, address(0), ""); - } - - function test_DeploysWithoutErrors() public virtual { - ldaDiamond = new LiFiDEXAggregatorDiamond( - USER_LDA_DIAMOND_OWNER, - address(diamondCutFacet) - ); - } - - function test_ForwardsCallsViaDelegateCall() public { - // only one facet with one selector is registered (diamondCut) - vm.startPrank(USER_LDA_DIAMOND_OWNER); - - DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); - - // make sure that this call fails (without ending the test) - bool failed = false; - try DiamondLoupeFacet(address(ldaDiamond)).facetAddresses() returns ( - address[] memory - ) {} catch { - failed = true; - } - if (!failed) revert InvalidDiamondSetup(); - - // prepare function selectors - bytes4[] memory functionSelectors = new bytes4[](4); - functionSelectors[0] = diamondLoupe.facets.selector; - functionSelectors[1] = diamondLoupe.facetFunctionSelectors.selector; - functionSelectors[2] = diamondLoupe.facetAddresses.selector; - functionSelectors[3] = diamondLoupe.facetAddress.selector; - - // prepare diamondCut - LibDiamond.FacetCut[] memory cuts = new LibDiamond.FacetCut[](1); - cuts[0] = LibDiamond.FacetCut({ - facetAddress: address(diamondLoupe), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }); - - DiamondCutFacet(address(ldaDiamond)).diamondCut(cuts, address(0), ""); - } - - function test_RevertsOnUnknownFunctionSelector() public { - // call random function selectors - bytes memory callData = hex"a516f0f3"; // getPeripheryContract(string) - - vm.expectRevert(FunctionDoesNotExist.selector); - (bool success, ) = address(ldaDiamond).call(callData); - if (!success) revert ShouldNotReachThisCode(); // was only added to silence a compiler warning - } - - function test_CanReceiveETH() public { - (bool success, ) = address(ldaDiamond).call{ value: 1 ether }(""); - if (!success) revert ExternalCallFailed(); - - assertEq(address(ldaDiamond).balance, 1 ether); - } -} diff --git a/test/solidity/utils/CommonDiamondTest.sol b/test/solidity/utils/CommonDiamondTest.sol new file mode 100644 index 000000000..cb1b27c29 --- /dev/null +++ b/test/solidity/utils/CommonDiamondTest.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { BaseDiamondTest } from "./BaseDiamondTest.sol"; + +/// @title CommonDiamondTest +/// @notice Base contract with common diamond test functions to reduce code duplication +/// @dev Provides standard test patterns that work with any diamond implementation +abstract contract CommonDiamondTest is BaseDiamondTest { + // Main diamond instance - accessible to all inheriting contracts including TestBase + LiFiDiamond internal diamond; + + // Common facet instances + DiamondCutFacet internal diamondCutFacet; + OwnershipFacet internal ownershipFacet; + DiamondLoupeFacet internal diamondLoupeFacet; + PeripheryRegistryFacet internal peripheryFacet; + EmergencyPauseFacet internal emergencyPauseFacet; + + // Events + event DiamondCut( + LibDiamond.FacetCut[] _diamondCut, + address _init, + bytes _calldata + ); + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + // Errors + error FunctionDoesNotExist(); + error ShouldNotReachThisCode(); + error InvalidDiamondSetup(); + error ExternalCallFailed(); + + function setUp() public virtual { + // Create the main LiFiDiamond that TestBase and other facet tests expect + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + } + + /// @notice Creates a fully configured diamond with all standard facets + /// @param _diamondOwner Owner address for the diamond + /// @param _pauserWallet Pauser wallet address for emergency pause facet + /// @return The created LiFiDiamond instance + function createDiamond( + address _diamondOwner, + address _pauserWallet + ) internal returns (LiFiDiamond) { + vm.startPrank(_diamondOwner); + + // Recreate facets with the specified pauser + diamondCutFacet = new DiamondCutFacet(); + diamondLoupeFacet = new DiamondLoupeFacet(); + ownershipFacet = new OwnershipFacet(); + peripheryFacet = new PeripheryRegistryFacet(); + emergencyPauseFacet = new EmergencyPauseFacet(_pauserWallet); + + // Create new diamond + diamond = new LiFiDiamond(_diamondOwner, address(diamondCutFacet)); + + // Add Diamond Loupe + _addDiamondLoupeSelectors(address(diamondLoupeFacet)); + + // Add Ownership + _addOwnershipSelectors(address(ownershipFacet)); + + // Add PeripheryRegistry + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = PeripheryRegistryFacet + .registerPeripheryContract + .selector; + functionSelectors[1] = PeripheryRegistryFacet + .getPeripheryContract + .selector; + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(peripheryFacet), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + + // Add EmergencyPause + functionSelectors = new bytes4[](3); + functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; + functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; + functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(emergencyPauseFacet), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + + DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); + delete cut; + vm.stopPrank(); + return diamond; + } + + /// @notice Override this to return the diamond address for testing + function getDiamondAddress() internal view virtual returns (address) { + return address(diamond); + } + + /// @notice Override this to return the diamond owner address + function getDiamondOwner() internal view virtual returns (address) { + return USER_DIAMOND_OWNER; + } + + /// @notice Test that diamond deployment works without errors + function test_DeploysWithoutErrors() public virtual { + assertTrue( + getDiamondAddress() != address(0), + "Diamond should be deployed" + ); + } + + /// @notice Test that diamond forwards calls via delegate call + function test_ForwardsCallsViaDelegateCall() public { + address diamondAddr = getDiamondAddress(); + address owner = getDiamondOwner(); + + vm.startPrank(owner); + + DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); + + // Check if DiamondLoupeFacet is already installed + bool loupeAlreadyInstalled = false; + try DiamondLoupeFacet(diamondAddr).facetAddresses() returns ( + address[] memory + ) { + loupeAlreadyInstalled = true; + } catch { + // Loupe not installed, which is expected for basic diamonds + } + + if (!loupeAlreadyInstalled) { + // prepare function selectors + bytes4[] memory functionSelectors = new bytes4[](4); + functionSelectors[0] = diamondLoupe.facets.selector; + functionSelectors[1] = diamondLoupe + .facetFunctionSelectors + .selector; + functionSelectors[2] = diamondLoupe.facetAddresses.selector; + functionSelectors[3] = diamondLoupe.facetAddress.selector; + + // prepare diamondCut + LibDiamond.FacetCut[] memory cuts = new LibDiamond.FacetCut[](1); + cuts[0] = LibDiamond.FacetCut({ + facetAddress: address(diamondLoupe), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + + DiamondCutFacet(diamondAddr).diamondCut(cuts, address(0), ""); + } + + // Now the call should succeed + address[] memory facetAddresses = DiamondLoupeFacet(diamondAddr) + .facetAddresses(); + assertTrue( + facetAddresses.length > 0, + "Should have facets after adding DiamondLoupe" + ); + + vm.stopPrank(); + } + + /// @notice Test that diamond reverts on unknown function selectors + function test_RevertsOnUnknownFunctionSelector() public { + address diamondAddr = getDiamondAddress(); + + // Use a completely random selector that definitely doesn't exist + bytes memory callData = hex"deadbeef"; + + vm.expectRevert(FunctionDoesNotExist.selector); + (bool success, ) = diamondAddr.call(callData); + if (!success) { + vm.expectRevert("Diamond: Function does not exist"); + (bool success2, ) = diamondAddr.call(callData); + if (!success2) { + revert ShouldNotReachThisCode(); + } + } + } + + /// @notice Test that diamond can receive ETH + function test_CanReceiveETH() public { + address diamondAddr = getDiamondAddress(); + uint256 balanceBefore = diamondAddr.balance; + (bool success, ) = diamondAddr.call{ value: 1 ether }(""); + if (!success) revert ExternalCallFailed(); + + assertEq(address(diamond).balance, balanceBefore + 1 ether); + } +} diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/utils/DiamondTest.sol index 07cb169b2..fe2dad81e 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/utils/DiamondTest.sol @@ -2,81 +2,24 @@ pragma solidity ^0.8.17; import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; -import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; -import { BaseDiamondTest } from "./BaseDiamondTest.sol"; +import { CommonDiamondTest } from "./CommonDiamondTest.sol"; -contract DiamondTest is BaseDiamondTest { - LiFiDiamond internal diamond; - DiamondCutFacet internal diamondCutFacet; - DiamondLoupeFacet internal diamondLoupeFacet; - OwnershipFacet internal ownershipFacet; - PeripheryRegistryFacet internal peripheryFacet; - EmergencyPauseFacet internal emergencyPauseFacet; - - function setUp() public virtual { - diamondCutFacet = new DiamondCutFacet(); - diamondLoupeFacet = new DiamondLoupeFacet(); - ownershipFacet = new OwnershipFacet(); - peripheryFacet = new PeripheryRegistryFacet(); - emergencyPauseFacet = new EmergencyPauseFacet(USER_PAUSER); +contract DiamondTest is CommonDiamondTest { + function setUp() public virtual override { + super.setUp(); + // Call createDiamond to get a fully configured diamond with all facets createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); } - function createDiamond( - address _diamondOwner, - address _pauserWallet - ) internal returns (LiFiDiamond) { - vm.startPrank(USER_DIAMOND_OWNER); - diamondCutFacet = new DiamondCutFacet(); - diamondLoupeFacet = new DiamondLoupeFacet(); - ownershipFacet = new OwnershipFacet(); - peripheryFacet = new PeripheryRegistryFacet(); - emergencyPauseFacet = new EmergencyPauseFacet(_pauserWallet); - diamond = new LiFiDiamond(_diamondOwner, address(diamondCutFacet)); - - // Add Diamond Loupe - _addDiamondLoupeSelectors(address(diamondLoupeFacet)); - - // Add Ownership - _addOwnershipSelectors(address(ownershipFacet)); - - // Add PeripheryRegistry - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = PeripheryRegistryFacet - .registerPeripheryContract - .selector; - functionSelectors[1] = PeripheryRegistryFacet - .getPeripheryContract - .selector; - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(peripheryFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) + /// @notice Test that LiFiDiamond can be deployed without errors + function test_DeploysWithoutErrors() public override { + LiFiDiamond testDiamond = new LiFiDiamond( + USER_DIAMOND_OWNER, + address(diamondCutFacet) ); - - // Add EmergencyPause - functionSelectors = new bytes4[](3); - functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; - functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; - functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(emergencyPauseFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) + assertTrue( + address(testDiamond) != address(0), + "Diamond should be deployed" ); - - DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); - delete cut; - vm.stopPrank(); - return diamond; } } diff --git a/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol b/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol new file mode 100644 index 000000000..8e8e6719e --- /dev/null +++ b/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { CommonDiamondTest } from "./CommonDiamondTest.sol"; + +/// @title LiFiDEXAggregatorDiamondTest +/// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. +/// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. +contract LiFiDEXAggregatorDiamondTest is CommonDiamondTest { + LiFiDEXAggregatorDiamond public ldaDiamond; + + function setUp() public virtual override { + super.setUp(); // This creates the main LiFiDiamond as 'diamond' + + ldaDiamond = new LiFiDEXAggregatorDiamond( + USER_LDA_DIAMOND_OWNER, + address(diamondCutFacet) + ); + + // prepare function selector for diamondCut (OwnershipFacet) + bytes4[] memory functionSelectors = new bytes4[](1); + functionSelectors[0] = ownershipFacet.owner.selector; + + // prepare parameters for diamondCut (OwnershipFacet) + LibDiamond.FacetCut[] memory cut = new LibDiamond.FacetCut[](1); + cut[0] = LibDiamond.FacetCut({ + facetAddress: address(ownershipFacet), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + + vm.prank(USER_LDA_DIAMOND_OWNER); + DiamondCutFacet(address(ldaDiamond)).diamondCut(cut, address(0), ""); + } + + function test_DeploysWithoutErrors() public virtual override { + ldaDiamond = new LiFiDEXAggregatorDiamond( + USER_LDA_DIAMOND_OWNER, + address(diamondCutFacet) + ); + super.test_DeploysWithoutErrors(); + } +} diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 42bb28d4d..70ccf7bfd 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -14,7 +14,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LiFiDEXAggregatorDiamondTest } from "../Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol"; +import { LiFiDEXAggregatorDiamondTest } from "./LiFiDEXAggregatorDiamondTest.sol"; using stdJson for string; From 58896e3a4374e9801808d2976316164e93849336 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 3 Sep 2025 17:07:22 +0200 Subject: [PATCH 153/220] Add test for LiFiDEXAggregatorDiamond to ensure it reverts when constructed with a zero address owner, utilizing InvalidConfig error handling. --- test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol b/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol index 8e8e6719e..445c3e5cb 100644 --- a/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol +++ b/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol @@ -5,6 +5,7 @@ import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDi import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { CommonDiamondTest } from "./CommonDiamondTest.sol"; +import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; /// @title LiFiDEXAggregatorDiamondTest /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. @@ -43,4 +44,15 @@ contract LiFiDEXAggregatorDiamondTest is CommonDiamondTest { ); super.test_DeploysWithoutErrors(); } + + /// @notice Test that LiFiDEXAggregatorDiamond reverts when constructed with zero address owner + function testRevert_LiFiDEXAggregatorDiamondConstructedWithZeroAddressOwner() + public + { + vm.expectRevert(InvalidConfig.selector); + new LiFiDEXAggregatorDiamond( + address(0), // This should trigger InvalidConfig + address(diamondCutFacet) + ); + } } From 4c45d4a172dc4f296e856c07ac34350e5bd9e374 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 3 Sep 2025 17:52:06 +0200 Subject: [PATCH 154/220] Update conventions.md to standardize naming from LDA to LiFiDEXAggregator, enhancing clarity in documentation. Adjust comments in VelodromeV2Facet and GasZipPeriphery test to reflect the new naming convention --- conventions.md | 18 +++++++++--------- src/Periphery/LDA/Facets/VelodromeV2Facet.sol | 2 +- test/solidity/Periphery/GasZipPeriphery.t.sol | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/conventions.md b/conventions.md index 9062aa3f9..94ff38344 100644 --- a/conventions.md +++ b/conventions.md @@ -379,15 +379,15 @@ We use Foundry as our primary development and testing framework. Foundry provide } ``` -## LDA (LiFi DEX Aggregator) Conventions +## LiFiDEXAggregator (LDA) Conventions -The LDA (LiFi DEX Aggregator) is a specialized component within the LI.FI ecosystem that provides efficient, modular DEX integration capabilities through its own Diamond Standard implementation. +The LiFiDEXAggregator (LDA) is a specialized component within the LI.FI ecosystem that provides efficient, modular DEX integration capabilities through its own Diamond Standard implementation. ### Architecture Overview #### Core Components -- **LiFiDEXAggregatorDiamond.sol**: Base EIP-2535 Diamond Proxy Contract for LDA +- **LiFiDEXAggregatorDiamond.sol**: Base EIP-2535 Diamond Proxy Contract for DEX Aggregator - **CoreRouteFacet.sol**: Orchestrates route execution using direct function selector dispatch - **BaseRouteConstants.sol**: Shared constants across DEX facets - **PoolCallbackAuthenticator.sol**: Abstract contract providing pool callback authentication @@ -399,14 +399,14 @@ src/Periphery/LDA/ ├── LiFiDEXAggregatorDiamond.sol # LiFiDEXAggregatorDiamond Diamond proxy implementation ├── BaseRouteConstants.sol # Common constants for DEX facets ├── PoolCallbackAuthenticator.sol # Callback authentication base -├── Facets/ # LDA-specific facets +├── Facets/ # LiFiDEXAggregator-specific facets │ ├── CoreRouteFacet.sol # Route orchestration │ ├── UniV3StyleFacet.sol # UniV3-style DEX integrations │ ├── UniV2StyleFacet.sol # UniV2-style DEX integrations │ ├── NativeWrapperFacet.sol # Native token wrapping │ └── {DexName}Facet.sol # Custom DEX integrations └── Errors/ - └── Errors.sol # LDA-specific error definitions + └── Errors.sol # LiFiDEXAggregator-specific error definitions ``` ### DEX Integration Decision Tree @@ -428,7 +428,7 @@ When integrating a new DEX, follow this decision tree: 4. **Else** → Create new custom facet with swap function without callbacks - Write tests inheriting `BaseDEXFacetTest` -### LDA Facet Requirements +### LiFiDEXAggregator Facet Requirements #### Naming and Location @@ -464,10 +464,10 @@ When integrating a new DEX, follow this decision tree: #### Error Handling -- **LDA-specific errors**: Define in `src/Periphery/LDA/LiFiDEXAggregatorErrors.sol` +- **LiFiDEXAggregator-specific errors**: Define in `src/Periphery/LDA/LiFiDEXAggregatorErrors.sol` - **Generic errors**: Use existing errors from `src/Errors/GenericErrors.sol` -### LDA Testing Conventions +### LiFiDEXAggregator Testing Conventions #### Test File Structure @@ -539,7 +539,7 @@ test/solidity/Periphery/LDA/ - **Token Decimals**: Override `_getDefaultAmountForTokenIn()` for non-18 decimal tokens - **Pool Verification**: Verify pool addresses exist and are correctly configured. Ensure the chosen fork block number has the pools deployed and contains sufficient liquidity for testing -### LDA Deployment Scripts +### LiFiDEXAggregator Deployment Scripts #### Location and Naming diff --git a/src/Periphery/LDA/Facets/VelodromeV2Facet.sol b/src/Periphery/LDA/Facets/VelodromeV2Facet.sol index e3dbcc130..aca380b3e 100644 --- a/src/Periphery/LDA/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/LDA/Facets/VelodromeV2Facet.sol @@ -79,7 +79,7 @@ contract VelodromeV2Facet is BaseRouteConstants { // 'swap' function from IVelodromeV2Pool should be called from a contract which performs important safety checks. // Safety Checks Covered: - // - Reentrancy: LDA has a custom lock() modifier + // - Reentrancy: LiFiDEXAggregator has a custom lock() modifier // - Token transfer safety: SafeERC20 is used to ensure token transfers revert on failure // - Expected output verification: The contract calls getAmountOut (including fees) before executing the swap // - Flashloan trigger: A flashloan flag is used to determine if the callback should be triggered diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index a966f02ba..be922b96a 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -653,7 +653,7 @@ contract GasZipPeripheryTest is TestBase { return LibSwap.SwapData({ - callTo: address(ldaDiamond), // LDA diamond address (CoreRouteFacet lives here) + callTo: address(ldaDiamond), // LiFiDEXAggregator diamond address (CoreRouteFacet lives here) approveTo: address(ldaDiamond), sendingAssetId: tokenIn, receivingAssetId: address(0), // native From 5992e3eedeabfa5572b8c73d13c7446b28724f7f Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 3 Sep 2025 18:31:19 +0200 Subject: [PATCH 155/220] Refactor LiFiDiamond and LiFiDEXAggregatorDiamond to improve error handling and update imports. Add test for LiFiDiamond to ensure it reverts on zero address owner. Rename LiFiDEXAggregatorDiamondTest to DEXAggregatorDiamondTest for consistency. --- src/LiFiDiamond.sol | 10 +++++---- .../LDA/LiFiDEXAggregatorDiamond.sol | 7 +----- test/solidity/Facets/HopFacet.t.sol | 6 ++--- test/solidity/Periphery/GasZipPeriphery.t.sol | 4 ++-- .../Periphery/LDA/BaseCoreRoute.t.sol | 9 +++----- ...dTest.sol => DEXAggregatorDiamondTest.sol} | 22 +++++-------------- test/solidity/utils/DiamondTest.sol | 10 +++++++++ test/solidity/utils/TestBase.sol | 6 ++--- .../utils/TestBaseRandomConstants.sol | 1 - 9 files changed, 33 insertions(+), 42 deletions(-) rename test/solidity/utils/{LiFiDEXAggregatorDiamondTest.sol => DEXAggregatorDiamondTest.sol} (71%) diff --git a/src/LiFiDiamond.sol b/src/LiFiDiamond.sol index 49b571bbd..3f619a683 100644 --- a/src/LiFiDiamond.sol +++ b/src/LiFiDiamond.sol @@ -3,15 +3,17 @@ pragma solidity ^0.8.17; import { LibDiamond } from "./Libraries/LibDiamond.sol"; import { IDiamondCut } from "./Interfaces/IDiamondCut.sol"; -// solhint-disable-next-line no-unused-import -import { LibUtil } from "./Libraries/LibUtil.sol"; +import { InvalidConfig } from "./Errors/GenericErrors.sol"; -/// @title LIFI Diamond +/// @title LIFIDiamond /// @author LI.FI (https://li.fi) /// @notice Base EIP-2535 Diamond Proxy Contract. -/// @custom:version 1.0.0 +/// @custom:version 1.0.1 contract LiFiDiamond { constructor(address _contractOwner, address _diamondCutFacet) payable { + if (_contractOwner == address(0)) { + revert InvalidConfig(); + } LibDiamond.setContractOwner(_contractOwner); // Add the diamondCut external function from the diamondCutFacet diff --git a/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol index 4991b425c..bed1beb80 100644 --- a/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol +++ b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.17; import { LiFiDiamond } from "../../LiFiDiamond.sol"; -import { InvalidConfig } from "../../Errors/GenericErrors.sol"; /// @title LiFiDEXAggregatorDiamond /// @author LI.FI (https://li.fi) @@ -12,9 +11,5 @@ contract LiFiDEXAggregatorDiamond is LiFiDiamond { constructor( address _contractOwner, address _diamondCutFacet - ) LiFiDiamond(_contractOwner, _diamondCutFacet) { - if (_contractOwner == address(0)) { - revert InvalidConfig(); - } - } + ) LiFiDiamond(_contractOwner, _diamondCutFacet) {} } diff --git a/test/solidity/Facets/HopFacet.t.sol b/test/solidity/Facets/HopFacet.t.sol index 23a983e0c..ba444c488 100644 --- a/test/solidity/Facets/HopFacet.t.sol +++ b/test/solidity/Facets/HopFacet.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.17; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { LibSwap, TestBaseFacet } from "../utils/TestBaseFacet.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; import { HopFacet } from "lifi/Facets/HopFacet.sol"; -import { OnlyContractOwner, InvalidConfig, InvalidAmount } from "src/Errors/GenericErrors.sol"; -import { LiFiDiamond } from "../utils/DiamondTest.sol"; +import { OnlyContractOwner, InvalidConfig, InvalidAmount } from "lifi/Errors/GenericErrors.sol"; +import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; +import { LibSwap, TestBaseFacet } from "../utils/TestBaseFacet.sol"; // Stub HopFacet Contract contract TestHopFacet is HopFacet { diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index be922b96a..cec004384 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -10,7 +10,7 @@ import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { LiFiDEXAggregatorDiamondTest } from "../utils/LiFiDEXAggregatorDiamondTest.sol"; +import { DEXAggregatorDiamondTest } from "../utils/DEXAggregatorDiamondTest.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; @@ -61,7 +61,7 @@ contract GasZipPeripheryTest is TestBase { function setUp() public override { customBlockNumberForForking = 22566858; initTestBase(); - LiFiDEXAggregatorDiamondTest.setUp(); + DEXAggregatorDiamondTest.setUp(); // deploy contracts gasZipPeriphery = new TestGasZipPeriphery( diff --git a/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol index 8eea70290..5f97f2ee0 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { LiFiDEXAggregatorDiamondTest } from "../../utils/LiFiDEXAggregatorDiamondTest.sol"; +import { DEXAggregatorDiamondTest } from "../../utils/DEXAggregatorDiamondTest.sol"; /// @title BaseCoreRouteTest /// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. @@ -15,10 +15,7 @@ import { LiFiDEXAggregatorDiamondTest } from "../../utils/LiFiDEXAggregatorDiamo /// - Event expectations helpers /// - Overloads of `_executeAndVerifySwap` including revert path /// Concrete tests compose these helpers to succinctly define swap scenarios. -abstract contract BaseCoreRouteTest is - LiFiDEXAggregatorDiamondTest, - TestHelpers -{ +abstract contract BaseCoreRouteTest is DEXAggregatorDiamondTest, TestHelpers { using SafeERC20 for IERC20; // ==== Types ==== @@ -123,7 +120,7 @@ abstract contract BaseCoreRouteTest is /// @notice Deploys and attaches `CoreRouteFacet` to the diamond under test. /// @dev Invoked from `setUp` of child tests via inheritance chain. function setUp() public virtual override { - LiFiDEXAggregatorDiamondTest.setUp(); + DEXAggregatorDiamondTest.setUp(); _addCoreRouteFacet(); } diff --git a/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol b/test/solidity/utils/DEXAggregatorDiamondTest.sol similarity index 71% rename from test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol rename to test/solidity/utils/DEXAggregatorDiamondTest.sol index 445c3e5cb..8088b8746 100644 --- a/test/solidity/utils/LiFiDEXAggregatorDiamondTest.sol +++ b/test/solidity/utils/DEXAggregatorDiamondTest.sol @@ -5,19 +5,18 @@ import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDi import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { CommonDiamondTest } from "./CommonDiamondTest.sol"; -import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -/// @title LiFiDEXAggregatorDiamondTest +/// @title DEXAggregatorDiamondTest /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. /// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. -contract LiFiDEXAggregatorDiamondTest is CommonDiamondTest { +contract DEXAggregatorDiamondTest is CommonDiamondTest { LiFiDEXAggregatorDiamond public ldaDiamond; function setUp() public virtual override { super.setUp(); // This creates the main LiFiDiamond as 'diamond' ldaDiamond = new LiFiDEXAggregatorDiamond( - USER_LDA_DIAMOND_OWNER, + USER_DIAMOND_OWNER, address(diamondCutFacet) ); @@ -33,26 +32,15 @@ contract LiFiDEXAggregatorDiamondTest is CommonDiamondTest { functionSelectors: functionSelectors }); - vm.prank(USER_LDA_DIAMOND_OWNER); + vm.prank(USER_DIAMOND_OWNER); DiamondCutFacet(address(ldaDiamond)).diamondCut(cut, address(0), ""); } function test_DeploysWithoutErrors() public virtual override { ldaDiamond = new LiFiDEXAggregatorDiamond( - USER_LDA_DIAMOND_OWNER, + USER_DIAMOND_OWNER, address(diamondCutFacet) ); super.test_DeploysWithoutErrors(); } - - /// @notice Test that LiFiDEXAggregatorDiamond reverts when constructed with zero address owner - function testRevert_LiFiDEXAggregatorDiamondConstructedWithZeroAddressOwner() - public - { - vm.expectRevert(InvalidConfig.selector); - new LiFiDEXAggregatorDiamond( - address(0), // This should trigger InvalidConfig - address(diamondCutFacet) - ); - } } diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/utils/DiamondTest.sol index fe2dad81e..c1cc6be4d 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/utils/DiamondTest.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.17; import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { CommonDiamondTest } from "./CommonDiamondTest.sol"; +import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; contract DiamondTest is CommonDiamondTest { function setUp() public virtual override { @@ -22,4 +23,13 @@ contract DiamondTest is CommonDiamondTest { "Diamond should be deployed" ); } + + /// @notice Test that LiFiDiamond reverts when constructed with zero address owner + function testRevert_LiFiDiamondConstructedWithZeroAddressOwner() public { + vm.expectRevert(InvalidConfig.selector); + new LiFiDiamond( + address(0), // This should trigger InvalidConfig + address(diamondCutFacet) + ); + } } diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index 70ccf7bfd..b90760e42 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -14,7 +14,7 @@ import { stdJson } from "forge-std/StdJson.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { LiFiDEXAggregatorDiamondTest } from "./LiFiDEXAggregatorDiamondTest.sol"; +import { DEXAggregatorDiamondTest } from "./DEXAggregatorDiamondTest.sol"; using stdJson for string; @@ -97,7 +97,7 @@ abstract contract TestBase is TestBaseForksConstants, TestBaseRandomConstants, TestHelpers, - LiFiDEXAggregatorDiamondTest, + DEXAggregatorDiamondTest, ILiFi { address internal _facetTestContractAddress; @@ -239,7 +239,7 @@ abstract contract TestBase is weth = ERC20(ADDRESS_WRAPPED_NATIVE); // deploy & configure LiFiDiamond and LiFiDEXAggregatorDiamond - LiFiDEXAggregatorDiamondTest.setUp(); + DEXAggregatorDiamondTest.setUp(); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); diff --git a/test/solidity/utils/TestBaseRandomConstants.sol b/test/solidity/utils/TestBaseRandomConstants.sol index c2ae61fec..4fad2acdd 100644 --- a/test/solidity/utils/TestBaseRandomConstants.sol +++ b/test/solidity/utils/TestBaseRandomConstants.sol @@ -8,5 +8,4 @@ abstract contract TestBaseRandomConstants { address internal constant USER_PAUSER = address(0xdeadbeef); address internal constant USER_DIAMOND_OWNER = 0x5042255A3F3FD7727e419CeA387cAFDfad3C3aF8; - address internal constant USER_LDA_DIAMOND_OWNER = address(0x123457); } From 01bba97b6613658bf380ea404c3286a2aeccb848 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 3 Sep 2025 18:49:38 +0200 Subject: [PATCH 156/220] added blank space --- src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol index bed1beb80..18e1c93ca 100644 --- a/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol +++ b/src/Periphery/LDA/LiFiDEXAggregatorDiamond.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { LiFiDiamond } from "../../LiFiDiamond.sol"; - + /// @title LiFiDEXAggregatorDiamond /// @author LI.FI (https://li.fi) /// @notice Base EIP-2535 Diamond Proxy Contract for LDA (LiFi DEX Aggregator). From 675217b698307c6e08b00fff2829832bd5734985 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 4 Sep 2025 14:52:36 +0200 Subject: [PATCH 157/220] Refactor deployment scripts and configurations to transition from LDA to LiFiDEXAggregator. Update global.json to reflect new facet names and addresses. Remove deprecated deployLDACoreFacets script and adjust related functions for improved clarity and maintainability. Ensure all references to LDADiamond are replaced with LiFiDEXAggregatorDiamond across scripts and tests. --- config/global.json | 12 +- deployments/_deployments_log_file.json | 124 ++++++++++- deployments/arbitrum.lda.staging.json | 22 +- script/deploy/deployAllLDAContracts.sh | 201 ++++++++++++++++-- .../deploy/deployFacetAndAddToLDADiamond.sh | 4 +- script/deploy/deployLDACoreFacets.sh | 70 ------ .../facets/LDA/DeployKatanaV3Facet.s.sol | 13 ++ .../LDA/DeployLiFiDEXAggregatorDiamond.s.sol | 56 +++-- .../LDA/UpdateCoreRouteFacet.s.sol} | 2 +- .../LDA/UpdateKatanaV3Facet.s.sol} | 2 +- .../facets/LDA/UpdateLDACoreFacets.s.sol | 97 +++++++-- .../facets/LDA/UpdateVelodromeV2Facet.s.sol | 13 ++ .../facets/LDA/utils/UpdateLDAScriptBase.sol | 20 +- script/deploy/ldaHealthCheck.ts | 58 ++--- .../LDA/DeployKatanaV3Facet.zksync.s.sol | 13 ++ .../zksync/LDA/DeployLDADiamond.zksync.s.sol | 56 ----- .../LDA/DeployLDADiamondCutFacet.zksync.s.sol | 15 -- .../DeployLDADiamondLoupeFacet.zksync.s.sol | 15 -- .../DeployLDAEmergencyPauseFacet.zksync.s.sol | 32 --- .../LDA/DeployLDAOwnershipFacet.zksync.s.sol | 15 -- ...ployLDAPeripheryRegistryFacet.zksync.s.sol | 15 -- ...eployLiFiDEXAggregatorDiamond.zksync.s.sol | 71 +++++++ .../LDA/UpdateLDACoreFacets.zksync.s.sol | 97 +++++++-- .../zksync/LDA/utils/UpdateLDAScriptBase.sol | 18 +- script/helperFunctions.sh | 34 --- script/scriptMaster.sh | 15 +- script/tasks/ldaDiamondUpdateFacet.sh | 10 +- 27 files changed, 676 insertions(+), 424 deletions(-) delete mode 100644 script/deploy/deployLDACoreFacets.sh create mode 100644 script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol rename script/deploy/{zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol => facets/LDA/UpdateCoreRouteFacet.s.sol} (87%) rename script/deploy/{zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol => facets/LDA/UpdateKatanaV3Facet.s.sol} (85%) create mode 100644 script/deploy/facets/LDA/UpdateVelodromeV2Facet.s.sol create mode 100644 script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol delete mode 100644 script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol delete mode 100644 script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol delete mode 100644 script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol delete mode 100644 script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol delete mode 100644 script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol delete mode 100644 script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol diff --git a/config/global.json b/config/global.json index 5bc3d9936..aab029afd 100644 --- a/config/global.json +++ b/config/global.json @@ -68,14 +68,8 @@ "TokenWrapper" ], "ldaCoreFacets": [ - "LDADiamondCutFacet", - "LDADiamondLoupeFacet", - "LDAOwnershipFacet", - "LDAEmergencyPauseFacet", - "LDAPeripheryRegistryFacet", - "CoreRouteFacet", - "NativeWrapperFacet", - "UniV3StyleFacet", - "UniV2StyleFacet" + "DiamondCutFacet", + "DiamondLoupeFacet", + "OwnershipFacet" ] } diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 167b6871f..05169bf1c 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39959,10 +39959,10 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", + "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:37:53", - "CONSTRUCTOR_ARGS": "0x", + "TIMESTAMP": "2025-09-04 14:02:46", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -40006,9 +40006,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x414fc3eB441664807027346817dcBe8490938C78", + "ADDRESS": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:39:16", + "TIMESTAMP": "2025-09-04 14:27:13", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40038,9 +40038,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", + "ADDRESS": "0x181a353054883D9DdE6864Ba074226E5b77cf511", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:40:41", + "TIMESTAMP": "2025-09-04 14:26:48", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40102,9 +40102,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", + "ADDRESS": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 16:24:13", + "TIMESTAMP": "2025-09-04 14:26:09", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40149,9 +40149,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x2688a406d7F7A99C7448d959371063275CfC2E81", + "ADDRESS": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:41:17", + "TIMESTAMP": "2025-09-04 14:05:55", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40175,5 +40175,107 @@ ] } } + }, + "LiFiDEXAggregatorDiamond": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 13:07:35", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000b2a8517734cdf985d53f303a1f7759a34fdc772f", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "AlgebraFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 14:02:26", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "CurveFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 14:05:34", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "KatanaV3Facet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 14:08:38", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "SyncSwapV2Facet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 14:26:30", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } + }, + "VelodromeV2Facet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 14:27:34", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + } } } diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json index 1af232b74..07b73728f 100644 --- a/deployments/arbitrum.lda.staging.json +++ b/deployments/arbitrum.lda.staging.json @@ -1,13 +1,13 @@ { - "LDADiamondCutFacet": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", - "LDADiamondLoupeFacet": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", - "LDAOwnershipFacet": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", - "LDAEmergencyPauseFacet": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", - "LDAPeripheryRegistryFacet": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", - "CoreRouteFacet": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", - "UniV3StyleFacet": "0x414fc3eB441664807027346817dcBe8490938C78", - "UniV2StyleFacet": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", - "LDADiamond": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", - "NativeWrapperFacet": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", - "IzumiV3Facet": "0x2688a406d7F7A99C7448d959371063275CfC2E81" + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" } \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index f876050b3..5b585538c 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -1,5 +1,99 @@ #!/bin/bash +# Function to check if all LDA core facets are deployed +checkLDACoreFacetsExist() { + local NETWORK="$1" + local ENVIRONMENT="$2" + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start checkLDACoreFacetsExist" + + # get file suffix based on value in variable ENVIRONMENT + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # Check if regular network deployment file exists (where core facets should be) + local REGULAR_DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" + if [[ ! -f "$REGULAR_DEPLOYMENT_FILE" ]]; then + echo "" + echo "[ERROR] ❌ LiFiDEXAggregator deployment failed!" + echo "[ERROR] Regular network deployment file not found: $REGULAR_DEPLOYMENT_FILE" + echo "[ERROR] LDA core facets are deployed as part of the regular LiFi Diamond deployment." + echo "[ERROR] Please deploy the regular LiFi Diamond first using option 3 in the script master menu." + echo "[ERROR] This will deploy the core facets that LDA Diamond requires." + echo "" + return 1 + fi + + # Read LDA core facets from global.json config file + local GLOBAL_CONFIG_PATH="./config/global.json" + if [[ ! -f "$GLOBAL_CONFIG_PATH" ]]; then + error "Global config file not found: $GLOBAL_CONFIG_PATH" + return 1 + fi + + # Extract LDA core facets from JSON config + local LDA_CORE_FACETS_JSON=$(jq -r '.ldaCoreFacets[]' "$GLOBAL_CONFIG_PATH") + local LDA_CORE_FACETS=() + while IFS= read -r facet; do + LDA_CORE_FACETS+=("$facet") + done <<< "$LDA_CORE_FACETS_JSON" + + echo "[info] Found ${#LDA_CORE_FACETS[@]} LDA core facets in config: ${LDA_CORE_FACETS[*]}" + echo "[info] Checking for core facets in regular deployment file: $REGULAR_DEPLOYMENT_FILE" + + local MISSING_FACETS=() + + # Check each LDA core facet exists in regular deployment logs (not LDA-specific logs) + for FACET_NAME in "${LDA_CORE_FACETS[@]}"; do + echo "[info] Checking if LDA core facet exists: $FACET_NAME" + + # Check if facet address exists in regular deployment logs (shared with regular LiFi Diamond) + local FACET_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_NAME") + + if [[ -z "$FACET_ADDRESS" ]]; then + echo "[error] LDA core facet $FACET_NAME not found in regular deployment logs for network $NETWORK" + MISSING_FACETS+=("$FACET_NAME") + else + echo "[info] ✅ LDA core facet $FACET_NAME found at address: $FACET_ADDRESS" + fi + done + + # If any facets are missing, fail the deployment + if [[ ${#MISSING_FACETS[@]} -gt 0 ]]; then + echo "" + echo "[ERROR] ❌ LiFiDEXAggregator deployment failed!" + echo "[ERROR] The following LDA core facets are missing from network $NETWORK regular deployment logs:" + for missing_facet in "${MISSING_FACETS[@]}"; do + echo "[ERROR] - $missing_facet" + done + echo "" + echo "[ERROR] LDA core facets are deployed as part of the regular LiFi Diamond deployment." + echo "[ERROR] Please deploy the regular LiFi Diamond first using option 3 in the script master menu." + echo "[ERROR] This will deploy the core facets (DiamondCutFacet, DiamondLoupeFacet, OwnershipFacet) that LDA Diamond requires." + echo "" + return 1 + fi + + echo "[info] ✅ All LDA core facets are available in regular deployment logs" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< checkLDACoreFacetsExist completed" + + return 0 +} + +# Function to get LDA facet contract names from the LDA Facets directory +getLDAFacetContractNames() { + local LDA_FACETS_PATH="src/Periphery/LDA/Facets/" + + # Check if the LDA Facets directory exists + if [ ! -d "$LDA_FACETS_PATH" ]; then + error "LDA Facets directory not found: $LDA_FACETS_PATH" + return 1 + fi + + # Get contract names using the existing helper function + local LDA_FACETS=$(getContractNamesInFolder "$LDA_FACETS_PATH") + echo "$LDA_FACETS" +} + deployAllLDAContracts() { echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployAllLDAContracts" @@ -7,7 +101,6 @@ deployAllLDAContracts() { source script/config.sh source script/helperFunctions.sh source script/deploy/deployAndStoreCREATE3Factory.sh - source script/deploy/deployLDACoreFacets.sh source script/deploy/deployFacetAndAddToLDADiamond.sh source script/tasks/ldaDiamondUpdateFacet.sh @@ -34,10 +127,11 @@ deployAllLDAContracts() { START_FROM=$( gum choose \ "1) Initial setup and CREATE3Factory deployment" \ - "2) Deploy LDA core facets" \ + "2) Check LDA core facets availability" \ "3) Deploy LDA diamond and update with core facets" \ - "4) Run LDA health check only" \ - "5) Ownership transfer to timelock (production only)" + "4) Deploy non-core LDA facets and add to diamond" \ + "5) Run LDA health check only" \ + "6) Ownership transfer to timelock (production only)" ) # Extract the stage number from the selection @@ -51,6 +145,8 @@ deployAllLDAContracts() { START_STAGE=4 elif [[ "$START_FROM" == *"5)"* ]]; then START_STAGE=5 + elif [[ "$START_FROM" == *"6)"* ]]; then + START_STAGE=6 else error "invalid selection: $START_FROM - exiting script now" exit 1 @@ -60,7 +156,7 @@ deployAllLDAContracts() { echo "" # LDA Diamond contract name - local LDA_DIAMOND_CONTRACT_NAME="LDADiamond" + local LDA_DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" # Stage 1: Initial setup and CREATE3Factory deployment if [[ $START_STAGE -le 1 ]]; then @@ -121,13 +217,16 @@ deployAllLDAContracts() { echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 1 completed" fi - # Stage 2: Deploy LDA core facets + # Stage 2: Check LDA core facets availability (instead of deploying them) if [[ $START_STAGE -le 2 ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 2: Deploy LDA core facets" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 2: Check LDA core facets availability" - # deploy LDA core facets - deployLDACoreFacets "$NETWORK" "$ENVIRONMENT" + # check if LDA core facets are available in deployment logs + checkLDACoreFacetsExist "$NETWORK" "$ENVIRONMENT" + + # check if last command was executed successfully, otherwise exit script with error message + checkFailure $? "verify LDA core facets availability for network $NETWORK" echo "" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 2 completed" @@ -162,37 +261,95 @@ deployAllLDAContracts() { echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 3 completed" fi - # Stage 4: Run LDA health check + # Stage 4: Deploy non-core LDA facets and add to diamond if [[ $START_STAGE -le 4 ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Run LDA health check only" - bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Deploy non-core LDA facets and add to diamond" + + # Get all LDA facet contract names from the LDA Facets directory + local LDA_FACETS_PATH="src/Periphery/LDA/Facets/" + echo "[info] Getting LDA facets from directory: $LDA_FACETS_PATH" + + # Read LDA core facets to exclude them from non-core deployment + local GLOBAL_CONFIG_PATH="./config/global.json" + local LDA_CORE_FACETS_JSON=$(jq -r '.ldaCoreFacets[]' "$GLOBAL_CONFIG_PATH") + local LDA_CORE_FACETS=() + while IFS= read -r facet; do + LDA_CORE_FACETS+=("$facet") + done <<< "$LDA_CORE_FACETS_JSON" + + # prepare regExp to exclude LDA core facets + local EXCLUDED_LDA_FACETS_REGEXP="^($(echo "${LDA_CORE_FACETS[@]}" | tr ' ' '|'))$" + + echo "[info] LDA core facets to exclude: ${LDA_CORE_FACETS[*]}" + echo "[info] Exclusion regex: $EXCLUDED_LDA_FACETS_REGEXP" + + # Deploy all non-core LDA facets and add to diamond + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now deploying non-core LDA facets and adding to diamond contract" + + # loop through LDA facet contract names + for FACET_NAME in $(getContractNamesInFolder "$LDA_FACETS_PATH"); do + echo "[info] Processing LDA facet: $FACET_NAME" + + # Skip if this is a core facet (already handled in previous stages) + if [[ "$FACET_NAME" =~ $EXCLUDED_LDA_FACETS_REGEXP ]]; then + echo "[info] Skipping LDA core facet: $FACET_NAME (already handled)" + continue + fi + + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying non-core LDA facet: $FACET_NAME" + + # get current contract version + local FACET_VERSION=$(getCurrentContractVersion "$FACET_NAME") + + # deploy LDA facet and add to diamond + deployFacetAndAddToLDADiamond "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$LDA_DIAMOND_CONTRACT_NAME" "$FACET_VERSION" + + # check if deployment was successful + if [ $? -eq 0 ]; then + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA facet $FACET_NAME successfully deployed and added to $LDA_DIAMOND_CONTRACT_NAME" + else + error "failed to deploy and add LDA facet $FACET_NAME to $LDA_DIAMOND_CONTRACT_NAME on network $NETWORK" + return 1 + fi + done + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< non-core LDA facets deployment completed" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 4 completed" + fi + + # Stage 5: Run LDA health check + if [[ $START_STAGE -le 5 ]]; then + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Run LDA health check only" + bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" # Pause and ask user if they want to continue with ownership transfer (for production) - if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 4 ]]; then + if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 5 ]]; then echo "" echo "Health check completed. Do you want to continue with ownership transfer to timelock?" echo "This should only be done if the health check shows only diamond ownership errors." - echo "Continue with stage 5 (ownership transfer)? (y/n)" + echo "Continue with stage 6 (ownership transfer)? (y/n)" read -r response if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - echo "Proceeding with stage 5..." + echo "Proceeding with stage 6..." else - echo "Skipping stage 5 - ownership transfer cancelled by user" + echo "Skipping stage 6 - ownership transfer cancelled by user" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" return fi fi fi - # Stage 5: Ownership transfer to timelock (production only) - if [[ $START_STAGE -le 5 ]]; then + # Stage 6: Ownership transfer to timelock (production only) + if [[ $START_STAGE -le 6 ]]; then if [[ "$ENVIRONMENT" == "production" ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Ownership transfer to timelock (production only)" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Ownership transfer to timelock (production only)" - # make sure SAFE_ADDRESS is available (if starting in stage 5 it's not available yet) + # make sure SAFE_ADDRESS is available (if starting in stage 6 it's not available yet) SAFE_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.safeAddress") if [[ -z "$SAFE_ADDRESS" || "$SAFE_ADDRESS" == "null" ]]; then echo "SAFE address not found in networks.json. Cannot prepare ownership transfer to Timelock" @@ -230,10 +387,10 @@ deployAllLDAContracts() { echo "" # ------------------------------------------------------------ else - echo "Stage 5 skipped - ownership transfer to timelock is only for production environment" + echo "Stage 6 skipped - ownership transfer to timelock is only for production environment" fi - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 6 completed" fi echo "" diff --git a/script/deploy/deployFacetAndAddToLDADiamond.sh b/script/deploy/deployFacetAndAddToLDADiamond.sh index c1cb717d1..2626b7e1a 100644 --- a/script/deploy/deployFacetAndAddToLDADiamond.sh +++ b/script/deploy/deployFacetAndAddToLDADiamond.sh @@ -1,6 +1,6 @@ #!/bin/bash -# deploys an LDA facet contract and adds it to LDADiamond contract +# deploys an LDA facet contract and adds it to LiFiDEXAggregatorDiamond contract function deployFacetAndAddToLDADiamond() { # load env variables source .env @@ -66,7 +66,7 @@ function deployFacetAndAddToLDADiamond() { # if no DIAMOND_CONTRACT_NAME was passed to this function, default to LDADiamond if [[ -z "$DIAMOND_CONTRACT_NAME" ]]; then - DIAMOND_CONTRACT_NAME="LDADiamond" + DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" fi # get LDA diamond address from deployments script (using .lda. file suffix) diff --git a/script/deploy/deployLDACoreFacets.sh b/script/deploy/deployLDACoreFacets.sh deleted file mode 100644 index 50818a3db..000000000 --- a/script/deploy/deployLDACoreFacets.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -# deploys LDA core facets -deployLDACoreFacets() { - # load config & helper functions - source script/config.sh - source script/helperFunctions.sh - source script/deploy/deploySingleContract.sh - - # read function arguments into variables - local NETWORK="$1" - local ENVIRONMENT="$2" - - # load env variables - source .env - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployLDACoreFacets" - - # get file suffix based on value in variable ENVIRONMENT - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - - # logging for debug purposes - echo "" - echoDebug "in function deployLDACoreFacets" - echoDebug "NETWORK=$NETWORK" - echoDebug "ENVIRONMENT=$ENVIRONMENT" - echoDebug "FILE_SUFFIX=$FILE_SUFFIX" - echo "" - - # Read LDA core facets from global.json config file - local GLOBAL_CONFIG_PATH="./config/global.json" - if [[ ! -f "$GLOBAL_CONFIG_PATH" ]]; then - error "Global config file not found: $GLOBAL_CONFIG_PATH" - return 1 - fi - - # Extract LDA core facets from JSON config - local LDA_CORE_FACETS_JSON=$(jq -r '.ldaCoreFacets[]' "$GLOBAL_CONFIG_PATH") - local LDA_CORE_FACETS=() - while IFS= read -r facet; do - LDA_CORE_FACETS+=("$facet") - done <<< "$LDA_CORE_FACETS_JSON" - - echo "[info] Found ${#LDA_CORE_FACETS[@]} LDA core facets in config: ${LDA_CORE_FACETS[*]}" - - # loop through LDA core facets and deploy them - for FACET_NAME in "${LDA_CORE_FACETS[@]}"; do - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying LDA core facet: $FACET_NAME" - - # get current contract version - local VERSION=$(getCurrentContractVersion "$FACET_NAME") - - # deploy the LDA core facet - deploySingleContract "$FACET_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" "false" "true" - - # check if last command was executed successfully - if [ $? -eq 0 ]; then - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA core facet $FACET_NAME successfully deployed" - else - error "failed to deploy LDA core facet $FACET_NAME to network $NETWORK" - return 1 - fi - done - - echo "" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployLDACoreFacets completed" - - return 0 -} diff --git a/script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol b/script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol new file mode 100644 index 000000000..3eb0af671 --- /dev/null +++ b/script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { KatanaV3Facet } from "lifi/Periphery/LDA/Facets/KatanaV3Facet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("KatanaV3Facet") {} + + function run() public returns (KatanaV3Facet deployed) { + deployed = KatanaV3Facet(deploy(type(KatanaV3Facet).creationCode)); + } +} diff --git a/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol index b121b8d27..aef1609c1 100644 --- a/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol +++ b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; -contract DeployScript is DeployLDAScriptBase { +contract DeployScript is DeployScriptBase { using stdJson for string; constructor() DeployScriptBase("LiFiDEXAggregatorDiamond") {} @@ -24,38 +24,52 @@ contract DeployScript is DeployLDAScriptBase { } function getConstructorArgs() internal override returns (bytes memory) { - // Check if fileSuffix already contains "lda." to avoid double prefix - string memory ldaPrefix = ""; + // LDA Diamond uses DiamondCutFacet from regular deployment (shared with regular LiFi Diamond) + // Need to construct regular deployment path by removing "lda." prefix from fileSuffix + + string memory regularFileSuffix; bytes memory fileSuffixBytes = bytes(fileSuffix); - bool hasLdaPrefix = false; - - // Check if fileSuffix starts with "lda." - if (fileSuffixBytes.length >= 4) { - hasLdaPrefix = (fileSuffixBytes[0] == "l" && - fileSuffixBytes[1] == "d" && - fileSuffixBytes[2] == "a" && - fileSuffixBytes[3] == "."); - } - if (!hasLdaPrefix) { - ldaPrefix = ".lda."; + // Check if fileSuffix starts with "lda." and remove it + if ( + fileSuffixBytes.length >= 4 && + fileSuffixBytes[0] == "l" && + fileSuffixBytes[1] == "d" && + fileSuffixBytes[2] == "a" && + fileSuffixBytes[3] == "." + ) { + // Extract everything after "lda." by creating new bytes array + bytes memory remainingBytes = new bytes( + fileSuffixBytes.length - 4 + ); + for (uint256 i = 4; i < fileSuffixBytes.length; i++) { + remainingBytes[i - 4] = fileSuffixBytes[i]; + } + regularFileSuffix = string(remainingBytes); } else { - ldaPrefix = "."; + // If no "lda." prefix, use as is + regularFileSuffix = fileSuffix; } - string memory path = string.concat( + string memory regularPath = string.concat( root, "/deployments/", network, - ldaPrefix, - fileSuffix, + ".", + regularFileSuffix, "json" ); + + emit log_named_string("regularPath", regularPath); + + // Get DiamondCutFacet address from regular deployment file address diamondCut = _getConfigContractAddress( - path, - ".LDADiamondCutFacet" + regularPath, + ".DiamondCutFacet" ); + emit log_named_address("diamondCut", diamondCut); + return abi.encode(deployerAddress, diamondCut); } } diff --git a/script/deploy/zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol b/script/deploy/facets/LDA/UpdateCoreRouteFacet.s.sol similarity index 87% rename from script/deploy/zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol rename to script/deploy/facets/LDA/UpdateCoreRouteFacet.s.sol index 2c033f696..b0ea64657 100644 --- a/script/deploy/zksync/LDA/UpdateLDAOwnershipFacet.zksync.s.sol +++ b/script/deploy/facets/LDA/UpdateCoreRouteFacet.s.sol @@ -8,6 +8,6 @@ contract DeployScript is UpdateLDAScriptBase { public returns (address[] memory facets, bytes memory cutData) { - return update("LDAOwnershipFacet"); + return update("CoreRouteFacet"); } } diff --git a/script/deploy/zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol b/script/deploy/facets/LDA/UpdateKatanaV3Facet.s.sol similarity index 85% rename from script/deploy/zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol rename to script/deploy/facets/LDA/UpdateKatanaV3Facet.s.sol index 2c52caa13..c6328a577 100644 --- a/script/deploy/zksync/LDA/UpdateLDAEmergencyPauseFacet.zksync.s.sol +++ b/script/deploy/facets/LDA/UpdateKatanaV3Facet.s.sol @@ -8,6 +8,6 @@ contract DeployScript is UpdateLDAScriptBase { public returns (address[] memory facets, bytes memory cutData) { - return update("LDAEmergencyPauseFacet"); + return update("KatanaV3Facet"); } } diff --git a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol index 87b55f810..0ca40b2a0 100644 --- a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol +++ b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { IERC173 } from "lifi/Interfaces/IERC173.sol"; import { TransferrableOwnership } from "lifi/Helpers/TransferrableOwnership.sol"; @@ -39,11 +39,66 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { return emptyExcludes; } + /// @notice Get regular deployment file path (without lda. prefix) + function getRegularDeploymentPath() internal view returns (string memory) { + // Need to construct regular deployment path by removing "lda." prefix from fileSuffix + string memory regularFileSuffix; + bytes memory fileSuffixBytes = bytes(fileSuffix); + + // Check if fileSuffix starts with "lda." and remove it + if ( + fileSuffixBytes.length >= 4 && + fileSuffixBytes[0] == "l" && + fileSuffixBytes[1] == "d" && + fileSuffixBytes[2] == "a" && + fileSuffixBytes[3] == "." + ) { + // Extract everything after "lda." by creating new bytes array + bytes memory remainingBytes = new bytes( + fileSuffixBytes.length - 4 + ); + for (uint256 i = 4; i < fileSuffixBytes.length; i++) { + remainingBytes[i - 4] = fileSuffixBytes[i]; + } + regularFileSuffix = string(remainingBytes); + } else { + // If no "lda." prefix, use as is + regularFileSuffix = fileSuffix; + } + + return + string.concat( + root, + "/deployments/", + network, + ".", + regularFileSuffix, + "json" + ); + } + + /// @notice Override getSelectors to use the correct contract-selectors script + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal override returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; // Use regular contract-selectors script + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + function run() public returns (address[] memory facets, bytes memory cutData) { - // Read LDA core facets dynamically from lda-global.json config + // Read LDA core facets dynamically from global.json config string memory ldaGlobalConfigPath = string.concat( vm.projectRoot(), "/config/global.json" @@ -56,26 +111,34 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log("LDA core facets found in config/global.json: "); emit log_uint(ldaCoreFacets.length); + // Get regular deployment path for reading core facets + string memory regularDeploymentPath = getRegularDeploymentPath(); + emit log_named_string( + "Reading core facets from regular deployment file", + regularDeploymentPath + ); + // Check if the LDA loupe was already added to the diamond bool loupeExists; try loupe.facetAddresses() returns (address[] memory) { // If call was successful, loupe exists on LDA diamond already - emit log("LDA Loupe exists on diamond already"); + emit log("DiamondLoupeFacet exists on diamond already"); loupeExists = true; } catch { // No need to do anything, just making sure that the flow continues in both cases with try/catch } - // Handle LDADiamondLoupeFacet separately as it needs special treatment + // Handle DiamondLoupeFacet separately as it needs special treatment if (!loupeExists) { - emit log("LDA Loupe does not exist on diamond yet"); + emit log("DiamondLoupeFacet does not exist on diamond yet"); + // Read DiamondLoupeFacet from regular deployment file address ldaDiamondLoupeAddress = _getConfigContractAddress( - path, - ".LDADiamondLoupeFacet" + regularDeploymentPath, + ".DiamondLoupeFacet" ); bytes4[] memory loupeSelectors = getSelectors( - "LDADiamondLoupeFacet", - getExcludes("LDADiamondLoupeFacet") + "DiamondLoupeFacet", + getExcludes("DiamondLoupeFacet") ); buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); @@ -93,26 +156,26 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { for (uint256 i = 0; i < ldaCoreFacets.length; i++) { string memory facetName = ldaCoreFacets[i]; - // Skip LDADiamondCutFacet and LDADiamondLoupeFacet as they were already handled + // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled if ( keccak256(bytes(facetName)) == - keccak256(bytes("LDADiamondLoupeFacet")) + keccak256(bytes("DiamondLoupeFacet")) ) { continue; } - // Skip LDADiamondCutFacet as it was already handled during LDA diamond deployment + // Skip DiamondCutFacet as it was already handled during LDA diamond deployment if ( keccak256(bytes(facetName)) == - keccak256(bytes("LDADiamondCutFacet")) + keccak256(bytes("DiamondCutFacet")) ) { continue; } - emit log("Now adding LDA facet: "); + emit log("Now adding LDA core facet: "); emit log(facetName); - // Use _getConfigContractAddress which validates the contract exists + // Read core facets from regular deployment file, not LDA file address facetAddress = _getConfigContractAddress( - path, + regularDeploymentPath, string.concat(".", facetName) ); bytes4[] memory selectors = getSelectors( @@ -128,7 +191,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { if (noBroadcast) { if (cut.length > 0) { cutData = abi.encodeWithSelector( - LDADiamondCutFacet.diamondCut.selector, + DiamondCutFacet.diamondCut.selector, cut, address(0), "" diff --git a/script/deploy/facets/LDA/UpdateVelodromeV2Facet.s.sol b/script/deploy/facets/LDA/UpdateVelodromeV2Facet.s.sol new file mode 100644 index 000000000..2f75bf760 --- /dev/null +++ b/script/deploy/facets/LDA/UpdateVelodromeV2Facet.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("VelodromeV2Facet"); + } +} diff --git a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol index d6876b1b4..7ec64c5d4 100644 --- a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.17; import { ScriptBase } from "../../utils/ScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; -import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; contract UpdateLDAScriptBase is ScriptBase { @@ -17,8 +17,8 @@ contract UpdateLDAScriptBase is ScriptBase { bytes4[] internal selectorsToReplace; bytes4[] internal selectorsToRemove; bytes4[] internal selectorsToAdd; - LDADiamondCutFacet internal cutter; - LDADiamondLoupeFacet internal loupe; + DiamondCutFacet internal cutter; + DiamondLoupeFacet internal loupe; string internal path; string internal json; bool internal noBroadcast = false; @@ -38,9 +38,9 @@ contract UpdateLDAScriptBase is ScriptBase { json = vm.readFile(path); // Get LDA Diamond address (not LiFi Diamond) - ldaDiamond = json.readAddress(".LDADiamond"); - cutter = LDADiamondCutFacet(ldaDiamond); - loupe = LDADiamondLoupeFacet(ldaDiamond); + ldaDiamond = json.readAddress(".LiFiDEXAggregatorDiamond"); + cutter = DiamondCutFacet(ldaDiamond); + loupe = DiamondLoupeFacet(ldaDiamond); } function update( @@ -60,7 +60,7 @@ contract UpdateLDAScriptBase is ScriptBase { // prepare full diamondCut calldata and log for debugging purposes if (cut.length > 0) { cutData = abi.encodeWithSelector( - LDADiamondCutFacet.diamondCut.selector, + DiamondCutFacet.diamondCut.selector, cut, callData.length > 0 ? facet : address(0), callData @@ -97,9 +97,9 @@ contract UpdateLDAScriptBase is ScriptBase { function getSelectors( string memory _facetName, bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { + ) internal virtual returns (bytes4[] memory selectors) { string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; + cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; // Default to regular contract-selectors cmd[1] = _facetName; string memory exclude; for (uint256 i; i < _exclude.length; i++) { diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index 499468c67..0e1778614 100644 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -118,8 +118,9 @@ const main = defineCommand({ consola.warn('Some shared infrastructure checks will be skipped.') } } - } else - // Production - use main deployment file + } + // Production - use main deployment file + else try { const { default: contracts } = await import(mainDeploymentFile) mainDeployedContracts = contracts @@ -129,7 +130,6 @@ const main = defineCommand({ ) consola.warn('Some shared infrastructure checks will be skipped.') } - // Note: We keep LDA and main contracts separate for clarity @@ -157,17 +157,17 @@ const main = defineCommand({ // ╰─────────────────────────────────────────────────────────╯ consola.box('Checking LDADiamond Contract...') const diamondDeployed = await checkIsDeployedWithCast( - 'LDADiamond', + 'LiFiDEXAggregatorDiamond', ldaDeployedContracts, rpcUrl ) if (!diamondDeployed) { - logError('LDADiamond not deployed') + logError('LiFiDEXAggregatorDiamond not deployed') finish() - } else consola.success('LDADiamond deployed') + } else consola.success('LiFiDEXAggregatorDiamond deployed') - const diamondAddress = ldaDeployedContracts['LDADiamond'] + const diamondAddress = ldaDeployedContracts['LiFiDEXAggregatorDiamond'] // ╭─────────────────────────────────────────────────────────╮ // │ Check LDA core facets │ @@ -240,45 +240,47 @@ const main = defineCommand({ { encoding: 'utf8', stdio: 'pipe' } ).trim() - consola.info(`LDADiamond current owner: ${owner}`) + consola.info(`LiFiDEXAggregatorDiamond current owner: ${owner}`) // Check if timelock is deployed and compare (timelock is in main deployments, not LDA deployments) const timelockAddress = mainDeployedContracts['LiFiTimelockController'] if (timelockAddress) { consola.info(`Found LiFiTimelockController at: ${timelockAddress}`) - if (owner.toLowerCase() === timelockAddress.toLowerCase()) - consola.success('LDADiamond is owned by LiFiTimelockController') - else - if (environment === 'production') - consola.error( - `LDADiamond owner is ${owner}, expected ${timelockAddress}` - ) - else { - consola.warn( - `LDADiamond owner is ${owner}, expected ${timelockAddress} for production` - ) - consola.info( - 'For staging environment, ownership transfer to timelock is typically done later' - ) - } - + if (owner.toLowerCase() === timelockAddress.toLowerCase()) + consola.success( + 'LiFiDEXAggregatorDiamond is owned by LiFiTimelockController' + ) + else if (environment === 'production') + consola.error( + `LiFiDEXAggregatorDiamond owner is ${owner}, expected ${timelockAddress}` + ) + else { + consola.warn( + `LiFiDEXAggregatorDiamond owner is ${owner}, expected ${timelockAddress} for production` + ) + consola.info( + 'For staging environment, ownership transfer to timelock is typically done later' + ) + } } else { - if (environment === 'production') + if (environment === 'production') consola.error( 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' ) - else + else consola.warn( 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' ) - + consola.info( 'Note: LiFiTimelockController should be deployed as shared infrastructure before LDA deployment' ) } } catch (error) { logError( - `Failed to check LDADiamond ownership: ${(error as Error).message}` + `Failed to check LiFiDEXAggregatorDiamond ownership: ${ + (error as Error).message + }` ) } diff --git a/script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol new file mode 100644 index 000000000..3eb0af671 --- /dev/null +++ b/script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { KatanaV3Facet } from "lifi/Periphery/LDA/Facets/KatanaV3Facet.sol"; + +contract DeployScript is DeployLDAScriptBase { + constructor() DeployLDAScriptBase("KatanaV3Facet") {} + + function run() public returns (KatanaV3Facet deployed) { + deployed = KatanaV3Facet(deploy(type(KatanaV3Facet).creationCode)); + } +} diff --git a/script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol deleted file mode 100644 index f60eeb285..000000000 --- a/script/deploy/zksync/LDA/DeployLDADiamond.zksync.s.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; -import { stdJson } from "forge-std/Script.sol"; -import { LDADiamond } from "lifi/Periphery/LDA/LDADiamond.sol"; - -contract DeployScript is DeployLDAScriptBase { - using stdJson for string; - - constructor() DeployLDAScriptBase("LDADiamond") {} - - function run() - public - returns (LDADiamond deployed, bytes memory constructorArgs) - { - constructorArgs = getConstructorArgs(); - deployed = LDADiamond(deploy(type(LDADiamond).creationCode)); - } - - function getConstructorArgs() internal override returns (bytes memory) { - // Check if fileSuffix already contains "lda." to avoid double prefix - string memory ldaPrefix = ""; - bytes memory fileSuffixBytes = bytes(fileSuffix); - bool hasLdaPrefix = false; - - // Check if fileSuffix starts with "lda." - if (fileSuffixBytes.length >= 4) { - hasLdaPrefix = (fileSuffixBytes[0] == "l" && - fileSuffixBytes[1] == "d" && - fileSuffixBytes[2] == "a" && - fileSuffixBytes[3] == "."); - } - - if (!hasLdaPrefix) { - ldaPrefix = ".lda."; - } else { - ldaPrefix = "."; - } - - string memory path = string.concat( - root, - "/deployments/", - network, - ldaPrefix, - fileSuffix, - "json" - ); - address diamondCut = _getConfigContractAddress( - path, - ".LDADiamondCutFacet" - ); - - return abi.encode(deployerAddress, diamondCut); - } -} diff --git a/script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol deleted file mode 100644 index 1294ed540..000000000 --- a/script/deploy/zksync/LDA/DeployLDADiamondCutFacet.zksync.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; - -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("LDADiamondCutFacet") {} - - function run() public returns (LDADiamondCutFacet deployed) { - deployed = LDADiamondCutFacet( - deploy(type(LDADiamondCutFacet).creationCode) - ); - } -} diff --git a/script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol deleted file mode 100644 index 7963855d9..000000000 --- a/script/deploy/zksync/LDA/DeployLDADiamondLoupeFacet.zksync.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; -import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; - -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("LDADiamondLoupeFacet") {} - - function run() public returns (LDADiamondLoupeFacet deployed) { - deployed = LDADiamondLoupeFacet( - deploy(type(LDADiamondLoupeFacet).creationCode) - ); - } -} diff --git a/script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol deleted file mode 100644 index ced775bde..000000000 --- a/script/deploy/zksync/LDA/DeployLDAEmergencyPauseFacet.zksync.s.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; -import { stdJson } from "forge-std/Script.sol"; -import { LDAEmergencyPauseFacet } from "lifi/Periphery/LDA/Facets/LDAEmergencyPauseFacet.sol"; - -contract DeployScript is DeployLDAScriptBase { - using stdJson for string; - - constructor() DeployLDAScriptBase("LDAEmergencyPauseFacet") {} - - function run() - public - returns (LDAEmergencyPauseFacet deployed, bytes memory constructorArgs) - { - constructorArgs = getConstructorArgs(); - - deployed = LDAEmergencyPauseFacet( - deploy(type(LDAEmergencyPauseFacet).creationCode) - ); - } - - function getConstructorArgs() internal override returns (bytes memory) { - string memory path = string.concat(root, "/config/global.json"); - string memory json = vm.readFile(path); - - address pauserWallet = json.readAddress(".pauserWallet"); - - return abi.encode(pauserWallet); - } -} diff --git a/script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol deleted file mode 100644 index 2e86201a2..000000000 --- a/script/deploy/zksync/LDA/DeployLDAOwnershipFacet.zksync.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; -import { LDAOwnershipFacet } from "lifi/Periphery/LDA/Facets/LDAOwnershipFacet.sol"; - -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("LDAOwnershipFacet") {} - - function run() public returns (LDAOwnershipFacet deployed) { - deployed = LDAOwnershipFacet( - deploy(type(LDAOwnershipFacet).creationCode) - ); - } -} diff --git a/script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol deleted file mode 100644 index 7ae636c20..000000000 --- a/script/deploy/zksync/LDA/DeployLDAPeripheryRegistryFacet.zksync.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; -import { LDAPeripheryRegistryFacet } from "lifi/Periphery/LDA/Facets/LDAPeripheryRegistryFacet.sol"; - -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("LDAPeripheryRegistryFacet") {} - - function run() public returns (LDAPeripheryRegistryFacet deployed) { - deployed = LDAPeripheryRegistryFacet( - deploy(type(LDAPeripheryRegistryFacet).creationCode) - ); - } -} diff --git a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol new file mode 100644 index 000000000..af10d48dd --- /dev/null +++ b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; + +contract DeployScript is DeployLDAScriptBase { + using stdJson for string; + + constructor() DeployLDAScriptBase("LiFiDEXAggregatorDiamond") {} + + function run() + public + returns ( + LiFiDEXAggregatorDiamond deployed, + bytes memory constructorArgs + ) + { + constructorArgs = getConstructorArgs(); + deployed = LiFiDEXAggregatorDiamond( + deploy(type(LiFiDEXAggregatorDiamond).creationCode) + ); + } + + function getConstructorArgs() internal override returns (bytes memory) { + // LDA Diamond uses DiamondCutFacet from regular deployment (shared with regular LiFi Diamond) + // Need to construct regular deployment path by removing "lda." prefix from fileSuffix + + string memory regularFileSuffix; + bytes memory fileSuffixBytes = bytes(fileSuffix); + + // Check if fileSuffix starts with "lda." and remove it + if ( + fileSuffixBytes.length >= 4 && + fileSuffixBytes[0] == "l" && + fileSuffixBytes[1] == "d" && + fileSuffixBytes[2] == "a" && + fileSuffixBytes[3] == "." + ) { + // Extract everything after "lda." by creating new bytes array + bytes memory remainingBytes = new bytes( + fileSuffixBytes.length - 4 + ); + for (uint256 i = 4; i < fileSuffixBytes.length; i++) { + remainingBytes[i - 4] = fileSuffixBytes[i]; + } + regularFileSuffix = string(remainingBytes); + } else { + // If no "lda." prefix, use as is + regularFileSuffix = fileSuffix; + } + + string memory regularPath = string.concat( + root, + "/deployments/", + network, + ".", + regularFileSuffix, + "json" + ); + + // Get DiamondCutFacet address from regular deployment file + address diamondCut = _getConfigContractAddress( + regularPath, + ".DiamondCutFacet" + ); + + return abi.encode(deployerAddress, diamondCut); + } +} diff --git a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol index 87b55f810..520a45f1c 100644 --- a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol +++ b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { IERC173 } from "lifi/Interfaces/IERC173.sol"; import { TransferrableOwnership } from "lifi/Helpers/TransferrableOwnership.sol"; @@ -39,11 +39,66 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { return emptyExcludes; } + /// @notice Override getSelectors to use the correct contract-selectors script + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal override returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/zksync/utils/contract-selectors.sh"; // Use zkSync contract-selectors script + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + + /// @notice Get regular deployment file path (without lda. prefix) + function getRegularDeploymentPath() internal view returns (string memory) { + // Need to construct regular deployment path by removing "lda." prefix from fileSuffix + string memory regularFileSuffix; + bytes memory fileSuffixBytes = bytes(fileSuffix); + + // Check if fileSuffix starts with "lda." and remove it + if ( + fileSuffixBytes.length >= 4 && + fileSuffixBytes[0] == "l" && + fileSuffixBytes[1] == "d" && + fileSuffixBytes[2] == "a" && + fileSuffixBytes[3] == "." + ) { + // Extract everything after "lda." by creating new bytes array + bytes memory remainingBytes = new bytes( + fileSuffixBytes.length - 4 + ); + for (uint256 i = 4; i < fileSuffixBytes.length; i++) { + remainingBytes[i - 4] = fileSuffixBytes[i]; + } + regularFileSuffix = string(remainingBytes); + } else { + // If no "lda." prefix, use as is + regularFileSuffix = fileSuffix; + } + + return + string.concat( + root, + "/deployments/", + network, + ".", + regularFileSuffix, + "json" + ); + } + function run() public returns (address[] memory facets, bytes memory cutData) { - // Read LDA core facets dynamically from lda-global.json config + // Read LDA core facets dynamically from global.json config string memory ldaGlobalConfigPath = string.concat( vm.projectRoot(), "/config/global.json" @@ -56,26 +111,34 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log("LDA core facets found in config/global.json: "); emit log_uint(ldaCoreFacets.length); + // Get regular deployment path for reading core facets + string memory regularDeploymentPath = getRegularDeploymentPath(); + emit log_named_string( + "Reading core facets from regular deployment file", + regularDeploymentPath + ); + // Check if the LDA loupe was already added to the diamond bool loupeExists; try loupe.facetAddresses() returns (address[] memory) { // If call was successful, loupe exists on LDA diamond already - emit log("LDA Loupe exists on diamond already"); + emit log("DiamondLoupeFacet exists on diamond already"); loupeExists = true; } catch { // No need to do anything, just making sure that the flow continues in both cases with try/catch } - // Handle LDADiamondLoupeFacet separately as it needs special treatment + // Handle DiamondLoupeFacet separately as it needs special treatment if (!loupeExists) { - emit log("LDA Loupe does not exist on diamond yet"); + emit log("DiamondLoupeFacet does not exist on diamond yet"); + // Read DiamondLoupeFacet from regular deployment file address ldaDiamondLoupeAddress = _getConfigContractAddress( - path, - ".LDADiamondLoupeFacet" + regularDeploymentPath, + ".DiamondLoupeFacet" ); bytes4[] memory loupeSelectors = getSelectors( - "LDADiamondLoupeFacet", - getExcludes("LDADiamondLoupeFacet") + "DiamondLoupeFacet", + getExcludes("DiamondLoupeFacet") ); buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); @@ -93,26 +156,26 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { for (uint256 i = 0; i < ldaCoreFacets.length; i++) { string memory facetName = ldaCoreFacets[i]; - // Skip LDADiamondCutFacet and LDADiamondLoupeFacet as they were already handled + // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled if ( keccak256(bytes(facetName)) == - keccak256(bytes("LDADiamondLoupeFacet")) + keccak256(bytes("DiamondLoupeFacet")) ) { continue; } - // Skip LDADiamondCutFacet as it was already handled during LDA diamond deployment + // Skip DiamondCutFacet as it was already handled during LDA diamond deployment if ( keccak256(bytes(facetName)) == - keccak256(bytes("LDADiamondCutFacet")) + keccak256(bytes("DiamondCutFacet")) ) { continue; } - emit log("Now adding LDA facet: "); + emit log("Now adding LDA core facet: "); emit log(facetName); - // Use _getConfigContractAddress which validates the contract exists + // Read core facets from regular deployment file, not LDA file address facetAddress = _getConfigContractAddress( - path, + regularDeploymentPath, string.concat(".", facetName) ); bytes4[] memory selectors = getSelectors( @@ -128,7 +191,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { if (noBroadcast) { if (cut.length > 0) { cutData = abi.encodeWithSelector( - LDADiamondCutFacet.diamondCut.selector, + DiamondCutFacet.diamondCut.selector, cut, address(0), "" diff --git a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol index 1349310dd..1e347bac6 100644 --- a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.17; import { LDAScriptBase } from "./LDAScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { LDADiamondCutFacet } from "lifi/Periphery/LDA/Facets/LDADiamondCutFacet.sol"; -import { LDADiamondLoupeFacet } from "lifi/Periphery/LDA/Facets/LDADiamondLoupeFacet.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; contract UpdateLDAScriptBase is LDAScriptBase { @@ -20,8 +20,8 @@ contract UpdateLDAScriptBase is LDAScriptBase { bytes4[] internal selectorsToReplace; bytes4[] internal selectorsToRemove; bytes4[] internal selectorsToAdd; - LDADiamondCutFacet internal cutter; - LDADiamondLoupeFacet internal loupe; + DiamondCutFacet internal cutter; + DiamondLoupeFacet internal loupe; string internal path; string internal json; bool internal noBroadcast = false; @@ -42,9 +42,9 @@ contract UpdateLDAScriptBase is LDAScriptBase { "json" ); json = vm.readFile(path); - diamond = json.readAddress(".LDADiamond"); - cutter = LDADiamondCutFacet(diamond); - loupe = LDADiamondLoupeFacet(diamond); + diamond = json.readAddress(".LiFiDEXAggregatorDiamond"); + cutter = DiamondCutFacet(diamond); + loupe = DiamondLoupeFacet(diamond); } function update( @@ -64,7 +64,7 @@ contract UpdateLDAScriptBase is LDAScriptBase { if (noBroadcast) { if (cut.length > 0) { cutData = abi.encodeWithSelector( - LDADiamondCutFacet.diamondCut.selector, + DiamondCutFacet.diamondCut.selector, cut, callData.length > 0 ? facet : address(0), callData @@ -95,7 +95,7 @@ contract UpdateLDAScriptBase is LDAScriptBase { function getSelectors( string memory _facetName, bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { + ) internal virtual returns (bytes4[] memory selectors) { string[] memory cmd = new string[](3); cmd[0] = "script/deploy/zksync/LDA/utils/contract-selectors.sh"; cmd[1] = _facetName; diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index f1e9c684f..873ae2c66 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4767,40 +4767,6 @@ function removeNetworkFromTargetStateJSON() { # ==== LDA-SPECIFIC HELPER FUNCTIONS ==== -# Deploy LDA Diamond with core facets -deployLDADiamondWithCoreFacets() { - local NETWORK="$1" - local ENVIRONMENT="$2" - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployLDADiamondWithCoreFacets" - - # load required resources - source script/config.sh - source script/helperFunctions.sh - source script/deploy/deployLDACoreFacets.sh - source script/deploy/deploySingleContract.sh - source script/tasks/ldaDiamondUpdateFacet.sh - - # Deploy LDA core facets first - deployLDACoreFacets "$NETWORK" "$ENVIRONMENT" - checkFailure $? "deploy LDA core facets to network $NETWORK" - - # Get LDA Diamond version - local VERSION=$(getCurrentContractVersion "LDADiamond") - - # Deploy LDA Diamond - deploySingleContract "LDADiamond" "$NETWORK" "$ENVIRONMENT" "$VERSION" "true" "true" - checkFailure $? "deploy LDADiamond to network $NETWORK" - - # Update LDA Diamond with core facets - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "LDADiamond" "UpdateLDACoreFacets" false - checkFailure $? "update LDA core facets in LDADiamond on network $NETWORK" - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployLDADiamondWithCoreFacets completed" - - return 0 -} - # Deploy and add contract to LDA Diamond deployAndAddContractToLDADiamond() { local NETWORK="$1" diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index 73bb9ddab..a806e7c93 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -42,7 +42,6 @@ scriptMaster() { source script/deploy/deployAllLDAContracts.sh source script/helperFunctions.sh source script/deploy/deployFacetAndAddToDiamond.sh - source script/deploy/deployLDACoreFacets.sh source script/deploy/deployPeripheryContracts.sh source script/deploy/deployUpgradesToSAFE.sh for script in script/tasks/*.sh; do [ -f "$script" ] && source "$script"; done # sources all script in folder script/tasks/ @@ -124,7 +123,7 @@ scriptMaster() { "11) Update diamond log(s)" \ "12) Propose upgrade TX to Gnosis SAFE" \ "13) Remove facets or periphery from diamond" \ - "14) Deploy all LDA diamond with core contracts to one selected network" \ + "14) Deploy all LiFi DEX Aggregator diamond with contracts to one selected network" \ ) #--------------------------------------------------------------------------------------------------------------------- @@ -183,12 +182,12 @@ scriptMaster() { # check if new contract should be added to diamond after deployment if [[ "$CONTRACT_TYPE" == "LDA"* ]]; then # LDA contracts - if [[ ! "$CONTRACT" == "LDADiamond"* ]]; then + if [[ ! "$CONTRACT" == "LiFiDEXAggregatorDiamond"* ]]; then echo "" echo "Do you want to add this LDA contract to a diamond after deployment?" ADD_TO_DIAMOND=$( gum choose \ - "yes - to LDADiamond" \ + "yes - to LiFiDEXAggregatorDiamond" \ " no - do not update any diamond" ) fi @@ -214,8 +213,8 @@ scriptMaster() { echo "[info] selected option: $ADD_TO_DIAMOND" # determine the diamond type and call appropriate function - if [[ "$ADD_TO_DIAMOND" == *"LDADiamond"* ]]; then - deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LDADiamond" "$VERSION" + if [[ "$ADD_TO_DIAMOND" == *"LiFiDEXAggregatorDiamond"* ]]; then + deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDEXAggregatorDiamond" "$VERSION" elif [[ "$ADD_TO_DIAMOND" == *"LiFiDiamondImmutable"* ]]; then deployAndAddContractToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamondImmutable" "$VERSION" else @@ -594,10 +593,10 @@ scriptMaster() { bunx tsx script/tasks/cleanUpProdDiamond.ts #--------------------------------------------------------------------------------------------------------------------- - # use case 15: Deploy all LDA diamond with core contracts to one selected network + # use case 15: Deploy all LiFi DEX Aggregator diamond with contracts to one selected network elif [[ "$SELECTION" == "14)"* ]]; then echo "" - echo "[info] selected use case: Deploy all LDA diamond with core contracts to one selected network" + echo "[info] selected use case: Deploy all LiFi DEX Aggregator diamond with contracts to one selected network" checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" # get user-selected network from list diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh index b8960f33f..20a2d48a2 100644 --- a/script/tasks/ldaDiamondUpdateFacet.sh +++ b/script/tasks/ldaDiamondUpdateFacet.sh @@ -1,6 +1,6 @@ #!/bin/bash -# executes a diamond update script to update an LDA facet on LDADiamond +# executes a diamond update script to update an LDA facet on LiFiDEXAggregatorDiamond function ldaDiamondUpdateFacet() { # load required variables and helper functions @@ -52,9 +52,9 @@ function ldaDiamondUpdateFacet() { # get file suffix based on value in variable ENVIRONMENT FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - # if no DIAMOND_CONTRACT_NAME was passed to this function, default to LDADiamond + # if no DIAMOND_CONTRACT_NAME was passed to this function, default to LiFiDEXAggregatorDiamond if [[ -z "$DIAMOND_CONTRACT_NAME" ]]; then - DIAMOND_CONTRACT_NAME="LDADiamond" + DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" fi # if no UPDATE_SCRIPT was passed to this function, ask user to select it @@ -84,7 +84,7 @@ function ldaDiamondUpdateFacet() { return 1 fi - # set flag for LDA diamond (always false since LDADiamond is not the default diamond) + # set flag for LDA diamond (always false since LiFiDEXAggregatorDiamond is not the default diamond) USE_LDA_DIAMOND=false if [[ -z "$GAS_ESTIMATE_MULTIPLIER" ]]; then @@ -117,7 +117,7 @@ function ldaDiamondUpdateFacet() { doNotContinueUnlessGasIsBelowThreshold "$NETWORK" # try to execute call - RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") + RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") local RETURN_CODE=$? From 3ea087c42ec74161c23adeab76f0fe29e3da431d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 4 Sep 2025 20:34:24 +0200 Subject: [PATCH 158/220] Update deployment configurations and scripts for LDA to LiFiDEXAggregator transition. Add new staging entries for zksync and update existing addresses in arbitrum and optimism deployment files. Refactor deployment scripts to improve clarity and maintainability, including renaming stages in the deployAllLDAContracts script and enhancing health check functionality. Ensure all references to LDA are updated to reflect the new naming convention. --- deployments/_deployments_log_file.json | 299 +++++++++--------- deployments/arbitrum.lda.staging.json | 2 +- deployments/optimism.lda.staging.json | 22 +- deployments/zksync.lda.staging.json | 7 +- deployments/zksync.staging.json | 5 + script/deploy/deployAllLDAContracts.sh | 63 ++-- .../facets/LDA/UpdateLDACoreFacets.s.sol | 57 +--- .../facets/LDA/utils/UpdateLDAScriptBase.sol | 2 +- script/deploy/ldaHealthCheck.ts | 86 +++-- ...eployLiFiDEXAggregatorDiamond.zksync.s.sol | 2 + .../LDA/UpdateLDACoreFacets.zksync.s.sol | 110 +++---- .../zksync/LDA/utils/UpdateLDAScriptBase.sol | 2 +- 12 files changed, 323 insertions(+), 334 deletions(-) create mode 100644 deployments/zksync.staging.json diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 05169bf1c..4f9377d43 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -479,6 +479,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xd6731f2729e24F3eAeDe8d10aFBd908460c021c5", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 19:46:31", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] } }, "base": { @@ -1524,6 +1537,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xB03d1c11529d248727aE1fE1570f8Fd664968e31", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 17:37:53", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] } }, "base": { @@ -2582,6 +2608,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x1773bB176Ee1A7774bDeaf8fff92991bC005A56A", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 17:39:18", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] } }, "base": { @@ -39719,15 +39758,15 @@ } } }, - "LDADiamondCutFacet": { + "CoreRouteFacet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", + "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:10:27", - "CONSTRUCTOR_ARGS": "0x", + "TIMESTAMP": "2025-09-04 14:02:46", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -39739,10 +39778,10 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", + "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:41:58", - "CONSTRUCTOR_ARGS": "0x", + "TIMESTAMP": "2025-09-04 17:10:35", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -39754,10 +39793,10 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x8Bbe822959F60D0d6a882486928C7d851bf52adc", + "ADDRESS": "0xaFAf106f4D8eDdF68deE9F06dA0CB12BE1f6BD51", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:48:42", - "CONSTRUCTOR_ARGS": "0x", + "TIMESTAMP": "2025-09-04 18:42:03", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" @@ -39766,14 +39805,14 @@ } } }, - "LDADiamondLoupeFacet": { + "UniV3StyleFacet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", + "ADDRESS": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:11:52", + "TIMESTAMP": "2025-09-04 14:27:13", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39786,9 +39825,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", + "ADDRESS": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:42:13", + "TIMESTAMP": "2025-09-04 17:13:26", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39801,9 +39840,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x63144D19F86EeeF3722af56a6804098DD1b58640", + "ADDRESS": "0x0B960e4a64C325062B78f1A2FaE51E7bF6594E50", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:50:53", + "TIMESTAMP": "2025-09-04 17:54:50", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39813,14 +39852,14 @@ } } }, - "LDAOwnershipFacet": { + "UniV2StyleFacet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", + "ADDRESS": "0x181a353054883D9DdE6864Ba074226E5b77cf511", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:13:16", + "TIMESTAMP": "2025-09-04 14:26:48", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39833,9 +39872,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", + "ADDRESS": "0x181a353054883D9DdE6864Ba074226E5b77cf511", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:42:31", + "TIMESTAMP": "2025-09-04 17:13:01", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39848,9 +39887,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x53303BCE53bafFCD764501D0B4B3dcC798998b47", + "ADDRESS": "0xde90fA5Cbf867bF04954E53f124C3cE1e1417426", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:53:56", + "TIMESTAMP": "2025-09-04 17:53:31", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39860,15 +39899,15 @@ } } }, - "LDAEmergencyPauseFacet": { + "NativeWrapperFacet": { "arbitrum": { "staging": { - "1.0.2": [ + "1.0.0": [ { - "ADDRESS": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", + "ADDRESS": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:14:45", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "TIMESTAMP": "2025-09-04 14:26:09", + "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -39878,12 +39917,12 @@ }, "optimism": { "staging": { - "1.0.2": [ + "1.0.0": [ { - "ADDRESS": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", + "ADDRESS": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:42:53", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "TIMESTAMP": "2025-09-04 17:12:11", + "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -39893,12 +39932,12 @@ }, "zksync": { "staging": { - "1.0.2": [ + "1.0.0": [ { - "ADDRESS": "0x0000000000000000000000000000000000000000", + "ADDRESS": "0xD825ea1B88b1b10d0adD49a92c610E912Cb2fafF", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:56:04", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "TIMESTAMP": "2025-09-04 17:50:53", + "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" @@ -39907,14 +39946,14 @@ } } }, - "LDAPeripheryRegistryFacet": { + "IzumiV3Facet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", + "ADDRESS": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 21:16:11", + "TIMESTAMP": "2025-09-04 14:05:55", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39927,9 +39966,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", + "ADDRESS": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:43:10", + "TIMESTAMP": "2025-09-04 17:11:22", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39942,9 +39981,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xBbA05be3150Eafa7F0Ac3F57FEEFF0e57Da14F12", + "ADDRESS": "0xE3bE94EFf5ce759Cb4BbDe2c51c8900072F22f0b", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:58:15", + "TIMESTAMP": "2025-09-04 17:47:49", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -39954,15 +39993,15 @@ } } }, - "CoreRouteFacet": { + "LiFiDEXAggregatorDiamond": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "ADDRESS": "0xE6fc9224a622df3E5204f1165210102C4BD9A6c8", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:02:46", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", + "TIMESTAMP": "2025-09-04 19:50:27", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000b2a8517734cdf985d53f303a1f7759a34fdc772f", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -39974,10 +40013,10 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", + "ADDRESS": "0x897e12b5f25187648561A2e719e2ad22125626Ca", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:59:55", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", + "TIMESTAMP": "2025-09-04 17:09:40", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000b2a8517734cdf985d53f303a1f7759a34fdc772f", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -39989,10 +40028,10 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x0000000000000000000000000000000000000000", + "ADDRESS": "0xacB28A8DF1D8073d014F14D4509C2b3a30220b32", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 20:00:24", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", + "TIMESTAMP": "2025-09-04 19:25:21", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de6200000000000000000000000020305d7ceaf95d4baa29621ef39661fd756bc024", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" @@ -40001,14 +40040,14 @@ } } }, - "UniV3StyleFacet": { + "AlgebraFacet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "ADDRESS": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:27:13", + "TIMESTAMP": "2025-09-04 14:02:26", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40021,9 +40060,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x414fc3eB441664807027346817dcBe8490938C78", + "ADDRESS": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:00:28", + "TIMESTAMP": "2025-09-04 17:10:11", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40031,31 +40070,31 @@ } ] } - } - }, - "UniV2StyleFacet": { - "arbitrum": { + }, + "zksync": { "staging": { "1.0.0": [ { - "ADDRESS": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "ADDRESS": "0x6ac283a65406eef58c417ad7B83E10C75108d7C5", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:26:48", + "TIMESTAMP": "2025-09-04 18:40:40", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", - "ZK_SOLC_VERSION": "" + "ZK_SOLC_VERSION": "1.5.15" } ] } - }, - "optimism": { + } + }, + "CurveFacet": { + "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", + "ADDRESS": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:00:44", + "TIMESTAMP": "2025-09-04 14:05:34", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40063,17 +40102,15 @@ } ] } - } - }, - "LDADiamond": { - "arbitrum": { + }, + "optimism": { "staging": { "1.0.0": [ { - "ADDRESS": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", + "ADDRESS": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-29 22:02:41", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000fa0a1fd9e492f0f75168a7ef72418420379057b7", + "TIMESTAMP": "2025-09-04 17:10:59", + "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", "ZK_SOLC_VERSION": "" @@ -40081,30 +40118,30 @@ ] } }, - "optimism": { + "zksync": { "staging": { "1.0.0": [ { - "ADDRESS": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", + "ADDRESS": "0x44BD7D613c16480287CFDDe44820dA22E2a7F848", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 18:44:18", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000fa0a1fd9e492f0f75168a7ef72418420379057b7", + "TIMESTAMP": "2025-09-04 18:43:50", + "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", - "ZK_SOLC_VERSION": "" + "ZK_SOLC_VERSION": "1.5.15" } ] } } }, - "NativeWrapperFacet": { + "KatanaV3Facet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "ADDRESS": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:26:09", + "TIMESTAMP": "2025-09-04 14:08:38", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40117,9 +40154,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", + "ADDRESS": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:00:12", + "TIMESTAMP": "2025-09-04 17:11:45", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40132,9 +40169,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x0000000000000000000000000000000000000000", + "ADDRESS": "0xfBCE7EFcaeEf1DD2D84344FbbE050E5b11f2A931", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 20:02:32", + "TIMESTAMP": "2025-09-04 17:49:06", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40144,14 +40181,14 @@ } } }, - "IzumiV3Facet": { + "SyncSwapV2Facet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "ADDRESS": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:05:55", + "TIMESTAMP": "2025-09-04 14:26:30", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40164,43 +40201,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x2688a406d7F7A99C7448d959371063275CfC2E81", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-01 19:02:14", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - } - }, - "LiFiDEXAggregatorDiamond": { - "arbitrum": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 13:07:35", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000b2a8517734cdf985d53f303a1f7759a34fdc772f", - "SALT": "", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - } - }, - "AlgebraFacet": { - "arbitrum": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "ADDRESS": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:02:26", + "TIMESTAMP": "2025-09-04 17:12:36", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40208,33 +40211,31 @@ } ] } - } - }, - "CurveFacet": { - "arbitrum": { + }, + "zksync": { "staging": { "1.0.0": [ { - "ADDRESS": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "ADDRESS": "0xe6d5cd6aE1a72471D2862FED04Bd8cfF978FFB45", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:05:34", + "TIMESTAMP": "2025-09-04 17:52:11", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", - "ZK_SOLC_VERSION": "" + "ZK_SOLC_VERSION": "1.5.15" } ] } } }, - "KatanaV3Facet": { + "VelodromeV2Facet": { "arbitrum": { "staging": { "1.0.0": [ { - "ADDRESS": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "ADDRESS": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:08:38", + "TIMESTAMP": "2025-09-04 14:27:34", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40242,16 +40243,14 @@ } ] } - } - }, - "SyncSwapV2Facet": { - "arbitrum": { + }, + "optimism": { "staging": { "1.0.0": [ { - "ADDRESS": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "ADDRESS": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:26:30", + "TIMESTAMP": "2025-09-04 17:13:53", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", @@ -40259,20 +40258,18 @@ } ] } - } - }, - "VelodromeV2Facet": { - "arbitrum": { + }, + "zksync": { "staging": { "1.0.0": [ { - "ADDRESS": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "ADDRESS": "0x73bFe1D0DFe7933de9ea25F50bC8eacE80bB6613", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:27:34", + "TIMESTAMP": "2025-09-04 17:56:57", "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "false", - "ZK_SOLC_VERSION": "" + "ZK_SOLC_VERSION": "1.5.15" } ] } diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json index 07b73728f..f7ba59dff 100644 --- a/deployments/arbitrum.lda.staging.json +++ b/deployments/arbitrum.lda.staging.json @@ -1,5 +1,5 @@ { - "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "LiFiDEXAggregatorDiamond": "0xE6fc9224a622df3E5204f1165210102C4BD9A6c8", "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", diff --git a/deployments/optimism.lda.staging.json b/deployments/optimism.lda.staging.json index 84d3dd79f..07b73728f 100644 --- a/deployments/optimism.lda.staging.json +++ b/deployments/optimism.lda.staging.json @@ -1,13 +1,13 @@ { - "LDADiamondCutFacet": "0xFA0A1fD9E492F0F75168A7eF72418420379057b7", - "LDADiamondLoupeFacet": "0x394A2d0c39bdFB00232ba25843cD1E69904e119F", - "LDAOwnershipFacet": "0xB4A3E3E018B0F4C787B990C090a2A1Ec50F65af9", - "LDAEmergencyPauseFacet": "0xd69CCCa15A899b0bF3959550372F25247323dbdc", - "LDAPeripheryRegistryFacet": "0x1F9c5263c408b4A9b70e94DcC83aA065cf665aA5", - "LDADiamond": "0xdCFf401A4d7B08cAe3E5a6F7C37c8FCb27978E1d", - "CoreRouteFacet": "0x4DB27D574104b265c514F79B8c98D4bB856BB394", - "NativeWrapperFacet": "0x2087073e7d414F31E2C348b8A8131db01bB787F9", - "UniV3StyleFacet": "0x414fc3eB441664807027346817dcBe8490938C78", - "UniV2StyleFacet": "0x8bbC2e77a2326365f13D186abd2b22d7A66828A6", - "IzumiV3Facet": "0x2688a406d7F7A99C7448d959371063275CfC2E81" + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" } \ No newline at end of file diff --git a/deployments/zksync.lda.staging.json b/deployments/zksync.lda.staging.json index 227bc53bf..5d3da5cc4 100644 --- a/deployments/zksync.lda.staging.json +++ b/deployments/zksync.lda.staging.json @@ -4,6 +4,9 @@ "LDAOwnershipFacet": "0x53303BCE53bafFCD764501D0B4B3dcC798998b47", "LDAEmergencyPauseFacet": "0x0000000000000000000000000000000000000000", "LDAPeripheryRegistryFacet": "0xBbA05be3150Eafa7F0Ac3F57FEEFF0e57Da14F12", - "CoreRouteFacet": "0x0000000000000000000000000000000000000000", - "NativeWrapperFacet": "0x0000000000000000000000000000000000000000" + "CoreRouteFacet": "0xaFAf106f4D8eDdF68deE9F06dA0CB12BE1f6BD51", + "NativeWrapperFacet": "0x0000000000000000000000000000000000000000", + "LiFiDEXAggregatorDiamond": "0xacB28A8DF1D8073d014F14D4509C2b3a30220b32", + "AlgebraFacet": "0x6ac283a65406eef58c417ad7B83E10C75108d7C5", + "CurveFacet": "0x44BD7D613c16480287CFDDe44820dA22E2a7F848" } \ No newline at end of file diff --git a/deployments/zksync.staging.json b/deployments/zksync.staging.json new file mode 100644 index 000000000..a314a060b --- /dev/null +++ b/deployments/zksync.staging.json @@ -0,0 +1,5 @@ +{ + "DiamondCutFacet": "0xd6731f2729e24F3eAeDe8d10aFBd908460c021c5", + "DiamondLoupeFacet": "0xB03d1c11529d248727aE1fE1570f8Fd664968e31", + "OwnershipFacet": "0x1773bB176Ee1A7774bDeaf8fff92991bC005A56A" +} \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 5b585538c..50e6def5b 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -128,10 +128,11 @@ deployAllLDAContracts() { gum choose \ "1) Initial setup and CREATE3Factory deployment" \ "2) Check LDA core facets availability" \ - "3) Deploy LDA diamond and update with core facets" \ - "4) Deploy non-core LDA facets and add to diamond" \ - "5) Run LDA health check only" \ - "6) Ownership transfer to timelock (production only)" + "3) Deploy LDA diamond" \ + "4) Update LDA diamond with core facets" \ + "5) Deploy non-core LDA facets and add to diamond" \ + "6) Run LDA health check only" \ + "7) Ownership transfer to timelock (production only)" ) # Extract the stage number from the selection @@ -147,6 +148,8 @@ deployAllLDAContracts() { START_STAGE=5 elif [[ "$START_FROM" == *"6)"* ]]; then START_STAGE=6 + elif [[ "$START_FROM" == *"7)"* ]]; then + START_STAGE=7 else error "invalid selection: $START_FROM - exiting script now" exit 1 @@ -232,10 +235,10 @@ deployAllLDAContracts() { echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 2 completed" fi - # Stage 3: Deploy LDA diamond and update with core facets + # Stage 3: Deploy LDA diamond if [[ $START_STAGE -le 3 ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 3: Deploy LDA diamond and update with core facets" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 3: Deploy LDA diamond" # get current LDA diamond contract version local VERSION=$(getCurrentContractVersion "$LDA_DIAMOND_CONTRACT_NAME") @@ -248,9 +251,15 @@ deployAllLDAContracts() { checkFailure $? "deploy contract $LDA_DIAMOND_CONTRACT_NAME to network $NETWORK" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $LDA_DIAMOND_CONTRACT_NAME successfully deployed" - # update LDA diamond with core facets - echo "" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 3 completed" + fi + + # Stage 4: Update LDA diamond with core facets + if [[ $START_STAGE -le 4 ]]; then echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Update LDA diamond with core facets" + + # update LDA diamond with core facets echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now updating core facets in LDA diamond contract" ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" false @@ -258,13 +267,13 @@ deployAllLDAContracts() { checkFailure $? "update LDA core facets in $LDA_DIAMOND_CONTRACT_NAME on network $NETWORK" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA core facets update completed" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 3 completed" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 4 completed" fi - # Stage 4: Deploy non-core LDA facets and add to diamond - if [[ $START_STAGE -le 4 ]]; then + # Stage 5: Deploy non-core LDA facets and add to diamond + if [[ $START_STAGE -le 5 ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Deploy non-core LDA facets and add to diamond" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Deploy non-core LDA facets and add to diamond" # Get all LDA facet contract names from the LDA Facets directory local LDA_FACETS_PATH="src/Periphery/LDA/Facets/" @@ -316,40 +325,40 @@ deployAllLDAContracts() { done echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< non-core LDA facets deployment completed" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 4 completed" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" fi - # Stage 5: Run LDA health check - if [[ $START_STAGE -le 5 ]]; then + # Stage 6: Run LDA health check + if [[ $START_STAGE -le 6 ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Run LDA health check only" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Run LDA health check only" bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 6 completed" # Pause and ask user if they want to continue with ownership transfer (for production) - if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 5 ]]; then + if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 6 ]]; then echo "" echo "Health check completed. Do you want to continue with ownership transfer to timelock?" echo "This should only be done if the health check shows only diamond ownership errors." - echo "Continue with stage 6 (ownership transfer)? (y/n)" + echo "Continue with stage 7 (ownership transfer)? (y/n)" read -r response if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - echo "Proceeding with stage 6..." + echo "Proceeding with stage 7..." else - echo "Skipping stage 6 - ownership transfer cancelled by user" + echo "Skipping stage 7 - ownership transfer cancelled by user" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" return fi fi fi - # Stage 6: Ownership transfer to timelock (production only) - if [[ $START_STAGE -le 6 ]]; then + # Stage 7: Ownership transfer to timelock (production only) + if [[ $START_STAGE -le 7 ]]; then if [[ "$ENVIRONMENT" == "production" ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Ownership transfer to timelock (production only)" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 7: Ownership transfer to timelock (production only)" - # make sure SAFE_ADDRESS is available (if starting in stage 6 it's not available yet) + # make sure SAFE_ADDRESS is available (if starting in stage 7 it's not available yet) SAFE_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.safeAddress") if [[ -z "$SAFE_ADDRESS" || "$SAFE_ADDRESS" == "null" ]]; then echo "SAFE address not found in networks.json. Cannot prepare ownership transfer to Timelock" @@ -387,10 +396,10 @@ deployAllLDAContracts() { echo "" # ------------------------------------------------------------ else - echo "Stage 6 skipped - ownership transfer to timelock is only for production environment" + echo "Stage 7 skipped - ownership transfer to timelock is only for production environment" fi - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 6 completed" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 7 completed" fi echo "" diff --git a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol index 0ca40b2a0..e56b46608 100644 --- a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol +++ b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol @@ -4,41 +4,10 @@ pragma solidity ^0.8.17; import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { IERC173 } from "lifi/Interfaces/IERC173.sol"; -import { TransferrableOwnership } from "lifi/Helpers/TransferrableOwnership.sol"; contract UpdateLDACoreFacets is UpdateLDAScriptBase { using stdJson for string; - error FailedToReadLDACoreFacetsFromConfig(); - - /// @notice Returns function selectors to exclude for specific facets - /// @param facetName The name of the facet being processed - function getExcludes( - string memory facetName - ) internal pure returns (bytes4[] memory) { - // Exclude ownership function selectors from CoreRouteFacet to avoid collision with LDAOwnershipFacet - if ( - keccak256(bytes(facetName)) == keccak256(bytes("CoreRouteFacet")) - ) { - bytes4[] memory excludes = new bytes4[](5); - excludes[0] = IERC173.transferOwnership.selector; - excludes[1] = TransferrableOwnership - .cancelOwnershipTransfer - .selector; - excludes[2] = TransferrableOwnership - .confirmOwnershipTransfer - .selector; - excludes[3] = IERC173.owner.selector; - excludes[4] = bytes4(keccak256("pendingOwner()")); // public state variable not a function - return excludes; - } - - // No exclusions for other facets - bytes4[] memory emptyExcludes = new bytes4[](0); - return emptyExcludes; - } - /// @notice Get regular deployment file path (without lda. prefix) function getRegularDeploymentPath() internal view returns (string memory) { // Need to construct regular deployment path by removing "lda." prefix from fileSuffix @@ -77,23 +46,6 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { ); } - /// @notice Override getSelectors to use the correct contract-selectors script - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal override returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; // Use regular contract-selectors script - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - function run() public returns (address[] memory facets, bytes memory cutData) @@ -118,6 +70,8 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { regularDeploymentPath ); + bytes4[] memory exclude; + // Check if the LDA loupe was already added to the diamond bool loupeExists; try loupe.facetAddresses() returns (address[] memory) { @@ -138,7 +92,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { ); bytes4[] memory loupeSelectors = getSelectors( "DiamondLoupeFacet", - getExcludes("DiamondLoupeFacet") + exclude ); buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); @@ -178,10 +132,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { regularDeploymentPath, string.concat(".", facetName) ); - bytes4[] memory selectors = getSelectors( - facetName, - getExcludes(facetName) - ); + bytes4[] memory selectors = getSelectors(facetName, exclude); // at this point we know for sure that LDA diamond loupe exists on diamond buildDiamondCut(selectors, facetAddress); diff --git a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol index 7ec64c5d4..2eda7cc07 100644 --- a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol @@ -97,7 +97,7 @@ contract UpdateLDAScriptBase is ScriptBase { function getSelectors( string memory _facetName, bytes4[] memory _exclude - ) internal virtual returns (bytes4[] memory selectors) { + ) internal returns (bytes4[] memory selectors) { string[] memory cmd = new string[](3); cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; // Default to regular contract-selectors cmd[1] = _facetName; diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index 0e1778614..db7fc495f 100644 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -96,7 +96,7 @@ const main = defineCommand({ process.exit(1) } - // Load main deployment file for shared infrastructure (like LiFiTimelockController) + // Load main deployment file for shared infrastructure (like LiFiTimelockController and core facets) // For staging, try environment-specific file first, then fallback to main let mainDeployedContracts: Record = {} const mainDeploymentFile = `../../deployments/${network.toLowerCase()}.json` @@ -112,10 +112,11 @@ const main = defineCommand({ const { default: contracts } = await import(mainDeploymentFile) mainDeployedContracts = contracts } catch (fallbackError) { - consola.warn( + consola.error( `Failed to load deployment files: ${stagingFile} and ${mainDeploymentFile}` ) - consola.warn('Some shared infrastructure checks will be skipped.') + consola.error('Cannot verify core facets availability.') + process.exit(1) } } } @@ -125,14 +126,13 @@ const main = defineCommand({ const { default: contracts } = await import(mainDeploymentFile) mainDeployedContracts = contracts } catch (error) { - consola.warn( + consola.error( `Failed to load main deployment file: ${mainDeploymentFile}` ) - consola.warn('Some shared infrastructure checks will be skipped.') + consola.error('Cannot verify core facets availability.') + process.exit(1) } - // Note: We keep LDA and main contracts separate for clarity - // Load global config for LDA core facets const globalConfig = await import('../../config/global.json') const networksConfigModule = await import('../../config/networks.json') @@ -170,21 +170,28 @@ const main = defineCommand({ const diamondAddress = ldaDeployedContracts['LiFiDEXAggregatorDiamond'] // ╭─────────────────────────────────────────────────────────╮ - // │ Check LDA core facets │ + // │ Check LDA core facets availability │ // ╰─────────────────────────────────────────────────────────╯ - consola.box('Checking LDA Core Facets...') + consola.box('Checking LDA Core Facets Availability...') + consola.info( + 'LDA core facets are shared with regular LiFi Diamond and stored in regular deployment file' + ) + for (const facet of ldaCoreFacets) { + // Check if core facet is deployed in regular deployment file (shared with LiFi Diamond) const isDeployed = await checkIsDeployedWithCast( facet, - ldaDeployedContracts, + mainDeployedContracts, rpcUrl ) if (!isDeployed) { - logError(`LDA Facet ${facet} not deployed`) + logError( + `LDA Core Facet ${facet} not found in regular deployment file - please deploy regular LiFi Diamond first` + ) continue } - consola.success(`LDA Facet ${facet} deployed`) + consola.success(`LDA Core Facet ${facet} available in regular deployment`) } // ╭─────────────────────────────────────────────────────────╮ @@ -208,11 +215,28 @@ const main = defineCommand({ const onChainFacets = JSON.parse(jsonCompatibleString) if (Array.isArray(onChainFacets)) { - const configFacetsByAddress = Object.fromEntries( - Object.entries(ldaDeployedContracts).map(([name, address]) => { - return [address.toLowerCase(), name] - }) - ) + // Create mapping from addresses to facet names + // For core facets, use addresses from main deployment file + // For non-core facets, use addresses from LDA deployment file + const configFacetsByAddress: Record = {} + + // Add core facets from main deployment file + for (const facet of ldaCoreFacets) { + const address = mainDeployedContracts[facet] + if (address) + configFacetsByAddress[address.toLowerCase()] = facet + + } + + // Add non-core facets from LDA deployment file + Object.entries(ldaDeployedContracts).forEach(([name, address]) => { + if ( + name !== 'LiFiDEXAggregatorDiamond' && + !ldaCoreFacets.includes(name) + ) + configFacetsByAddress[address.toLowerCase()] = name + + }) registeredFacets = onChainFacets .map(([address]) => configFacetsByAddress[address.toLowerCase()]) @@ -225,12 +249,28 @@ const main = defineCommand({ consola.warn('Error:', (error as Error).message) } - for (const facet of ldaCoreFacets) - if (!registeredFacets.includes(facet)) - logError( - `LDA Facet ${facet} not registered in Diamond or possibly unverified` - ) - else consola.success(`LDA Facet ${facet} registered in Diamond`) + // Check core facets registration + for (const facet of ldaCoreFacets) + if (!registeredFacets.includes(facet)) + logError(`LDA Core Facet ${facet} not registered in Diamond`) + else + consola.success(`LDA Core Facet ${facet} registered in Diamond`) + + + + // Check non-core facets registration + const nonCoreFacets = Object.keys(ldaDeployedContracts).filter( + (name) => + name !== 'LiFiDEXAggregatorDiamond' && !ldaCoreFacets.includes(name) + ) + + for (const facet of nonCoreFacets) + if (!registeredFacets.includes(facet)) + logError(`LDA Non-Core Facet ${facet} not registered in Diamond`) + else + consola.success(`LDA Non-Core Facet ${facet} registered in Diamond`) + + // Basic ownership check using cast try { diff --git a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol index af10d48dd..878491262 100644 --- a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol @@ -60,6 +60,8 @@ contract DeployScript is DeployLDAScriptBase { "json" ); + emit log_named_string("regularPath", regularPath); + // Get DiamondCutFacet address from regular deployment file address diamondCut = _getConfigContractAddress( regularPath, diff --git a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol index 520a45f1c..89d61b568 100644 --- a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol +++ b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol @@ -4,58 +4,10 @@ pragma solidity ^0.8.17; import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { IERC173 } from "lifi/Interfaces/IERC173.sol"; -import { TransferrableOwnership } from "lifi/Helpers/TransferrableOwnership.sol"; contract UpdateLDACoreFacets is UpdateLDAScriptBase { using stdJson for string; - error FailedToReadLDACoreFacetsFromConfig(); - - /// @notice Returns function selectors to exclude for specific facets - /// @param facetName The name of the facet being processed - function getExcludes( - string memory facetName - ) internal pure returns (bytes4[] memory) { - // Exclude ownership function selectors from CoreRouteFacet to avoid collision with LDAOwnershipFacet - if ( - keccak256(bytes(facetName)) == keccak256(bytes("CoreRouteFacet")) - ) { - bytes4[] memory excludes = new bytes4[](5); - excludes[0] = IERC173.transferOwnership.selector; - excludes[1] = TransferrableOwnership - .cancelOwnershipTransfer - .selector; - excludes[2] = TransferrableOwnership - .confirmOwnershipTransfer - .selector; - excludes[3] = IERC173.owner.selector; - excludes[4] = bytes4(keccak256("pendingOwner()")); // public state variable not a function - return excludes; - } - - // No exclusions for other facets - bytes4[] memory emptyExcludes = new bytes4[](0); - return emptyExcludes; - } - - /// @notice Override getSelectors to use the correct contract-selectors script - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal override returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/zksync/utils/contract-selectors.sh"; // Use zkSync contract-selectors script - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - /// @notice Get regular deployment file path (without lda. prefix) function getRegularDeploymentPath() internal view returns (string memory) { // Need to construct regular deployment path by removing "lda." prefix from fileSuffix @@ -98,6 +50,8 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { public returns (address[] memory facets, bytes memory cutData) { + emit log("=== STARTING UpdateLDACoreFacets ==="); + // Read LDA core facets dynamically from global.json config string memory ldaGlobalConfigPath = string.concat( vm.projectRoot(), @@ -111,47 +65,67 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log("LDA core facets found in config/global.json: "); emit log_uint(ldaCoreFacets.length); - // Get regular deployment path for reading core facets - string memory regularDeploymentPath = getRegularDeploymentPath(); + // For zkSync, read core facets from the zkSync deployment file (same as regular UpdateCoreFacets) emit log_named_string( - "Reading core facets from regular deployment file", - regularDeploymentPath + "Reading core facets from zkSync deployment file", + path ); + bytes4[] memory exclude; // Check if the LDA loupe was already added to the diamond bool loupeExists; - try loupe.facetAddresses() returns (address[] memory) { + + emit log("=== CHECKING IF LOUPE EXISTS ==="); + try loupe.facetAddresses() returns (address[] memory existingFacets) { // If call was successful, loupe exists on LDA diamond already emit log("DiamondLoupeFacet exists on diamond already"); + emit log_uint(existingFacets.length); loupeExists = true; - } catch { - // No need to do anything, just making sure that the flow continues in both cases with try/catch + } catch Error(string memory reason) { + emit log("DiamondLoupeFacet check failed with reason:"); + emit log(reason); + } catch (bytes memory lowLevelData) { + emit log("DiamondLoupeFacet check failed with low level data:"); + emit log_bytes(lowLevelData); } // Handle DiamondLoupeFacet separately as it needs special treatment if (!loupeExists) { + emit log("=== ADDING DIAMONDLOUPE FACET ==="); emit log("DiamondLoupeFacet does not exist on diamond yet"); - // Read DiamondLoupeFacet from regular deployment file + // Read DiamondLoupeFacet from zkSync deployment file (same as regular script) address ldaDiamondLoupeAddress = _getConfigContractAddress( - regularDeploymentPath, + path, ".DiamondLoupeFacet" ); + emit log_named_address( + "DiamondLoupeFacet address", + ldaDiamondLoupeAddress + ); + bytes4[] memory loupeSelectors = getSelectors( "DiamondLoupeFacet", - getExcludes("DiamondLoupeFacet") + exclude ); + emit log("DiamondLoupeFacet selectors:"); + for (uint256 i = 0; i < loupeSelectors.length; i++) { + emit log_bytes32(loupeSelectors[i]); + } buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); + emit log("=== EXECUTING DIAMONDCUT FOR LOUPE ==="); vm.startBroadcast(deployerPrivateKey); if (cut.length > 0) { cutter.diamondCut(cut, address(0), ""); } vm.stopBroadcast(); + emit log("=== DIAMONDCUT FOR LOUPE COMPLETED ==="); // Reset diamond cut variable to remove LDA diamondLoupe information delete cut; } + emit log("=== PROCESSING OTHER CORE FACETS ==="); // Process all LDA core facets dynamically for (uint256 i = 0; i < ldaCoreFacets.length; i++) { string memory facetName = ldaCoreFacets[i]; @@ -161,6 +135,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { keccak256(bytes(facetName)) == keccak256(bytes("DiamondLoupeFacet")) ) { + emit log("Skipping DiamondLoupeFacet"); continue; } // Skip DiamondCutFacet as it was already handled during LDA diamond deployment @@ -168,20 +143,24 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { keccak256(bytes(facetName)) == keccak256(bytes("DiamondCutFacet")) ) { + emit log("Skipping DiamondCutFacet"); continue; } emit log("Now adding LDA core facet: "); emit log(facetName); - // Read core facets from regular deployment file, not LDA file + // Read core facets from zkSync deployment file (same as regular script) address facetAddress = _getConfigContractAddress( - regularDeploymentPath, + path, string.concat(".", facetName) ); - bytes4[] memory selectors = getSelectors( - facetName, - getExcludes(facetName) - ); + emit log_named_address("Facet address", facetAddress); + + bytes4[] memory selectors = getSelectors(facetName, exclude); + emit log("Facet selectors:"); + for (uint256 j = 0; j < selectors.length; j++) { + emit log_bytes32(selectors[j]); + } // at this point we know for sure that LDA diamond loupe exists on diamond buildDiamondCut(selectors, facetAddress); @@ -203,12 +182,15 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { return (facets, cutData); } + emit log("=== EXECUTING FINAL DIAMONDCUT ==="); vm.startBroadcast(deployerPrivateKey); if (cut.length > 0) { cutter.diamondCut(cut, address(0), ""); } vm.stopBroadcast(); + emit log("=== FINAL DIAMONDCUT COMPLETED ==="); facets = loupe.facetAddresses(); + emit log("=== UpdateLDACoreFacets COMPLETED ==="); } } diff --git a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol index 1e347bac6..ba0aa6e0f 100644 --- a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol @@ -95,7 +95,7 @@ contract UpdateLDAScriptBase is LDAScriptBase { function getSelectors( string memory _facetName, bytes4[] memory _exclude - ) internal virtual returns (bytes4[] memory selectors) { + ) internal returns (bytes4[] memory selectors) { string[] memory cmd = new string[](3); cmd[0] = "script/deploy/zksync/LDA/utils/contract-selectors.sh"; cmd[1] = _facetName; From 48b7fafc95ea75b6a06c452931df111d2236a239 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 4 Sep 2025 22:33:03 +0200 Subject: [PATCH 159/220] update deploySingleContract to also save addresses of lda contracts in network.json file --- deployments/_deployments_log_file.json | 4 ++-- deployments/arbitrum.lda.staging.json | 2 +- deployments/arbitrum.staging.json | 13 ++++++++++++- deployments/optimism.staging.json | 13 ++++++++++++- script/deploy/deploySingleContract.sh | 15 +++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 4f9377d43..b81d3f178 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39998,9 +39998,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xE6fc9224a622df3E5204f1165210102C4BD9A6c8", + "ADDRESS": "0x897e12b5f25187648561A2e719e2ad22125626Ca", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 19:50:27", + "TIMESTAMP": "2025-09-04 21:54:15", "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000b2a8517734cdf985d53f303a1f7759a34fdc772f", "SALT": "", "VERIFIED": "false", diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json index f7ba59dff..07b73728f 100644 --- a/deployments/arbitrum.lda.staging.json +++ b/deployments/arbitrum.lda.staging.json @@ -1,5 +1,5 @@ { - "LiFiDEXAggregatorDiamond": "0xE6fc9224a622df3E5204f1165210102C4BD9A6c8", + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index ec631b8d6..7757edbf0 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -58,5 +58,16 @@ "LiFiDEXAggregator": "0x14aB08312a1EA45F76fd83AaE89A3118537FC06D", "Patcher": "0x18069208cA7c2D55aa0073E047dD45587B26F6D4", "WhitelistManagerFacet": "0x603f0c31B37E5ca3eA75D5730CCfaBCFF6D17aa3", - "RelayDepositoryFacet": "0x004E291b9244C811B0BE00cA2C179d54FAA5073D" + "RelayDepositoryFacet": "0x004E291b9244C811B0BE00cA2C179d54FAA5073D", + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" } \ No newline at end of file diff --git a/deployments/optimism.staging.json b/deployments/optimism.staging.json index 1c0cb7e4c..4037b0882 100644 --- a/deployments/optimism.staging.json +++ b/deployments/optimism.staging.json @@ -46,5 +46,16 @@ "Permit2Proxy": "0x808eb38763f3F51F9C47bc93Ef8d5aB7E6241F46", "GasZipFacet": "0xfEeCe7B3e68B9cBeADB60598973704a776ac3ca1", "MayanFacet": "0x59A1Bcaa32EdB1a233fEF945857529BBD6df247f", - "GlacisFacet": "0x36e1375B0755162d720276dFF6893DF02bd49225" + "GlacisFacet": "0x36e1375B0755162d720276dFF6893DF02bd49225", + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" } \ No newline at end of file diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index adb2499d1..46374501e 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -388,6 +388,21 @@ deploySingleContract() { # save contract in network-specific deployment files saveContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$FILE_SUFFIX" + + # For LDA contracts, also save to the regular network deployment file + # This ensures all contracts deployed to a network are tracked in the main deployment log + if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + # Get the regular file suffix (without "lda." prefix) + local REGULAR_FILE_SUFFIX + if [[ "$ENVIRONMENT" == "production" ]]; then + REGULAR_FILE_SUFFIX="" + else + REGULAR_FILE_SUFFIX="staging." + fi + + echo "[info] Also saving LDA contract $CONTRACT to regular deployment file for complete network tracking" + saveContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$REGULAR_FILE_SUFFIX" + fi return 0 } From bc3da25b9f15168efcee12abd815669efaf501c8 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 4 Sep 2025 22:54:36 +0200 Subject: [PATCH 160/220] Renamed test files --- test/solidity/Facets/DexManagerFacet.t.sol | 4 ++-- .../DiamondTest.sol => LiFiDiamond.t.sol} | 4 ++-- test/solidity/Periphery/GasZipPeriphery.t.sol | 8 ++++---- .../Periphery/LDA/BaseCoreRoute.t.sol | 9 ++++++--- .../LDA/LiFiDEXAggregatorDiamond.t.sol} | 4 ++-- test/solidity/utils/TestBase.sol | 20 +++++++++++-------- 6 files changed, 28 insertions(+), 21 deletions(-) rename test/solidity/{utils/DiamondTest.sol => LiFiDiamond.t.sol} (90%) rename test/solidity/{utils/DEXAggregatorDiamondTest.sol => Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol} (93%) diff --git a/test/solidity/Facets/DexManagerFacet.t.sol b/test/solidity/Facets/DexManagerFacet.t.sol index 14e034cb4..99106b12f 100644 --- a/test/solidity/Facets/DexManagerFacet.t.sol +++ b/test/solidity/Facets/DexManagerFacet.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.17; import { DSTest } from "ds-test/test.sol"; -import { DiamondTest } from "../utils/DiamondTest.sol"; +import { LiFiDiamondTest } from "../LiFiDiamond.t.sol"; import { DexManagerFacet } from "lifi/Facets/DexManagerFacet.sol"; import { AccessManagerFacet } from "lifi/Facets/AccessManagerFacet.sol"; import { InvalidContract, CannotAuthoriseSelf, UnAuthorized } from "lifi/Errors/GenericErrors.sol"; contract Foo {} -contract DexManagerFacetTest is DSTest, DiamondTest { +contract DexManagerFacetTest is DSTest, LiFiDiamondTest { address internal constant NOT_DIAMOND_OWNER = address(0xabc123456); DexManagerFacet internal dexMgr; diff --git a/test/solidity/utils/DiamondTest.sol b/test/solidity/LiFiDiamond.t.sol similarity index 90% rename from test/solidity/utils/DiamondTest.sol rename to test/solidity/LiFiDiamond.t.sol index c1cc6be4d..0b6cc8794 100644 --- a/test/solidity/utils/DiamondTest.sol +++ b/test/solidity/LiFiDiamond.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.17; import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; -import { CommonDiamondTest } from "./CommonDiamondTest.sol"; +import { CommonDiamondTest } from "./utils/CommonDiamondTest.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -contract DiamondTest is CommonDiamondTest { +contract LiFiDiamondTest is CommonDiamondTest { function setUp() public virtual override { super.setUp(); // Call createDiamond to get a fully configured diamond with all facets diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index cec004384..399ce42fd 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -5,15 +5,15 @@ import { GasZipPeriphery } from "lifi/Periphery/GasZipPeriphery.sol"; import { LibSwap } from "lifi/Libraries/LibSwap.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { TestGnosisBridgeFacet } from "test/solidity/Facets/GnosisBridgeFacet.t.sol"; -import { TestBase, ILiFi } from "../utils/TestBase.sol"; import { IGnosisBridgeRouter } from "lifi/Interfaces/IGnosisBridgeRouter.sol"; import { IGasZip } from "lifi/Interfaces/IGasZip.sol"; -import { NonETHReceiver } from "../utils/TestHelpers.sol"; import { InvalidCallData } from "lifi/Errors/GenericErrors.sol"; -import { DEXAggregatorDiamondTest } from "../utils/DEXAggregatorDiamondTest.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; +import { TestBase, ILiFi } from "../utils/TestBase.sol"; +import { NonETHReceiver } from "../utils/TestHelpers.sol"; +import { LiFiDEXAggregatorDiamondTest } from "./LDA/LiFiDEXAggregatorDiamond.t.sol"; // Stub GenericSwapFacet Contract contract TestGasZipPeriphery is GasZipPeriphery { @@ -61,7 +61,7 @@ contract GasZipPeripheryTest is TestBase { function setUp() public override { customBlockNumberForForking = 22566858; initTestBase(); - DEXAggregatorDiamondTest.setUp(); + LiFiDEXAggregatorDiamondTest.setUp(); // deploy contracts gasZipPeriphery = new TestGasZipPeriphery( diff --git a/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol index 5f97f2ee0..83165a588 100644 --- a/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol +++ b/test/solidity/Periphery/LDA/BaseCoreRoute.t.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { TestHelpers } from "../../utils/TestHelpers.sol"; -import { DEXAggregatorDiamondTest } from "../../utils/DEXAggregatorDiamondTest.sol"; +import { LiFiDEXAggregatorDiamondTest } from "./LiFiDEXAggregatorDiamond.t.sol"; /// @title BaseCoreRouteTest /// @notice Shared utilities to build route bytes and execute swaps against `CoreRouteFacet`. @@ -15,7 +15,10 @@ import { DEXAggregatorDiamondTest } from "../../utils/DEXAggregatorDiamondTest.s /// - Event expectations helpers /// - Overloads of `_executeAndVerifySwap` including revert path /// Concrete tests compose these helpers to succinctly define swap scenarios. -abstract contract BaseCoreRouteTest is DEXAggregatorDiamondTest, TestHelpers { +abstract contract BaseCoreRouteTest is + LiFiDEXAggregatorDiamondTest, + TestHelpers +{ using SafeERC20 for IERC20; // ==== Types ==== @@ -120,7 +123,7 @@ abstract contract BaseCoreRouteTest is DEXAggregatorDiamondTest, TestHelpers { /// @notice Deploys and attaches `CoreRouteFacet` to the diamond under test. /// @dev Invoked from `setUp` of child tests via inheritance chain. function setUp() public virtual override { - DEXAggregatorDiamondTest.setUp(); + LiFiDEXAggregatorDiamondTest.setUp(); _addCoreRouteFacet(); } diff --git a/test/solidity/utils/DEXAggregatorDiamondTest.sol b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol similarity index 93% rename from test/solidity/utils/DEXAggregatorDiamondTest.sol rename to test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol index 8088b8746..30ab6660e 100644 --- a/test/solidity/utils/DEXAggregatorDiamondTest.sol +++ b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.17; import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { CommonDiamondTest } from "./CommonDiamondTest.sol"; +import { CommonDiamondTest } from "../../utils/CommonDiamondTest.sol"; /// @title DEXAggregatorDiamondTest /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. /// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. -contract DEXAggregatorDiamondTest is CommonDiamondTest { +contract LiFiDEXAggregatorDiamondTest is CommonDiamondTest { LiFiDEXAggregatorDiamond public ldaDiamond; function setUp() public virtual override { diff --git a/test/solidity/utils/TestBase.sol b/test/solidity/utils/TestBase.sol index b90760e42..9a2a78817 100644 --- a/test/solidity/utils/TestBase.sol +++ b/test/solidity/utils/TestBase.sol @@ -1,20 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { ERC20 } from "solmate/tokens/ERC20.sol"; import { Test } from "forge-std/Test.sol"; +import { stdJson } from "forge-std/StdJson.sol"; import { DSTest } from "ds-test/test.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; import { LibSwap } from "lifi/Libraries/LibSwap.sol"; -import { UniswapV2Router02 } from "../utils/Interfaces.sol"; -import { ERC20 } from "solmate/tokens/ERC20.sol"; import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; import { FeeCollector } from "lifi/Periphery/FeeCollector.sol"; -import { ReentrancyError, ETHTransferFailed } from "src/Errors/GenericErrors.sol"; -import { stdJson } from "forge-std/StdJson.sol"; +import { ReentrancyError, ETHTransferFailed } from "lifi/Errors/GenericErrors.sol"; +import { LiFiDEXAggregatorDiamondTest } from "../Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol"; +import { UniswapV2Router02 } from "../utils/Interfaces.sol"; import { TestBaseForksConstants } from "./TestBaseForksConstants.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; import { TestHelpers } from "./TestHelpers.sol"; -import { DEXAggregatorDiamondTest } from "./DEXAggregatorDiamondTest.sol"; using stdJson for string; @@ -97,7 +97,7 @@ abstract contract TestBase is TestBaseForksConstants, TestBaseRandomConstants, TestHelpers, - DEXAggregatorDiamondTest, + LiFiDEXAggregatorDiamondTest, ILiFi { address internal _facetTestContractAddress; @@ -137,7 +137,11 @@ abstract contract TestBase is // MODIFIERS //@dev token == address(0) => check balance of native token - modifier assertBalanceChange(address token, address user, int256 amount) { + modifier assertBalanceChange( + address token, + address user, + int256 amount + ) { // store initial balance if (token == address(0)) { initialBalances[token][user] = user.balance; @@ -239,7 +243,7 @@ abstract contract TestBase is weth = ERC20(ADDRESS_WRAPPED_NATIVE); // deploy & configure LiFiDiamond and LiFiDEXAggregatorDiamond - DEXAggregatorDiamondTest.setUp(); + LiFiDEXAggregatorDiamondTest.setUp(); // deploy feeCollector feeCollector = new FeeCollector(USER_DIAMOND_OWNER); From 4da34d5ded1f80b065860d76e8226274d832969f Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 01:18:15 +0200 Subject: [PATCH 161/220] Refactor update scripts to enhance clarity and maintainability, including the introduction of BaseUpdateScript and BaseZkSyncUpdateScript for improved structure --- deployments/_deployments_log_file.json | 165 ++++++++++++ deployments/base.lda.staging.json | 13 + deployments/base.staging.json | 13 +- .../facets/LDA/utils/UpdateLDAScriptBase.sol | 215 ++------------- .../deploy/facets/utils/BaseUpdateScript.sol | 221 +++++++++++++++ .../deploy/facets/utils/UpdateScriptBase.sol | 251 +++--------------- .../LDA/DeployAlgebraFacet.zksync.s.sol | 6 +- .../LDA/DeployCoreRouteFacet.zksync.s.sol | 6 +- .../zksync/LDA/DeployCurveFacet.zksync.s.sol | 6 +- .../LDA/DeployIzumiV3Facet.zksync.s.sol | 6 +- .../LDA/DeployKatanaV3Facet.zksync.s.sol | 6 +- ...eployLiFiDEXAggregatorDiamond.zksync.s.sol | 6 +- .../LDA/DeployNativeWrapperFacet.zksync.s.sol | 6 +- .../LDA/DeploySyncSwapV2Facet.zksync.s.sol | 6 +- .../LDA/DeployUniV2StyleFacet.zksync.s.sol | 6 +- .../LDA/DeployUniV3StyleFacet.zksync.s.sol | 6 +- .../LDA/DeployVelodromeV2Facet.zksync.s.sol | 6 +- .../LDA/UpdateLDACoreFacets.zksync.s.sol | 2 +- .../zksync/LDA/utils/DeployLDAScriptBase.sol | 80 ------ .../deploy/zksync/LDA/utils/LDAScriptBase.sol | 45 ---- .../zksync/LDA/utils/UpdateLDAScriptBase.sol | 215 ++------------- .../zksync/LDA/utils/contract-selectors.sh | 11 - .../zksync/utils/BaseZkSyncUpdateScript.sol | 212 +++++++++++++++ .../deploy/zksync/utils/DeployScriptBase.sol | 6 +- .../deploy/zksync/utils/UpdateScriptBase.sol | 220 ++------------- 25 files changed, 749 insertions(+), 986 deletions(-) create mode 100644 deployments/base.lda.staging.json create mode 100644 script/deploy/facets/utils/BaseUpdateScript.sol delete mode 100644 script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol delete mode 100644 script/deploy/zksync/LDA/utils/LDAScriptBase.sol delete mode 100755 script/deploy/zksync/LDA/utils/contract-selectors.sh create mode 100644 script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index b81d3f178..0fe0ca610 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39803,6 +39803,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:51:51", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV3StyleFacet": { @@ -39850,6 +39865,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:59:04", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV2StyleFacet": { @@ -39897,6 +39927,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:58:03", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "NativeWrapperFacet": { @@ -39944,6 +39989,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:55:58", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "IzumiV3Facet": { @@ -39991,6 +40051,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:53:55", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LiFiDEXAggregatorDiamond": { @@ -40038,6 +40113,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:49:44", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000d3f8e9697ecc837753ca29aa10c039ead8d1025d", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "AlgebraFacet": { @@ -40085,6 +40175,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:50:50", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "CurveFacet": { @@ -40132,6 +40237,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:52:53", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "KatanaV3Facet": { @@ -40179,6 +40299,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:54:57", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "SyncSwapV2Facet": { @@ -40226,6 +40361,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:57:01", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "VelodromeV2Facet": { @@ -40273,6 +40423,21 @@ } ] } + }, + "base": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-04 23:59:57", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } } } diff --git a/deployments/base.lda.staging.json b/deployments/base.lda.staging.json new file mode 100644 index 000000000..07b73728f --- /dev/null +++ b/deployments/base.lda.staging.json @@ -0,0 +1,13 @@ +{ + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" +} \ No newline at end of file diff --git a/deployments/base.staging.json b/deployments/base.staging.json index e63b33a7a..7935b0026 100644 --- a/deployments/base.staging.json +++ b/deployments/base.staging.json @@ -16,5 +16,16 @@ "WithdrawFacet": "0x6C4f0E7Ef3564f6bc6FE5590B927dC053A005b04", "LiFiDiamond": "0x947330863B5BA5E134fE8b73e0E1c7Eed90446C7", "MayanFacet": "0xA77abE6A3E0Fe927d503c68fCF9B26e11A8bB1A4", - "GlacisFacet": "0xB4735037e3a6C40a47061A111Fd4E912c9b8e106" + "GlacisFacet": "0xB4735037e3a6C40a47061A111Fd4E912c9b8e106", + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" } \ No newline at end of file diff --git a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol index 2eda7cc07..03cc203b3 100644 --- a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol @@ -1,213 +1,34 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { ScriptBase } from "../../utils/ScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { BaseUpdateScript } from "../../utils/BaseUpdateScript.sol"; -contract UpdateLDAScriptBase is ScriptBase { +contract UpdateLDAScriptBase is BaseUpdateScript { using stdJson for string; - error InvalidHexDigit(uint8 d); - - address internal ldaDiamond; - LibDiamond.FacetCut[] internal cut; - bytes4[] internal selectorsToReplace; - bytes4[] internal selectorsToRemove; - bytes4[] internal selectorsToAdd; - DiamondCutFacet internal cutter; - DiamondLoupeFacet internal loupe; - string internal path; - string internal json; - bool internal noBroadcast = false; - - constructor() { - noBroadcast = vm.envOr("NO_BROADCAST", false); - - // Create LDA-specific deployment file path - path = string.concat( - root, - "/deployments/", - network, - ".lda.", - fileSuffix, - "json" - ); - json = vm.readFile(path); - - // Get LDA Diamond address (not LiFi Diamond) - ldaDiamond = json.readAddress(".LiFiDEXAggregatorDiamond"); - cutter = DiamondCutFacet(ldaDiamond); - loupe = DiamondLoupeFacet(ldaDiamond); - } - - function update( - string memory name - ) + function _buildDeploymentPath() internal - virtual - returns (address[] memory facets, bytes memory cutData) + view + override + returns (string memory) { - address facet = json.readAddress(string.concat(".", name)); - - bytes4[] memory excludes = getExcludes(); - bytes memory callData = getCallData(); - - buildDiamondCut(getSelectors(name, excludes), facet); - - // prepare full diamondCut calldata and log for debugging purposes - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - callData.length > 0 ? facet : address(0), - callData + return + string.concat( + root, + "/deployments/", + network, + ".lda.", + fileSuffix, + "json" ); - - emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); - emit log_bytes(cutData); - emit log("=== END CALLDATA ==="); - } - - if (noBroadcast) { - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - - if (cut.length > 0) { - cutter.diamondCut( - cut, - callData.length > 0 ? facet : address(0), - callData - ); - } - - facets = loupe.facetAddresses(); - - vm.stopBroadcast(); - } - - function getExcludes() internal virtual returns (bytes4[] memory) {} - - function getCallData() internal virtual returns (bytes memory) {} - - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; // Default to regular contract-selectors - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - - function buildDiamondCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - address oldFacet; - - selectorsToAdd = new bytes4[](0); - selectorsToReplace = new bytes4[](0); - selectorsToRemove = new bytes4[](0); - - // Get selectors to add or replace - for (uint256 i; i < newSelectors.length; i++) { - if (loupe.facetAddress(newSelectors[i]) == address(0)) { - selectorsToAdd.push(newSelectors[i]); - // Don't replace if the new facet address is the same as the old facet address - } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { - selectorsToReplace.push(newSelectors[i]); - oldFacet = loupe.facetAddress(newSelectors[i]); - } - } - - // Get selectors to remove - bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); - for (uint256 i; i < oldSelectors.length; i++) { - bool found = false; - for (uint256 j; j < newSelectors.length; j++) { - if (oldSelectors[i] == newSelectors[j]) { - found = true; - break; - } - } - if (!found) { - selectorsToRemove.push(oldSelectors[i]); - } - } - - // Build diamond cut - if (selectorsToReplace.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Replace, - functionSelectors: selectorsToReplace - }) - ); - } - - if (selectorsToRemove.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(0), - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: selectorsToRemove - }) - ); - } - - if (selectorsToAdd.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectorsToAdd - }) - ); - } - } - - function buildInitialCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: newSelectors - }) - ); } - function toHexDigit(uint8 d) internal pure returns (bytes1) { - if (0 <= d && d <= 9) { - return bytes1(uint8(bytes1("0")) + d); - } else if (10 <= uint8(d) && uint8(d) <= 15) { - return bytes1(uint8(bytes1("a")) + d - 10); - } - revert InvalidHexDigit(d); + function _getDiamondAddress() internal override returns (address) { + return json.readAddress(".LiFiDEXAggregatorDiamond"); } - function fromCode(bytes4 code) public pure returns (string memory) { - bytes memory result = new bytes(10); - result[0] = bytes1("0"); - result[1] = bytes1("x"); - for (uint256 i = 0; i < 4; ++i) { - result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); - result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); - } - return string(result); + function _shouldUseDefaultDiamond() internal pure override returns (bool) { + return false; // LDA doesn't use the USE_DEF_DIAMOND env var } } diff --git a/script/deploy/facets/utils/BaseUpdateScript.sol b/script/deploy/facets/utils/BaseUpdateScript.sol new file mode 100644 index 000000000..651bffa09 --- /dev/null +++ b/script/deploy/facets/utils/BaseUpdateScript.sol @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { ScriptBase } from "./ScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; + +abstract contract BaseUpdateScript is ScriptBase { + using stdJson for string; + + error InvalidHexDigit(uint8 d); + + struct FunctionSignature { + string name; + bytes sig; + } + + address internal diamond; + LibDiamond.FacetCut[] internal cut; + bytes4[] internal selectorsToReplace; + bytes4[] internal selectorsToRemove; + bytes4[] internal selectorsToAdd; + DiamondCutFacet internal cutter; + DiamondLoupeFacet internal loupe; + string internal path; + string internal json; + bool internal noBroadcast = false; + bool internal useDefaultDiamond; + + constructor() { + useDefaultDiamond = _shouldUseDefaultDiamond(); + noBroadcast = vm.envOr("NO_BROADCAST", false); + + path = _buildDeploymentPath(); + json = vm.readFile(path); + diamond = _getDiamondAddress(); + cutter = DiamondCutFacet(diamond); + loupe = DiamondLoupeFacet(diamond); + } + + function update( + string memory name + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + address facet = json.readAddress(string.concat(".", name)); + + bytes4[] memory excludes = getExcludes(); + bytes memory callData = getCallData(); + + buildDiamondCut(getSelectors(name, excludes), facet); + + // prepare full diamondCut calldata and log for debugging purposes + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + callData.length > 0 ? facet : address(0), + callData + ); + + emit log("DiamondCutCalldata: "); + emit log_bytes(cutData); + } + + if (noBroadcast) { + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + + if (cut.length > 0) { + cutter.diamondCut( + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } + + function getExcludes() internal virtual returns (bytes4[] memory) {} + + function getCallData() internal virtual returns (bytes memory) {} + + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + + function buildDiamondCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + address oldFacet; + + selectorsToAdd = new bytes4[](0); + selectorsToReplace = new bytes4[](0); + selectorsToRemove = new bytes4[](0); + + // Get selectors to add or replace + for (uint256 i; i < newSelectors.length; i++) { + if (loupe.facetAddress(newSelectors[i]) == address(0)) { + selectorsToAdd.push(newSelectors[i]); + // Don't replace if the new facet address is the same as the old facet address + } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { + selectorsToReplace.push(newSelectors[i]); + oldFacet = loupe.facetAddress(newSelectors[i]); + } + } + + // Get selectors to remove + bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); + for (uint256 i; i < oldSelectors.length; i++) { + bool found = false; + for (uint256 j; j < newSelectors.length; j++) { + if (oldSelectors[i] == newSelectors[j]) { + found = true; + break; + } + } + if (!found) { + selectorsToRemove.push(oldSelectors[i]); + } + } + + // Build diamond cut + if (selectorsToReplace.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Replace, + functionSelectors: selectorsToReplace + }) + ); + } + + if (selectorsToRemove.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(0), + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: selectorsToRemove + }) + ); + } + + if (selectorsToAdd.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectorsToAdd + }) + ); + } + } + + function buildInitialCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: newSelectors + }) + ); + } + + function toHexDigit(uint8 d) internal pure returns (bytes1) { + if (0 <= d && d <= 9) { + return bytes1(uint8(bytes1("0")) + d); + } else if (10 <= uint8(d) && uint8(d) <= 15) { + return bytes1(uint8(bytes1("a")) + d - 10); + } + revert InvalidHexDigit(d); + } + + function fromCode(bytes4 code) public pure returns (string memory) { + bytes memory result = new bytes(10); + result[0] = bytes1("0"); + result[1] = bytes1("x"); + for (uint256 i = 0; i < 4; ++i) { + result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); + result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); + } + return string(result); + } + + // Abstract functions for customization + function _shouldUseDefaultDiamond() internal virtual returns (bool) { + return vm.envOr("USE_DEF_DIAMOND", true); + } + + function _buildDeploymentPath() + internal + view + virtual + returns (string memory); + function _getDiamondAddress() internal virtual returns (address); +} diff --git a/script/deploy/facets/utils/UpdateScriptBase.sol b/script/deploy/facets/utils/UpdateScriptBase.sol index 9c457044d..dc3ee1879 100644 --- a/script/deploy/facets/utils/UpdateScriptBase.sol +++ b/script/deploy/facets/utils/UpdateScriptBase.sol @@ -1,235 +1,52 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { ScriptBase } from "./ScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { AccessManagerFacet } from "lifi/Facets/AccessManagerFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { BaseUpdateScript } from "./BaseUpdateScript.sol"; -contract UpdateScriptBase is ScriptBase { +contract UpdateScriptBase is BaseUpdateScript { using stdJson for string; - error InvalidHexDigit(uint8 d); - - struct FunctionSignature { - string name; - bytes sig; - } - struct Approval { address aTokenAddress; address bContractAddress; } - address internal diamond; - LibDiamond.FacetCut[] internal cut; - bytes4[] internal selectorsToReplace; - bytes4[] internal selectorsToRemove; - bytes4[] internal selectorsToAdd; - DiamondCutFacet internal cutter; - DiamondLoupeFacet internal loupe; - string internal path; - string internal json; - bool internal noBroadcast = false; - bool internal useDefaultDiamond; - - constructor() { - useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); - noBroadcast = vm.envOr("NO_BROADCAST", false); - - path = string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - json = vm.readFile(path); - diamond = useDefaultDiamond - ? json.readAddress(".LiFiDiamond") - : json.readAddress(".LiFiDiamondImmutable"); - cutter = DiamondCutFacet(diamond); - loupe = DiamondLoupeFacet(diamond); - } - - function update( - string memory name - ) + function _buildDeploymentPath() internal - virtual - returns (address[] memory facets, bytes memory cutData) + view + override + returns (string memory) { - address facet = json.readAddress(string.concat(".", name)); - - bytes4[] memory excludes = getExcludes(); - bytes memory callData = getCallData(); - - buildDiamondCut(getSelectors(name, excludes), facet); - - // prepare full diamondCut calldata and log for debugging purposes - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - callData.length > 0 ? facet : address(0), - callData - ); - - emit log("DiamondCutCalldata: "); - emit log_bytes(cutData); - } - - if (noBroadcast) { - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - - if (cut.length > 0) { - cutter.diamondCut( - cut, - callData.length > 0 ? facet : address(0), - callData - ); - } - - facets = loupe.facetAddresses(); - - vm.stopBroadcast(); - } - - function getExcludes() internal virtual returns (bytes4[] memory) {} - - function getCallData() internal virtual returns (bytes memory) {} - - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - - function buildDiamondCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - address oldFacet; - - selectorsToAdd = new bytes4[](0); - selectorsToReplace = new bytes4[](0); - selectorsToRemove = new bytes4[](0); - - // Get selectors to add or replace - for (uint256 i; i < newSelectors.length; i++) { - if (loupe.facetAddress(newSelectors[i]) == address(0)) { - selectorsToAdd.push(newSelectors[i]); - // Don't replace if the new facet address is the same as the old facet address - } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { - selectorsToReplace.push(newSelectors[i]); - oldFacet = loupe.facetAddress(newSelectors[i]); - } - } - - // Get selectors to remove - bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); - for (uint256 i; i < oldSelectors.length; i++) { - bool found = false; - for (uint256 j; j < newSelectors.length; j++) { - if (oldSelectors[i] == newSelectors[j]) { - found = true; - break; - } - } - if (!found) { - selectorsToRemove.push(oldSelectors[i]); - } - } - - // Build diamond cut - if (selectorsToReplace.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Replace, - functionSelectors: selectorsToReplace - }) - ); - } - - if (selectorsToRemove.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(0), - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: selectorsToRemove - }) - ); - } - - if (selectorsToAdd.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectorsToAdd - }) + return + string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" ); - } } - function buildInitialCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: newSelectors - }) - ); - } - - function toHexDigit(uint8 d) internal pure returns (bytes1) { - if (0 <= d && d <= 9) { - return bytes1(uint8(bytes1("0")) + d); - } else if (10 <= uint8(d) && uint8(d) <= 15) { - return bytes1(uint8(bytes1("a")) + d - 10); - } - revert InvalidHexDigit(d); - } - - function fromCode(bytes4 code) public pure returns (string memory) { - bytes memory result = new bytes(10); - result[0] = bytes1("0"); - result[1] = bytes1("x"); - for (uint256 i = 0; i < 4; ++i) { - result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); - result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); - } - return string(result); + function _getDiamondAddress() internal override returns (address) { + return + useDefaultDiamond + ? json.readAddress(".LiFiDiamond") + : json.readAddress(".LiFiDiamondImmutable"); } function approveRefundWallet() internal { // get refund wallet address from global config file - path = string.concat(root, "/config/global.json"); - json = vm.readFile(path); - address refundWallet = json.readAddress(".refundWallet"); + string memory globalPath = string.concat(root, "/config/global.json"); + string memory globalJson = vm.readFile(globalPath); + address refundWallet = globalJson.readAddress(".refundWallet"); // get function signatures that should be approved for refundWallet - bytes memory rawConfig = json.parseRaw(".approvedSigsForRefundWallet"); + bytes memory rawConfig = globalJson.parseRaw( + ".approvedSigsForRefundWallet" + ); // parse raw data from config into FunctionSignature array FunctionSignature[] memory funcSigsToBeApproved = abi.decode( @@ -249,13 +66,13 @@ contract UpdateScriptBase is ScriptBase { } function approveDeployerWallet() internal { - // get refund wallet address from global config file - path = string.concat(root, "/config/global.json"); - json = vm.readFile(path); - address refundWallet = json.readAddress(".deployerWallet"); + // get deployer wallet address from global config file + string memory globalPath = string.concat(root, "/config/global.json"); + string memory globalJson = vm.readFile(globalPath); + address deployerWallet = globalJson.readAddress(".deployerWallet"); - // get function signatures that should be approved for refundWallet - bytes memory rawConfig = json.parseRaw( + // get function signatures that should be approved for deployerWallet + bytes memory rawConfig = globalJson.parseRaw( ".approvedSigsForDeployerWallet" ); @@ -267,10 +84,10 @@ contract UpdateScriptBase is ScriptBase { // go through array with function signatures for (uint256 i = 0; i < funcSigsToBeApproved.length; i++) { - // Register refundWallet as authorized wallet to call these functions + // Register deployerWallet as authorized wallet to call these functions AccessManagerFacet(diamond).setCanExecute( bytes4(funcSigsToBeApproved[i].sig), - refundWallet, + deployerWallet, true ); } diff --git a/script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol index 2ee324755..96529ff29 100644 --- a/script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployAlgebraFacet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("AlgebraFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("AlgebraFacet") {} function run() public returns (AlgebraFacet deployed) { deployed = AlgebraFacet(deploy(type(AlgebraFacet).creationCode)); diff --git a/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol index 5fda51651..d9fbfbcd3 100644 --- a/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { +contract DeployScript is DeployScriptBase { using stdJson for string; - constructor() DeployLDAScriptBase("CoreRouteFacet") {} + constructor() DeployScriptBase("CoreRouteFacet") {} function run() public diff --git a/script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol index 9f723542a..3d4c674da 100644 --- a/script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployCurveFacet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("CurveFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("CurveFacet") {} function run() public returns (CurveFacet deployed) { deployed = CurveFacet(deploy(type(CurveFacet).creationCode)); diff --git a/script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol index 201f91926..79b86480b 100644 --- a/script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployIzumiV3Facet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("IzumiV3Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("IzumiV3Facet") {} function run() public returns (IzumiV3Facet deployed) { deployed = IzumiV3Facet(deploy(type(IzumiV3Facet).creationCode)); diff --git a/script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol index 3eb0af671..8ab1003b6 100644 --- a/script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployKatanaV3Facet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { KatanaV3Facet } from "lifi/Periphery/LDA/Facets/KatanaV3Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("KatanaV3Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("KatanaV3Facet") {} function run() public returns (KatanaV3Facet deployed) { deployed = KatanaV3Facet(deploy(type(KatanaV3Facet).creationCode)); diff --git a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol index 878491262..93a45528f 100644 --- a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { +contract DeployScript is DeployScriptBase { using stdJson for string; - constructor() DeployLDAScriptBase("LiFiDEXAggregatorDiamond") {} + constructor() DeployScriptBase("LiFiDEXAggregatorDiamond") {} function run() public diff --git a/script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol index f6b87788c..901c20b27 100644 --- a/script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployNativeWrapperFacet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("NativeWrapperFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("NativeWrapperFacet") {} function run() public returns (NativeWrapperFacet deployed) { deployed = NativeWrapperFacet( diff --git a/script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol index f5ba26cbd..45f252e84 100644 --- a/script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeploySyncSwapV2Facet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("SyncSwapV2Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("SyncSwapV2Facet") {} function run() public returns (SyncSwapV2Facet deployed) { deployed = SyncSwapV2Facet(deploy(type(SyncSwapV2Facet).creationCode)); diff --git a/script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol index 88982d4ea..ccbccfc48 100644 --- a/script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployUniV2StyleFacet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("UniV2StyleFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("UniV2StyleFacet") {} function run() public returns (UniV2StyleFacet deployed) { deployed = UniV2StyleFacet(deploy(type(UniV2StyleFacet).creationCode)); diff --git a/script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol index fe5e99377..ab168ece9 100644 --- a/script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployUniV3StyleFacet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("UniV3StyleFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("UniV3StyleFacet") {} function run() public returns (UniV3StyleFacet deployed) { deployed = UniV3StyleFacet(deploy(type(UniV3StyleFacet).creationCode)); diff --git a/script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol b/script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol index 82491b8d1..7acd360c6 100644 --- a/script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployVelodromeV2Facet.zksync.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("VelodromeV2Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("VelodromeV2Facet") {} function run() public returns (VelodromeV2Facet deployed) { deployed = VelodromeV2Facet( diff --git a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol index 89d61b568..ac1fa8e54 100644 --- a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol +++ b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol @@ -162,7 +162,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log_bytes32(selectors[j]); } - // at this point we know for sure that LDA diamond loupe exists on diamond + // at this point we know for sure that diamond loupe exists on diamond buildDiamondCut(selectors, facetAddress); } diff --git a/script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol deleted file mode 100644 index 2b8aec63b..000000000 --- a/script/deploy/zksync/LDA/utils/DeployLDAScriptBase.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import { LDAScriptBase } from "./LDAScriptBase.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { stdJson } from "forge-std/Script.sol"; - -interface IContractDeployer { - function getNewAddressCreate2( - address _sender, - bytes32 _bytecodeHash, - bytes32 _salt, - bytes calldata _input - ) external view returns (address newAddress); -} - -contract DeployLDAScriptBase is LDAScriptBase { - using stdJson for string; - - /// @dev The prefix used to create CREATE2 addresses. - bytes32 internal salt; - string internal contractName; - address internal constant DEPLOYER_CONTRACT_ADDRESS = - 0x0000000000000000000000000000000000008006; - - constructor(string memory _contractName) { - contractName = _contractName; - string memory saltPrefix = vm.envString("DEPLOYSALT"); - salt = keccak256(abi.encodePacked(saltPrefix, contractName)); - } - - function getConstructorArgs() internal virtual returns (bytes memory) {} - - function deploy( - bytes memory creationCode - ) internal virtual returns (address payable deployed) { - bytes memory constructorArgs = getConstructorArgs(); - - string memory path = string.concat( - root, - "/zkout/", - contractName, - ".sol/", - contractName, - ".json" - ); - string memory json = vm.readFile(path); - bytes32 bytecodeHash = json.readBytes32(".hash"); - bytes memory deploymentBytecode = bytes.concat( - creationCode, - constructorArgs - ); - vm.startBroadcast(deployerPrivateKey); - - address predicted = IContractDeployer(DEPLOYER_CONTRACT_ADDRESS) - .getNewAddressCreate2( - deployerAddress, - salt, - bytecodeHash, - constructorArgs - ); - - emit log_named_address("LI.FI: Predicted Address: ", predicted); - - if (LibAsset.isContract(predicted)) { - emit log("LI.FI: Contract is already deployed"); - - return payable(predicted); - } - - // Deploy a contract using the CREATE2 opcode for deterministic addr - assembly { - let len := mload(deploymentBytecode) - let data := add(deploymentBytecode, 0x20) - deployed := create2(0, data, len, sload(salt.slot)) - } - - vm.stopBroadcast(); - } -} diff --git a/script/deploy/zksync/LDA/utils/LDAScriptBase.sol b/script/deploy/zksync/LDA/utils/LDAScriptBase.sol deleted file mode 100644 index 9544baa5b..000000000 --- a/script/deploy/zksync/LDA/utils/LDAScriptBase.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import { Script } from "forge-std/Script.sol"; -import { DSTest } from "ds-test/test.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { stdJson } from "forge-std/Script.sol"; - -contract LDAScriptBase is Script, DSTest { - using stdJson for string; - - error NotAContract(string key); - - uint256 internal deployerPrivateKey; - address internal deployerAddress; - string internal root; - string internal network; - string internal fileSuffix; - - constructor() { - deployerPrivateKey = uint256(vm.envBytes32("PRIVATE_KEY")); - deployerAddress = vm.addr(deployerPrivateKey); - root = vm.projectRoot(); - network = vm.envString("NETWORK"); - fileSuffix = vm.envString("FILE_SUFFIX"); - } - - // reads an address from a config file and makes sure that the address contains code - function _getConfigContractAddress( - string memory path, - string memory key - ) internal returns (address contractAddress) { - // load json file - string memory json = vm.readFile(path); - - // read address - contractAddress = json.readAddress(key); - - // check if address contains code - if (!LibAsset.isContract(contractAddress)) - revert( - string.concat(key, " in file ", path, " is not a contract") - ); - } -} diff --git a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol index ba0aa6e0f..7fcc78d85 100644 --- a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol @@ -1,211 +1,30 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { LDAScriptBase } from "./LDAScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { BaseZkSyncUpdateScript } from "../../utils/BaseZkSyncUpdateScript.sol"; -contract UpdateLDAScriptBase is LDAScriptBase { +contract UpdateLDAScriptBase is BaseZkSyncUpdateScript { using stdJson for string; - struct FunctionSignature { - string name; - bytes sig; - } - - address internal diamond; - LibDiamond.FacetCut[] internal cut; - bytes4[] internal selectorsToReplace; - bytes4[] internal selectorsToRemove; - bytes4[] internal selectorsToAdd; - DiamondCutFacet internal cutter; - DiamondLoupeFacet internal loupe; - string internal path; - string internal json; - bool internal noBroadcast = false; - bool internal useDefaultDiamond; - - error FailedToConvert(); - - constructor() { - useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); - noBroadcast = vm.envOr("NO_BROADCAST", false); - - path = string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - json = vm.readFile(path); - diamond = json.readAddress(".LiFiDEXAggregatorDiamond"); - cutter = DiamondCutFacet(diamond); - loupe = DiamondLoupeFacet(diamond); - } - - function update( - string memory name - ) + function _buildDeploymentPath() internal - virtual - returns (address[] memory facets, bytes memory cutData) + view + override + returns (string memory) { - address facet = json.readAddress(string.concat(".", name)); - - bytes4[] memory excludes = getExcludes(); - bytes memory callData = getCallData(); - - buildDiamondCut(getSelectors(name, excludes), facet); - - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - callData.length > 0 ? facet : address(0), - callData - ); - } - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - - if (cut.length > 0) { - cutter.diamondCut( - cut, - callData.length > 0 ? facet : address(0), - callData + return + string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" ); - } - - facets = loupe.facetAddresses(); - - vm.stopBroadcast(); - } - - function getExcludes() internal virtual returns (bytes4[] memory) {} - - function getCallData() internal virtual returns (bytes memory) {} - - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/zksync/LDA/utils/contract-selectors.sh"; - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - - function buildDiamondCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - address oldFacet; - - selectorsToAdd = new bytes4[](0); - selectorsToReplace = new bytes4[](0); - selectorsToRemove = new bytes4[](0); - - // Get selectors to add or replace - for (uint256 i; i < newSelectors.length; i++) { - if (loupe.facetAddress(newSelectors[i]) == address(0)) { - selectorsToAdd.push(newSelectors[i]); - // Don't replace if the new facet address is the same as the old facet address - } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { - selectorsToReplace.push(newSelectors[i]); - oldFacet = loupe.facetAddress(newSelectors[i]); - } - } - - // Get selectors to remove - bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); - for (uint256 i; i < oldSelectors.length; i++) { - bool found = false; - for (uint256 j; j < newSelectors.length; j++) { - if (oldSelectors[i] == newSelectors[j]) { - found = true; - break; - } - } - if (!found) { - selectorsToRemove.push(oldSelectors[i]); - } - } - - // Build diamond cut - if (selectorsToReplace.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Replace, - functionSelectors: selectorsToReplace - }) - ); - } - - if (selectorsToRemove.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(0), - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: selectorsToRemove - }) - ); - } - - if (selectorsToAdd.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectorsToAdd - }) - ); - } - } - - function buildInitialCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: newSelectors - }) - ); - } - - function toHexDigit(uint8 d) internal pure returns (bytes1) { - if (0 <= d && d <= 9) { - return bytes1(uint8(bytes1("0")) + d); - } else if (10 <= uint8(d) && uint8(d) <= 15) { - return bytes1(uint8(bytes1("a")) + d - 10); - } - revert FailedToConvert(); } - function fromCode(bytes4 code) public pure returns (string memory) { - bytes memory result = new bytes(10); - result[0] = bytes1("0"); - result[1] = bytes1("x"); - for (uint256 i = 0; i < 4; ++i) { - result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); - result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); - } - return string(result); + function _getDiamondAddress() internal override returns (address) { + return json.readAddress(".LiFiDEXAggregatorDiamond"); } } diff --git a/script/deploy/zksync/LDA/utils/contract-selectors.sh b/script/deploy/zksync/LDA/utils/contract-selectors.sh deleted file mode 100755 index 0266173ea..000000000 --- a/script/deploy/zksync/LDA/utils/contract-selectors.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -declare -a EXCLUDE -IFS=" " -read -a EXCLUDE <<< $(sed 's/0x//g' <<< "$2") -filter='[]' # Empty JSON array -for x in "${EXCLUDE[@]}"; do - filter=$(jq -n --arg x "$x" --argjson exclude "$filter" '$exclude + [$x]') -done - -SELECTORS=$(jq --argjson exclude "$filter" -r '.methodIdentifiers | . | del(.. | select(. == $exclude[])) | join(",")' ./out/zksync/$1.sol/$1.json) -cast abi-encode "f(bytes4[])" "[$SELECTORS]" diff --git a/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol b/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol new file mode 100644 index 000000000..83a0f25e2 --- /dev/null +++ b/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { ScriptBase } from "./ScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; + +abstract contract BaseZkSyncUpdateScript is ScriptBase { + using stdJson for string; + + struct FunctionSignature { + string name; + bytes sig; + } + + address internal diamond; + LibDiamond.FacetCut[] internal cut; + bytes4[] internal selectorsToReplace; + bytes4[] internal selectorsToRemove; + bytes4[] internal selectorsToAdd; + DiamondCutFacet internal cutter; + DiamondLoupeFacet internal loupe; + string internal path; + string internal json; + bool internal noBroadcast = false; + bool internal useDefaultDiamond; + + error FailedToConvert(); + + constructor() { + useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); + noBroadcast = vm.envOr("NO_BROADCAST", false); + + path = _buildDeploymentPath(); + json = vm.readFile(path); + diamond = _getDiamondAddress(); + cutter = DiamondCutFacet(diamond); + loupe = DiamondLoupeFacet(diamond); + } + + function update( + string memory name + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + address facet = json.readAddress(string.concat(".", name)); + + bytes4[] memory excludes = getExcludes(); + bytes memory callData = getCallData(); + + buildDiamondCut(getSelectors(name, excludes), facet); + + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + + if (cut.length > 0) { + cutter.diamondCut( + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } + + function getExcludes() internal virtual returns (bytes4[] memory) {} + + function getCallData() internal virtual returns (bytes memory) {} + + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/zksync/utils/contract-selectors.sh"; + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + + function buildDiamondCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + address oldFacet; + + selectorsToAdd = new bytes4[](0); + selectorsToReplace = new bytes4[](0); + selectorsToRemove = new bytes4[](0); + + // Get selectors to add or replace + for (uint256 i; i < newSelectors.length; i++) { + if (loupe.facetAddress(newSelectors[i]) == address(0)) { + selectorsToAdd.push(newSelectors[i]); + // Don't replace if the new facet address is the same as the old facet address + } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { + selectorsToReplace.push(newSelectors[i]); + oldFacet = loupe.facetAddress(newSelectors[i]); + } + } + + // Get selectors to remove + bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); + for (uint256 i; i < oldSelectors.length; i++) { + bool found = false; + for (uint256 j; j < newSelectors.length; j++) { + if (oldSelectors[i] == newSelectors[j]) { + found = true; + break; + } + } + if (!found) { + selectorsToRemove.push(oldSelectors[i]); + } + } + + // Build diamond cut + if (selectorsToReplace.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Replace, + functionSelectors: selectorsToReplace + }) + ); + } + + if (selectorsToRemove.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(0), + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: selectorsToRemove + }) + ); + } + + if (selectorsToAdd.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectorsToAdd + }) + ); + } + } + + function buildInitialCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: newSelectors + }) + ); + } + + function toHexDigit(uint8 d) internal pure returns (bytes1) { + if (0 <= d && d <= 9) { + return bytes1(uint8(bytes1("0")) + d); + } else if (10 <= uint8(d) && uint8(d) <= 15) { + return bytes1(uint8(bytes1("a")) + d - 10); + } + revert FailedToConvert(); + } + + function fromCode(bytes4 code) public pure returns (string memory) { + bytes memory result = new bytes(10); + result[0] = bytes1("0"); + result[1] = bytes1("x"); + for (uint256 i = 0; i < 4; ++i) { + result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); + result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); + } + return string(result); + } + + // Abstract functions for customization + function _buildDeploymentPath() + internal + view + virtual + returns (string memory); + function _getDiamondAddress() internal virtual returns (address); +} diff --git a/script/deploy/zksync/utils/DeployScriptBase.sol b/script/deploy/zksync/utils/DeployScriptBase.sol index 025a255bb..2806f6ce2 100644 --- a/script/deploy/zksync/utils/DeployScriptBase.sol +++ b/script/deploy/zksync/utils/DeployScriptBase.sol @@ -1,9 +1,9 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { ScriptBase } from "../../facets/utils/ScriptBase.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; import { stdJson } from "forge-std/Script.sol"; +import { LibAsset } from "lifi/Libraries/LibAsset.sol"; +import { ScriptBase } from "../../facets/utils/ScriptBase.sol"; interface IContractDeployer { function getNewAddressCreate2( diff --git a/script/deploy/zksync/utils/UpdateScriptBase.sol b/script/deploy/zksync/utils/UpdateScriptBase.sol index b6c983c1b..788e65a19 100644 --- a/script/deploy/zksync/utils/UpdateScriptBase.sol +++ b/script/deploy/zksync/utils/UpdateScriptBase.sol @@ -1,213 +1,33 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { ScriptBase } from "./ScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { BaseZkSyncUpdateScript } from "./BaseZkSyncUpdateScript.sol"; -contract UpdateScriptBase is ScriptBase { +contract UpdateScriptBase is BaseZkSyncUpdateScript { using stdJson for string; - struct FunctionSignature { - string name; - bytes sig; - } - - address internal diamond; - LibDiamond.FacetCut[] internal cut; - bytes4[] internal selectorsToReplace; - bytes4[] internal selectorsToRemove; - bytes4[] internal selectorsToAdd; - DiamondCutFacet internal cutter; - DiamondLoupeFacet internal loupe; - string internal path; - string internal json; - bool internal noBroadcast = false; - bool internal useDefaultDiamond; - - error FailedToConvert(); - - constructor() { - useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); - noBroadcast = vm.envOr("NO_BROADCAST", false); - - path = string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - json = vm.readFile(path); - diamond = useDefaultDiamond - ? json.readAddress(".LiFiDiamond") - : json.readAddress(".LiFiDiamondImmutable"); - cutter = DiamondCutFacet(diamond); - loupe = DiamondLoupeFacet(diamond); - } - - function update( - string memory name - ) + function _buildDeploymentPath() internal - virtual - returns (address[] memory facets, bytes memory cutData) + view + override + returns (string memory) { - address facet = json.readAddress(string.concat(".", name)); - - bytes4[] memory excludes = getExcludes(); - bytes memory callData = getCallData(); - - buildDiamondCut(getSelectors(name, excludes), facet); - - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - callData.length > 0 ? facet : address(0), - callData - ); - } - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - - if (cut.length > 0) { - cutter.diamondCut( - cut, - callData.length > 0 ? facet : address(0), - callData + return + string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" ); - } - - facets = loupe.facetAddresses(); - - vm.stopBroadcast(); - } - - function getExcludes() internal virtual returns (bytes4[] memory) {} - - function getCallData() internal virtual returns (bytes memory) {} - - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/zksync/utils/contract-selectors.sh"; - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - - function buildDiamondCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - address oldFacet; - - selectorsToAdd = new bytes4[](0); - selectorsToReplace = new bytes4[](0); - selectorsToRemove = new bytes4[](0); - - // Get selectors to add or replace - for (uint256 i; i < newSelectors.length; i++) { - if (loupe.facetAddress(newSelectors[i]) == address(0)) { - selectorsToAdd.push(newSelectors[i]); - // Don't replace if the new facet address is the same as the old facet address - } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { - selectorsToReplace.push(newSelectors[i]); - oldFacet = loupe.facetAddress(newSelectors[i]); - } - } - - // Get selectors to remove - bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); - for (uint256 i; i < oldSelectors.length; i++) { - bool found = false; - for (uint256 j; j < newSelectors.length; j++) { - if (oldSelectors[i] == newSelectors[j]) { - found = true; - break; - } - } - if (!found) { - selectorsToRemove.push(oldSelectors[i]); - } - } - - // Build diamond cut - if (selectorsToReplace.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Replace, - functionSelectors: selectorsToReplace - }) - ); - } - - if (selectorsToRemove.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(0), - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: selectorsToRemove - }) - ); - } - - if (selectorsToAdd.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectorsToAdd - }) - ); - } - } - - function buildInitialCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: newSelectors - }) - ); - } - - function toHexDigit(uint8 d) internal pure returns (bytes1) { - if (0 <= d && d <= 9) { - return bytes1(uint8(bytes1("0")) + d); - } else if (10 <= uint8(d) && uint8(d) <= 15) { - return bytes1(uint8(bytes1("a")) + d - 10); - } - revert FailedToConvert(); } - function fromCode(bytes4 code) public pure returns (string memory) { - bytes memory result = new bytes(10); - result[0] = bytes1("0"); - result[1] = bytes1("x"); - for (uint256 i = 0; i < 4; ++i) { - result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); - result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); - } - return string(result); + function _getDiamondAddress() internal override returns (address) { + return + useDefaultDiamond + ? json.readAddress(".LiFiDiamond") + : json.readAddress(".LiFiDiamondImmutable"); } } From 5c82d5fe3eb01efd433d5b0968cf43f167694dd2 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 12:57:37 +0200 Subject: [PATCH 162/220] Update deployment files for LDA to LiFiDEXAggregator transition. Introduce new diamond deployment files for Arbitrum and Optimism, while removing outdated LDA staging files. Enhance helper functions and deployment scripts to support the new structure, ensuring all references to LDA are updated to reflect the new naming convention. --- deployments/arbitrum.lda.diamond.staging.json | 58 ++++++ deployments/arbitrum.lda.staging.json | 13 -- deployments/base.lda.staging.json | 13 -- deployments/optimism.lda.diamond.staging.json | 58 ++++++ deployments/zksync.lda.staging.json | 12 -- script/deploy/deployAllLDAContracts.sh | 49 +++-- .../deploy/deployFacetAndAddToLDADiamond.sh | 4 +- .../facets/LDA/utils/UpdateLDAScriptBase.sol | 17 -- .../deploy/facets/utils/BaseUpdateScript.sol | 14 +- .../deploy/facets/utils/UpdateScriptBase.sol | 17 -- script/deploy/ldaHealthCheck.ts | 138 +++++++++----- .../zksync/LDA/utils/UpdateLDAScriptBase.sol | 17 -- .../zksync/utils/BaseZkSyncUpdateScript.sol | 14 +- .../deploy/zksync/utils/UpdateScriptBase.sol | 17 -- script/helperFunctions.sh | 174 +++++++++++++++--- script/tasks/ldaDiamondUpdateFacet.sh | 4 +- 16 files changed, 411 insertions(+), 208 deletions(-) create mode 100644 deployments/arbitrum.lda.diamond.staging.json delete mode 100644 deployments/arbitrum.lda.staging.json delete mode 100644 deployments/base.lda.staging.json create mode 100644 deployments/optimism.lda.diamond.staging.json delete mode 100644 deployments/zksync.lda.staging.json diff --git a/deployments/arbitrum.lda.diamond.staging.json b/deployments/arbitrum.lda.diamond.staging.json new file mode 100644 index 000000000..07905ba44 --- /dev/null +++ b/deployments/arbitrum.lda.diamond.staging.json @@ -0,0 +1,58 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": { + "0xB2A8517734CDf985d53F303A1f7759A34fdC772F": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0x3B75025167b5fEc275266F43c132709d58329cf4": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x050cd05AFf1367e6DC4c20dA96fFD31FCB39e219": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF": { + "Name": "AlgebraFacet", + "Version": "1.0.0" + }, + "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef": { + "Name": "CoreRouteFacet", + "Version": "1.0.0" + }, + "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d": { + "Name": "CurveFacet", + "Version": "1.0.0" + }, + "0x7a7D7101a9A56882b34C0AC06328367f2356BB41": { + "Name": "IzumiV3Facet", + "Version": "1.0.0" + }, + "0xf33C1c24ccc5A137231d89272a2383c28B1dd046": { + "Name": "KatanaV3Facet", + "Version": "1.0.0" + }, + "0x59A1514CD90a4c3662b3003450C8878448E6D6dD": { + "Name": "NativeWrapperFacet", + "Version": "1.0.0" + }, + "0x283831120F19fd293206AB6FaEF1C45Cf83487D0": { + "Name": "SyncSwapV2Facet", + "Version": "1.0.0" + }, + "0x181a353054883D9DdE6864Ba074226E5b77cf511": { + "Name": "UniV2StyleFacet", + "Version": "1.0.0" + }, + "0xbE76705E06154dAb3A95837166ef04d890bDeA15": { + "Name": "UniV3StyleFacet", + "Version": "1.0.0" + }, + "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374": { + "Name": "VelodromeV2Facet", + "Version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json deleted file mode 100644 index 07b73728f..000000000 --- a/deployments/arbitrum.lda.staging.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", - "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", - "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", - "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", - "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", - "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", - "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" -} \ No newline at end of file diff --git a/deployments/base.lda.staging.json b/deployments/base.lda.staging.json deleted file mode 100644 index 07b73728f..000000000 --- a/deployments/base.lda.staging.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", - "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", - "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", - "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", - "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", - "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", - "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" -} \ No newline at end of file diff --git a/deployments/optimism.lda.diamond.staging.json b/deployments/optimism.lda.diamond.staging.json new file mode 100644 index 000000000..07905ba44 --- /dev/null +++ b/deployments/optimism.lda.diamond.staging.json @@ -0,0 +1,58 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": { + "0xB2A8517734CDf985d53F303A1f7759A34fdC772F": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0x3B75025167b5fEc275266F43c132709d58329cf4": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x050cd05AFf1367e6DC4c20dA96fFD31FCB39e219": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF": { + "Name": "AlgebraFacet", + "Version": "1.0.0" + }, + "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef": { + "Name": "CoreRouteFacet", + "Version": "1.0.0" + }, + "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d": { + "Name": "CurveFacet", + "Version": "1.0.0" + }, + "0x7a7D7101a9A56882b34C0AC06328367f2356BB41": { + "Name": "IzumiV3Facet", + "Version": "1.0.0" + }, + "0xf33C1c24ccc5A137231d89272a2383c28B1dd046": { + "Name": "KatanaV3Facet", + "Version": "1.0.0" + }, + "0x59A1514CD90a4c3662b3003450C8878448E6D6dD": { + "Name": "NativeWrapperFacet", + "Version": "1.0.0" + }, + "0x283831120F19fd293206AB6FaEF1C45Cf83487D0": { + "Name": "SyncSwapV2Facet", + "Version": "1.0.0" + }, + "0x181a353054883D9DdE6864Ba074226E5b77cf511": { + "Name": "UniV2StyleFacet", + "Version": "1.0.0" + }, + "0xbE76705E06154dAb3A95837166ef04d890bDeA15": { + "Name": "UniV3StyleFacet", + "Version": "1.0.0" + }, + "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374": { + "Name": "VelodromeV2Facet", + "Version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/deployments/zksync.lda.staging.json b/deployments/zksync.lda.staging.json deleted file mode 100644 index 5d3da5cc4..000000000 --- a/deployments/zksync.lda.staging.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "LDADiamondCutFacet": "0x8Bbe822959F60D0d6a882486928C7d851bf52adc", - "LDADiamondLoupeFacet": "0x63144D19F86EeeF3722af56a6804098DD1b58640", - "LDAOwnershipFacet": "0x53303BCE53bafFCD764501D0B4B3dcC798998b47", - "LDAEmergencyPauseFacet": "0x0000000000000000000000000000000000000000", - "LDAPeripheryRegistryFacet": "0xBbA05be3150Eafa7F0Ac3F57FEEFF0e57Da14F12", - "CoreRouteFacet": "0xaFAf106f4D8eDdF68deE9F06dA0CB12BE1f6BD51", - "NativeWrapperFacet": "0x0000000000000000000000000000000000000000", - "LiFiDEXAggregatorDiamond": "0xacB28A8DF1D8073d014F14D4509C2b3a30220b32", - "AlgebraFacet": "0x6ac283a65406eef58c417ad7B83E10C75108d7C5", - "CurveFacet": "0x44BD7D613c16480287CFDDe44820dA22E2a7F848" -} \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 50e6def5b..0f46f5745 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -131,8 +131,9 @@ deployAllLDAContracts() { "3) Deploy LDA diamond" \ "4) Update LDA diamond with core facets" \ "5) Deploy non-core LDA facets and add to diamond" \ - "6) Run LDA health check only" \ - "7) Ownership transfer to timelock (production only)" + "6) Update LDA diamond deployment logs" \ + "7) Run LDA health check only" \ + "8) Ownership transfer to timelock (production only)" ) # Extract the stage number from the selection @@ -150,6 +151,8 @@ deployAllLDAContracts() { START_STAGE=6 elif [[ "$START_FROM" == *"7)"* ]]; then START_STAGE=7 + elif [[ "$START_FROM" == *"8)"* ]]; then + START_STAGE=8 else error "invalid selection: $START_FROM - exiting script now" exit 1 @@ -328,37 +331,53 @@ deployAllLDAContracts() { echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" fi - # Stage 6: Run LDA health check + # Stage 6: Update LDA diamond deployment logs if [[ $START_STAGE -le 6 ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Run LDA health check only" - bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Update LDA diamond deployment logs" + echo "[info] Updating LDA diamond logs to populate .lda.diamond.json file..." + + # Update LDA diamond logs to create/populate the .lda.diamond.json file + updateLDADiamondLogs "$ENVIRONMENT" "$NETWORK" + + # check if last command was executed successfully + checkFailure $? "update LDA diamond logs for network $NETWORK" + + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA diamond logs updated successfully" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 6 completed" + fi + + # Stage 7: Run LDA health check + if [[ $START_STAGE -le 7 ]]; then + echo "" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 7: Run LDA health check only" + bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 7 completed" # Pause and ask user if they want to continue with ownership transfer (for production) - if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 6 ]]; then + if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 7 ]]; then echo "" echo "Health check completed. Do you want to continue with ownership transfer to timelock?" echo "This should only be done if the health check shows only diamond ownership errors." - echo "Continue with stage 7 (ownership transfer)? (y/n)" + echo "Continue with stage 8 (ownership transfer)? (y/n)" read -r response if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - echo "Proceeding with stage 7..." + echo "Proceeding with stage 8..." else - echo "Skipping stage 7 - ownership transfer cancelled by user" + echo "Skipping stage 8 - ownership transfer cancelled by user" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" return fi fi fi - # Stage 7: Ownership transfer to timelock (production only) - if [[ $START_STAGE -le 7 ]]; then + # Stage 8: Ownership transfer to timelock (production only) + if [[ $START_STAGE -le 8 ]]; then if [[ "$ENVIRONMENT" == "production" ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 7: Ownership transfer to timelock (production only)" + echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 8: Ownership transfer to timelock (production only)" - # make sure SAFE_ADDRESS is available (if starting in stage 7 it's not available yet) + # make sure SAFE_ADDRESS is available (if starting in stage 8 it's not available yet) SAFE_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.safeAddress") if [[ -z "$SAFE_ADDRESS" || "$SAFE_ADDRESS" == "null" ]]; then echo "SAFE address not found in networks.json. Cannot prepare ownership transfer to Timelock" @@ -396,10 +415,10 @@ deployAllLDAContracts() { echo "" # ------------------------------------------------------------ else - echo "Stage 7 skipped - ownership transfer to timelock is only for production environment" + echo "Stage 8 skipped - ownership transfer to timelock is only for production environment" fi - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 7 completed" + echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 8 completed" fi echo "" diff --git a/script/deploy/deployFacetAndAddToLDADiamond.sh b/script/deploy/deployFacetAndAddToLDADiamond.sh index 2626b7e1a..da32e2ca6 100644 --- a/script/deploy/deployFacetAndAddToLDADiamond.sh +++ b/script/deploy/deployFacetAndAddToLDADiamond.sh @@ -69,8 +69,8 @@ function deployFacetAndAddToLDADiamond() { DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" fi - # get LDA diamond address from deployments script (using .lda. file suffix) - local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.${FILE_SUFFIX}json" + # get LDA diamond address from deployments script (using .lda.diamond. file suffix) + local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$LDA_DEPLOYMENT_FILE") # if no diamond address was found, throw an error and exit this script diff --git a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol index 03cc203b3..01fd8059e 100644 --- a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol @@ -7,23 +7,6 @@ import { BaseUpdateScript } from "../../utils/BaseUpdateScript.sol"; contract UpdateLDAScriptBase is BaseUpdateScript { using stdJson for string; - function _buildDeploymentPath() - internal - view - override - returns (string memory) - { - return - string.concat( - root, - "/deployments/", - network, - ".lda.", - fileSuffix, - "json" - ); - } - function _getDiamondAddress() internal override returns (address) { return json.readAddress(".LiFiDEXAggregatorDiamond"); } diff --git a/script/deploy/facets/utils/BaseUpdateScript.sol b/script/deploy/facets/utils/BaseUpdateScript.sol index 651bffa09..3a2df8fab 100644 --- a/script/deploy/facets/utils/BaseUpdateScript.sol +++ b/script/deploy/facets/utils/BaseUpdateScript.sol @@ -33,7 +33,14 @@ abstract contract BaseUpdateScript is ScriptBase { useDefaultDiamond = _shouldUseDefaultDiamond(); noBroadcast = vm.envOr("NO_BROADCAST", false); - path = _buildDeploymentPath(); + path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); json = vm.readFile(path); diamond = _getDiamondAddress(); cutter = DiamondCutFacet(diamond); @@ -212,10 +219,5 @@ abstract contract BaseUpdateScript is ScriptBase { return vm.envOr("USE_DEF_DIAMOND", true); } - function _buildDeploymentPath() - internal - view - virtual - returns (string memory); function _getDiamondAddress() internal virtual returns (address); } diff --git a/script/deploy/facets/utils/UpdateScriptBase.sol b/script/deploy/facets/utils/UpdateScriptBase.sol index dc3ee1879..03b70d837 100644 --- a/script/deploy/facets/utils/UpdateScriptBase.sol +++ b/script/deploy/facets/utils/UpdateScriptBase.sol @@ -13,23 +13,6 @@ contract UpdateScriptBase is BaseUpdateScript { address bContractAddress; } - function _buildDeploymentPath() - internal - view - override - returns (string memory) - { - return - string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - } - function _getDiamondAddress() internal override returns (address) { return useDefaultDiamond diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index db7fc495f..db5c64579 100644 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -81,41 +81,53 @@ const main = defineCommand({ process.exit(1) } - // Load LDA-specific deployments - const ldaDeploymentFile = `../../deployments/${network.toLowerCase()}.lda.${environment}.json` - let ldaDeployedContracts: Record - - try { - const { default: contracts } = await import(ldaDeploymentFile) - ldaDeployedContracts = contracts - } catch (error) { - consola.error(`Failed to load LDA deployment file: ${ldaDeploymentFile}`) - consola.error( - `Please ensure LDA contracts are deployed to ${environment} first.` - ) - process.exit(1) - } - - // Load main deployment file for shared infrastructure (like LiFiTimelockController and core facets) + // Load main deployment file (contains LiFiDEXAggregatorDiamond address and shared infrastructure) // For staging, try environment-specific file first, then fallback to main let mainDeployedContracts: Record = {} const mainDeploymentFile = `../../deployments/${network.toLowerCase()}.json` if (environment === 'staging') { const stagingFile = `../../deployments/${network.toLowerCase()}.staging.json` + consola.info(`Loading staging deployment file: ${stagingFile}`) try { const { default: contracts } = await import(stagingFile) mainDeployedContracts = contracts + consola.info( + `Successfully loaded ${ + Object.keys(contracts).length + } contracts from staging file` + ) + consola.info( + `LiFiDEXAggregatorDiamond address: ${ + contracts['LiFiDEXAggregatorDiamond'] || 'NOT FOUND' + }` + ) } catch (error) { + consola.warn(`Failed to load staging file: ${error}`) // Fallback to main deployment file for staging try { + consola.info( + `Falling back to main deployment file: ${mainDeploymentFile}` + ) const { default: contracts } = await import(mainDeploymentFile) mainDeployedContracts = contracts + consola.info( + `Successfully loaded ${ + Object.keys(contracts).length + } contracts from main file` + ) + consola.info( + `LiFiDEXAggregatorDiamond address: ${ + contracts['LiFiDEXAggregatorDiamond'] || 'NOT FOUND' + }` + ) } catch (fallbackError) { consola.error( `Failed to load deployment files: ${stagingFile} and ${mainDeploymentFile}` ) - consola.error('Cannot verify core facets availability.') + consola.error( + 'Cannot verify LDA diamond and core facets availability.' + ) process.exit(1) } } @@ -129,10 +141,31 @@ const main = defineCommand({ consola.error( `Failed to load main deployment file: ${mainDeploymentFile}` ) - consola.error('Cannot verify core facets availability.') + consola.error('Cannot verify LDA diamond and core facets availability.') process.exit(1) } + // Load LDA-specific facet information + const ldaDeploymentFile = + environment === 'production' + ? `../../deployments/${network.toLowerCase()}.lda.diamond.json` + : `../../deployments/${network.toLowerCase()}.lda.diamond.${environment}.json` + let ldaFacetInfo: Record< + string, + { Facets?: Record } + > = {} + + try { + const { default: ldaData } = await import(ldaDeploymentFile) + ldaFacetInfo = ldaData + } catch (error) { + consola.error(`Failed to load LDA facet file: ${ldaDeploymentFile}`) + consola.error( + `Please ensure LDA diamond logs are updated after deployment.` + ) + process.exit(1) + } + // Load global config for LDA core facets const globalConfig = await import('../../config/global.json') const networksConfigModule = await import('../../config/networks.json') @@ -153,12 +186,25 @@ const main = defineCommand({ ) // ╭─────────────────────────────────────────────────────────╮ - // │ Check LDA Diamond Contract │ + // │ Check LDA diamond contract full deployment │ // ╰─────────────────────────────────────────────────────────╯ - consola.box('Checking LDADiamond Contract...') + consola.box('Checking LDA diamond contract full deployment...') + + const diamondAddress = mainDeployedContracts['LiFiDEXAggregatorDiamond'] + consola.info( + `Looking for LiFiDEXAggregatorDiamond at address: ${diamondAddress}` + ) + consola.info( + `Available contracts in mainDeployedContracts: ${Object.keys( + mainDeployedContracts + ) + .filter((k) => k.includes('LiFi')) + .join(', ')}` + ) + const diamondDeployed = await checkIsDeployedWithCast( 'LiFiDEXAggregatorDiamond', - ldaDeployedContracts, + mainDeployedContracts, rpcUrl ) @@ -167,8 +213,6 @@ const main = defineCommand({ finish() } else consola.success('LiFiDEXAggregatorDiamond deployed') - const diamondAddress = ldaDeployedContracts['LiFiDEXAggregatorDiamond'] - // ╭─────────────────────────────────────────────────────────╮ // │ Check LDA core facets availability │ // ╰─────────────────────────────────────────────────────────╯ @@ -223,19 +267,16 @@ const main = defineCommand({ // Add core facets from main deployment file for (const facet of ldaCoreFacets) { const address = mainDeployedContracts[facet] - if (address) - configFacetsByAddress[address.toLowerCase()] = facet - + if (address) configFacetsByAddress[address.toLowerCase()] = facet } - // Add non-core facets from LDA deployment file - Object.entries(ldaDeployedContracts).forEach(([name, address]) => { - if ( - name !== 'LiFiDEXAggregatorDiamond' && - !ldaCoreFacets.includes(name) - ) - configFacetsByAddress[address.toLowerCase()] = name - + // Add non-core facets from LDA facet info file + const diamondFacets = + ldaFacetInfo['LiFiDEXAggregatorDiamond']?.Facets || {} + Object.entries(diamondFacets).forEach(([address, facetData]) => { + const facetName = facetData.Name + if (facetName && !ldaCoreFacets.includes(facetName)) + configFacetsByAddress[address.toLowerCase()] = facetName }) registeredFacets = onChainFacets @@ -250,27 +291,24 @@ const main = defineCommand({ } // Check core facets registration - for (const facet of ldaCoreFacets) - if (!registeredFacets.includes(facet)) + for (const facet of ldaCoreFacets) + if (!registeredFacets.includes(facet)) logError(`LDA Core Facet ${facet} not registered in Diamond`) - else - consola.success(`LDA Core Facet ${facet} registered in Diamond`) - - + else consola.success(`LDA Core Facet ${facet} registered in Diamond`) // Check non-core facets registration - const nonCoreFacets = Object.keys(ldaDeployedContracts).filter( - (name) => - name !== 'LiFiDEXAggregatorDiamond' && !ldaCoreFacets.includes(name) - ) + const diamondFacets = ldaFacetInfo['LiFiDEXAggregatorDiamond']?.Facets || {} + const nonCoreFacets = Object.values(diamondFacets) + .map((facetData) => facetData.Name) + .filter( + (name): name is string => + name !== undefined && !ldaCoreFacets.includes(name) + ) - for (const facet of nonCoreFacets) - if (!registeredFacets.includes(facet)) + for (const facet of nonCoreFacets) + if (!registeredFacets.includes(facet)) logError(`LDA Non-Core Facet ${facet} not registered in Diamond`) - else - consola.success(`LDA Non-Core Facet ${facet} registered in Diamond`) - - + else consola.success(`LDA Non-Core Facet ${facet} registered in Diamond`) // Basic ownership check using cast try { diff --git a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol index 7fcc78d85..36a728f7b 100644 --- a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol @@ -7,23 +7,6 @@ import { BaseZkSyncUpdateScript } from "../../utils/BaseZkSyncUpdateScript.sol"; contract UpdateLDAScriptBase is BaseZkSyncUpdateScript { using stdJson for string; - function _buildDeploymentPath() - internal - view - override - returns (string memory) - { - return - string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - } - function _getDiamondAddress() internal override returns (address) { return json.readAddress(".LiFiDEXAggregatorDiamond"); } diff --git a/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol b/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol index 83a0f25e2..3d446e632 100644 --- a/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol +++ b/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol @@ -33,7 +33,14 @@ abstract contract BaseZkSyncUpdateScript is ScriptBase { useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); noBroadcast = vm.envOr("NO_BROADCAST", false); - path = _buildDeploymentPath(); + path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); json = vm.readFile(path); diamond = _getDiamondAddress(); cutter = DiamondCutFacet(diamond); @@ -203,10 +210,5 @@ abstract contract BaseZkSyncUpdateScript is ScriptBase { } // Abstract functions for customization - function _buildDeploymentPath() - internal - view - virtual - returns (string memory); function _getDiamondAddress() internal virtual returns (address); } diff --git a/script/deploy/zksync/utils/UpdateScriptBase.sol b/script/deploy/zksync/utils/UpdateScriptBase.sol index 788e65a19..bfd701ef6 100644 --- a/script/deploy/zksync/utils/UpdateScriptBase.sol +++ b/script/deploy/zksync/utils/UpdateScriptBase.sol @@ -7,23 +7,6 @@ import { BaseZkSyncUpdateScript } from "./BaseZkSyncUpdateScript.sol"; contract UpdateScriptBase is BaseZkSyncUpdateScript { using stdJson for string; - function _buildDeploymentPath() - internal - view - override - returns (string memory) - { - return - string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - } - function _getDiamondAddress() internal override returns (address) { return useDefaultDiamond diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 873ae2c66..6555234e6 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -1130,6 +1130,66 @@ function saveDiamondPeriphery() { printf %s "$result" >"$DIAMOND_FILE" done } + +# LDA-specific diamond save functions +function saveLDADiamondFacets() { + # read function arguments into variables + NETWORK=$1 + ENVIRONMENT=$2 + FACETS=$3 + + # logging for debug purposes + echo "" + echoDebug "in function saveLDADiamondFacets" + echoDebug "NETWORK=$NETWORK" + echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "FACETS=$FACETS" + + # get file suffix based on value in variable ENVIRONMENT + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # store function arguments in variables + FACETS=$(echo "$3" | tr -d '[' | tr -d ']' | tr -d ',') + FACETS=$(printf '"%s",' "$FACETS" | sed 's/,*$//') + + # define path for LDA diamond json file + DIAMOND_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" + DIAMOND_NAME="LiFiDEXAggregatorDiamond" + + # create an empty json that replaces the existing file + echo "{}" >"$DIAMOND_FILE" + + # create an iterable FACETS array + # Remove brackets from FACETS string + FACETS_ADJ="${3#\[}" + FACETS_ADJ="${FACETS_ADJ%\]}" + # Split string into array + IFS=', ' read -ra FACET_ADDRESSES <<<"$FACETS_ADJ" + + # loop through all facets + for FACET_ADDRESS in "${FACET_ADDRESSES[@]}"; do + # get a JSON entry from log file + JSON_ENTRY=$(findContractInMasterLogByAddress "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") + + # check if contract was found in log file + if [[ $? -ne 0 ]]; then + warning "could not find any information about this facet address ($FACET_ADDRESS) in master log file while creating $DIAMOND_FILE (ENVIRONMENT=$ENVIRONMENT), " + + # try to find name of contract from network-specific deployments file + # load JSON FILE that contains deployment addresses + NAME=$(getContractNameFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") + + # create JSON entry manually with limited information (address only) + JSON_ENTRY="{\"$FACET_ADDRESS\": {\"Name\": \"$NAME\", \"Version\": \"\"}}" + fi + + # add new entry to JSON file + result=$(cat "$DIAMOND_FILE" | jq -r --argjson json_entry "$JSON_ENTRY" '.[$diamond_name] |= . + {Facets: (.Facets + $json_entry)}' --arg diamond_name "$DIAMOND_NAME" || cat "$DIAMOND_FILE") + + printf %s "$result" >"$DIAMOND_FILE" + done +} + function saveContract() { # read function arguments into variables local NETWORK=$1 @@ -4322,6 +4382,7 @@ function updateDiamondLogForNetwork() { # read function arguments into variable local NETWORK=$1 local ENVIRONMENT=$2 + local DIAMOND_TYPE=${3:-"LiFiDiamond"} # Default to LiFiDiamond, can be "LiFiDEXAggregatorDiamond" for LDA # get RPC URL local RPC_URL=$(getRPCUrl "$NETWORK") || checkFailure $? "get rpc url" @@ -4332,10 +4393,10 @@ function updateDiamondLogForNetwork() { fi # get diamond address - local DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "LiFiDiamond") + local DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$DIAMOND_TYPE") if [[ $? -ne 0 ]]; then - error "[$NETWORK] Failed to get LiFiDiamond address on $NETWORK in $ENVIRONMENT environment" + error "[$NETWORK] Failed to get $DIAMOND_TYPE address on $NETWORK in $ENVIRONMENT environment" return 1 fi @@ -4361,12 +4422,27 @@ function updateDiamondLogForNetwork() { warning "[$NETWORK] Failed to get facets from diamond $DIAMOND_ADDRESS after $MAX_ATTEMPTS_PER_SCRIPT_EXECUTION attempts" fi - if [[ -z $KNOWN_FACET_ADDRESSES ]]; then - warning "[$NETWORK] no facets found in diamond $DIAMOND_ADDRESS" - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "true" + # Determine if this is an LDA diamond to use the correct save function + if [[ "$DIAMOND_TYPE" == "LiFiDEXAggregatorDiamond" ]]; then + # For LDA diamonds, use LDA-specific save functions (no peripheries) + if [[ -z $KNOWN_FACET_ADDRESSES ]]; then + warning "[$NETWORK] no facets found in LDA diamond $DIAMOND_ADDRESS" + # LDA diamonds don't have peripheries, so just create empty diamond file + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + local DIAMOND_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" + echo "{\"$DIAMOND_TYPE\": {\"Facets\": {}}}" >"$DIAMOND_FILE" + else + saveLDADiamondFacets "$NETWORK" "$ENVIRONMENT" "$KNOWN_FACET_ADDRESSES" + fi else - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" - # saveDiamondPeriphery is executed as part of saveDiamondFacets + # For regular diamonds, use existing save functions + if [[ -z $KNOWN_FACET_ADDRESSES ]]; then + warning "[$NETWORK] no facets found in diamond $DIAMOND_ADDRESS" + saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "true" + else + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" + # saveDiamondPeriphery is executed as part of saveDiamondFacets + fi fi # check result @@ -4814,13 +4890,6 @@ deployFacetAndAddToLDADiamond() { return 0 } -# Get LDA deployment file path -getLDADeploymentFilePath() { - local NETWORK="$1" - local ENVIRONMENT="$2" - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - echo "deployments/${NETWORK}.lda.${FILE_SUFFIX}json" -} # Update LDA diamond logs updateLDADiamondLogs() { @@ -4829,15 +4898,78 @@ updateLDADiamondLogs() { echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start updateLDADiamondLogs" - if [[ -n "$NETWORK" ]]; then - echo "[info] Updating LDA diamond log for network: $NETWORK" - bunx tsx script/deploy/updateLDADiamondLog.ts --environment "$ENVIRONMENT" --network "$NETWORK" + # if no network was passed to this function, update all networks + if [[ -z $NETWORK ]]; then + # get array with all network names + NETWORKS=($(getIncludedNetworksArray)) else - echo "[info] Updating LDA diamond logs for all networks" - bunx tsx script/deploy/updateLDADiamondLog.ts --environment "$ENVIRONMENT" + NETWORKS=($NETWORK) + fi + + echo "" + echo "Now updating all LDA diamond logs on network(s): ${NETWORKS[*]}" + echo "" + + # ENVIRONMENTS=("production" "staging") + if [[ "$ENVIRONMENT" == "production" || -z "$ENVIRONMENT" ]]; then + ENVIRONMENTS=("production") + else + ENVIRONMENTS=("staging") + fi + + # Create arrays to store background job PIDs and their corresponding network/environment info + local pids=() + local job_info=() + local job_index=0 + + # loop through all networks + for NETWORK in "${NETWORKS[@]}"; do + echo "" + echo "current Network: $NETWORK" + + for ENVIRONMENT in "${ENVIRONMENTS[@]}"; do + echo " -----------------------" + echo " current ENVIRONMENT: $ENVIRONMENT" + + # Call the helper function in background for parallel execution with LDA diamond type + updateDiamondLogForNetwork "$NETWORK" "$ENVIRONMENT" "LiFiDEXAggregatorDiamond" & + + # Store the PID and job info + pids+=($!) + job_info+=("$NETWORK:$ENVIRONMENT") + job_index=$((job_index + 1)) + done + done + + # Wait for all background jobs to complete and capture exit codes + echo "Waiting for all LDA diamond log updates to complete..." + local failed_jobs=() + local job_count=${#pids[@]} + + for i in "${!pids[@]}"; do + local pid="${pids[$i]}" + local info="${job_info[$i]}" + + # Wait for this specific job and capture its exit code + if wait "$pid"; then + echo "[$info] Completed successfully" + else + echo "[$info] Failed with exit code $?" + failed_jobs+=("$info") + fi + done + + # Check if any jobs failed + if [ ${#failed_jobs[@]} -gt 0 ]; then + error "Some LDA diamond log updates failed: ${failed_jobs[*]}" + echo "All LDA diamond log updates completed with ${#failed_jobs[@]} failure(s) out of $job_count total jobs" + playNotificationSound + return 1 + else + echo "All LDA diamond log updates completed successfully ($job_count jobs)" + playNotificationSound + return 0 fi - - checkFailure $? "update LDA diamond logs" echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< updateLDADiamondLogs completed" diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh index 20a2d48a2..f8ed2dbfd 100644 --- a/script/tasks/ldaDiamondUpdateFacet.sh +++ b/script/tasks/ldaDiamondUpdateFacet.sh @@ -74,8 +74,8 @@ function ldaDiamondUpdateFacet() { return 1 fi - # get LDA diamond address from LDA deployment file - local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.${FILE_SUFFIX}json" + # get LDA diamond address from LDA diamond deployment file + local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$LDA_DEPLOYMENT_FILE") # if no diamond address was found, throw an error and exit this script From cd1c0954b8773dc1f1c354b675aff53e0ca46eff Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 18:01:22 +0200 Subject: [PATCH 163/220] moved all lda deploy as a single for LiFiDEXAggregatorDiamond, add use case for deploying single facet for LDA --- deployments/arbitrum.lda.staging.json | 13 +++ deployments/arbitrum.staging.json | 4 +- script/deploy/deployAllLDAContracts.sh | 46 +++++++++- script/deploy/deploySingleContract.sh | 25 +++++ script/scriptMaster.sh | 122 ++++++++++++++++--------- 5 files changed, 165 insertions(+), 45 deletions(-) create mode 100644 deployments/arbitrum.lda.staging.json diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json new file mode 100644 index 000000000..d11a3f6e1 --- /dev/null +++ b/deployments/arbitrum.lda.staging.json @@ -0,0 +1,13 @@ +{ + "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", + "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41" +} \ No newline at end of file diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 7757edbf0..37216a6e1 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -63,11 +63,11 @@ "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41" } \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 0f46f5745..641d9c82a 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -246,9 +246,51 @@ deployAllLDAContracts() { # get current LDA diamond contract version local VERSION=$(getCurrentContractVersion "$LDA_DIAMOND_CONTRACT_NAME") - # deploy LDA diamond + # deploy LDA diamond directly (avoid infinite loop with deploySingleContract special case) echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying $LDA_DIAMOND_CONTRACT_NAME now" - deploySingleContract "$LDA_DIAMOND_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" "true" "true" + + # Call the deploy script directly instead of going through deploySingleContract + # to avoid the infinite loop caused by the special case detection + local DEPLOY_SCRIPT_PATH="script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol" + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # For LDA contracts, modify FILE_SUFFIX to include "lda." + if [[ "$ENVIRONMENT" == "production" ]]; then + FILE_SUFFIX="lda." + else + FILE_SUFFIX="lda.staging." + fi + + # Get required deployment variables + local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") + local CREATE3_FACTORY_ADDRESS=$(getCreate3FactoryAddress "$NETWORK") + local SALT_INPUT="$BYTECODE""$SALT" + local DEPLOYSALT=$(cast keccak "$SALT_INPUT") + local CONTRACT_ADDRESS=$(getContractAddressFromSalt "$DEPLOYSALT" "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$ENVIRONMENT") + + # Deploy the LDA diamond using forge script directly with all required environment variables + local RAW_RETURN_DATA=$(DEPLOYSALT=$DEPLOYSALT CREATE3_FACTORY_ADDRESS=$CREATE3_FACTORY_ADDRESS NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT=$DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS=$DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") DIAMOND_TYPE=$DIAMOND_TYPE forge script "$DEPLOY_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "${GAS_ESTIMATE_MULTIPLIER:-130}") + + # Extract deployed address + local ADDRESS=$(extractDeployedAddressFromRawReturnData "$RAW_RETURN_DATA" "$NETWORK") + if [[ -z "$ADDRESS" || "$ADDRESS" == "null" ]]; then + error "failed to deploy $LDA_DIAMOND_CONTRACT_NAME - could not extract address" + return 1 + fi + + echo "[info] $LDA_DIAMOND_CONTRACT_NAME deployed to $NETWORK at address $ADDRESS" + + # Save contract in network-specific deployment files + saveContract "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$ADDRESS" "$FILE_SUFFIX" + + # Also save to the regular network deployment file for complete network tracking + local REGULAR_FILE_SUFFIX + if [[ "$ENVIRONMENT" == "production" ]]; then + REGULAR_FILE_SUFFIX="" + else + REGULAR_FILE_SUFFIX="staging." + fi + saveContract "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$ADDRESS" "$REGULAR_FILE_SUFFIX" # check if last command was executed successfully, otherwise exit script with error message checkFailure $? "deploy contract $LDA_DIAMOND_CONTRACT_NAME to network $NETWORK" diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index 46374501e..fbe8a86e3 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -8,6 +8,7 @@ deploySingleContract() { source script/config.sh source script/helperFunctions.sh source script/deploy/resources/contractSpecificReminders.sh + source script/deploy/deployAllLDAContracts.sh # read function arguments into variables local CONTRACT="$1" @@ -111,6 +112,30 @@ deploySingleContract() { echo -e "\n\n" fi + # Special case: LiFiDEXAggregatorDiamond is treated as a single external contract + # that triggers the full LDA deployment process + if [[ "$CONTRACT" == "LiFiDEXAggregatorDiamond" ]]; then + echo "[info] LiFiDEXAggregatorDiamond detected - deploying full LDA diamond with all contracts" + echo "[info] Calling deployAllLDAContracts script..." + + # Call the full LDA deployment script + deployAllLDAContracts "$NETWORK" "$ENVIRONMENT" + + # Check if deployment was successful + local LDA_DEPLOYMENT_RESULT=$? + if [[ $LDA_DEPLOYMENT_RESULT -eq 0 ]]; then + echo "[info] LiFiDEXAggregatorDiamond (full LDA deployment) completed successfully" + return 0 + else + error "LiFiDEXAggregatorDiamond (full LDA deployment) failed" + if [[ -z "$EXIT_ON_ERROR" || "$EXIT_ON_ERROR" == "false" ]]; then + return 1 + else + exit 1 + fi + fi + fi + # check if deploy script exists if ! checkIfFileExists "$FULL_SCRIPT_PATH" >/dev/null; then error "could not find deploy script for $CONTRACT in this path: $FULL_SCRIPT_PATH". Aborting deployment. diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index a806e7c93..b22c5e15c 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -42,6 +42,7 @@ scriptMaster() { source script/deploy/deployAllLDAContracts.sh source script/helperFunctions.sh source script/deploy/deployFacetAndAddToDiamond.sh + source script/deploy/deployFacetAndAddToLDADiamond.sh source script/deploy/deployPeripheryContracts.sh source script/deploy/deployUpgradesToSAFE.sh for script in script/tasks/*.sh; do [ -f "$script" ] && source "$script"; done # sources all script in folder script/tasks/ @@ -123,7 +124,7 @@ scriptMaster() { "11) Update diamond log(s)" \ "12) Propose upgrade TX to Gnosis SAFE" \ "13) Remove facets or periphery from diamond" \ - "14) Deploy all LiFi DEX Aggregator diamond with contracts to one selected network" \ + "14) Deploy LDA facet to one selected network" \ ) #--------------------------------------------------------------------------------------------------------------------- @@ -157,49 +158,46 @@ scriptMaster() { # get user-selected deploy script and contract from list SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") else - # Ask user to choose between regular and LDA contracts - echo "Which type of contract do you want to deploy?" - CONTRACT_TYPE=$( - gum choose \ - "Regular LiFi contracts" \ - "LDA (LiFi DEX Aggregator) contracts" - ) - - # Set script directory based on contract type - if [[ "$CONTRACT_TYPE" == "LDA"* ]]; then - DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" - else - DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" - fi + # Combine regular LiFi contracts and LDA contracts in the selection + REGULAR_SCRIPTS=$(ls -1 "script/deploy/facets/" | sed -e 's/\.s.sol$//' | grep 'Deploy') + LDA_SCRIPTS=$(ls -1 "script/deploy/facets/LDA/" | sed -e 's/\.s.sol$//' | grep 'Deploy') - # get user-selected deploy script and contract from list - SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") + # Combine both lists and let user select + ALL_SCRIPTS=$(echo -e "$REGULAR_SCRIPTS\n$LDA_SCRIPTS") + SCRIPT=$(echo "$ALL_SCRIPTS" | gum filter --placeholder "Deploy Script") fi # get user-selected deploy script and contract from list CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') + + # Determine if this is an LDA contract and set the appropriate directory + if echo "$LDA_SCRIPTS" | grep -q "^$SCRIPT$"; then + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" + IS_LDA_CONTRACT="true" + else + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" + IS_LDA_CONTRACT="false" + fi # check if new contract should be added to diamond after deployment - if [[ "$CONTRACT_TYPE" == "LDA"* ]]; then - # LDA contracts - if [[ ! "$CONTRACT" == "LiFiDEXAggregatorDiamond"* ]]; then - echo "" - echo "Do you want to add this LDA contract to a diamond after deployment?" + # Skip diamond addition question for LiFiDEXAggregatorDiamond (has custom flow) and LiFiDiamond contracts + if [[ ! "$CONTRACT" == "LiFiDiamond"* && ! "$CONTRACT" == "LiFiDEXAggregatorDiamond" ]]; then + echo "" + echo "Do you want to add this contract to a diamond after deployment?" + if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + # For LDA contracts, offer LDA diamond option ADD_TO_DIAMOND=$( gum choose \ "yes - to LiFiDEXAggregatorDiamond" \ " no - do not update any diamond" ) - fi - else - # Regular LiFi contracts - if [[ ! "$CONTRACT" == "LiFiDiamond"* ]]; then - echo "" - echo "Do you want to add this contract to a diamond after deployment?" + else + # For regular contracts, offer regular diamond options ADD_TO_DIAMOND=$( gum choose \ "yes - to LiFiDiamond" \ "yes - to LiFiDiamondImmutable" \ + "yes - to LiFiDEXAggregatorDiamond" \ " no - do not update any diamond" ) fi @@ -221,12 +219,8 @@ scriptMaster() { deployAndAddContractToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamond" "$VERSION" fi else - # just deploy the contract (determine if LDA based on CONTRACT_TYPE) - if [[ "$CONTRACT_TYPE" == "LDA"* ]]; then - deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "true" - else - deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "false" - fi + # just deploy the contract (determine if LDA based on IS_LDA_CONTRACT) + deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "$IS_LDA_CONTRACT" fi # check if last command was executed successfully, otherwise exit script with error message @@ -593,23 +587,69 @@ scriptMaster() { bunx tsx script/tasks/cleanUpProdDiamond.ts #--------------------------------------------------------------------------------------------------------------------- - # use case 15: Deploy all LiFi DEX Aggregator diamond with contracts to one selected network + # use case 14: Deploy LDA facet to one selected network elif [[ "$SELECTION" == "14)"* ]]; then echo "" - echo "[info] selected use case: Deploy all LiFi DEX Aggregator diamond with contracts to one selected network" + echo "[info] selected use case: Deploy LDA facet to one selected network" checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" # get user-selected network from list local NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") + echo "[info] selected network: $NETWORK" + echo "[info] loading deployer wallet balance..." - # call deploy script - deployAllLDAContracts "$NETWORK" "$ENVIRONMENT" + # get deployer wallet balance + BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") - # check if last command was executed successfully, otherwise exit script with error message - checkFailure $? "deploy all LDA contracts to network $NETWORK" + echo "[info] deployer wallet balance in this network: $BALANCE" + echo "" + checkRequiredVariablesInDotEnv "$NETWORK" - playNotificationSound + # Handle ZkSync + if isZkEvmNetwork "$NETWORK"; then + # Use zksync specific LDA scripts + DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/LDA/" + # Check if the foundry-zksync binaries exist, if not fetch them + install_foundry_zksync + # get user-selected deploy script and contract from list + SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") + else + # Use regular LDA scripts + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" + # get user-selected deploy script and contract from list + SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") + fi + + # get user-selected deploy script and contract from list + CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') + + # check if new LDA facet should be added to LDA diamond after deployment + if [[ ! "$CONTRACT" == "LiFiDEXAggregatorDiamond"* ]]; then + echo "" + echo "Do you want to add this LDA facet to the LDA diamond after deployment?" + ADD_TO_DIAMOND=$( + gum choose \ + "yes - to LiFiDEXAggregatorDiamond" \ + " no - do not update any diamond" + ) + fi + + # get current contract version + local VERSION=$(getCurrentContractVersion "$CONTRACT") + + # check if contract should be added after deployment + if [[ "$ADD_TO_DIAMOND" == "yes"* ]]; then + echo "[info] selected option: $ADD_TO_DIAMOND" + # deploy LDA facet and add to LDA diamond + deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDEXAggregatorDiamond" "$VERSION" + else + # just deploy the LDA contract + deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "true" + fi + + # check if last command was executed successfully, otherwise exit script with error message + checkFailure $? "deploy LDA contract $CONTRACT to network $NETWORK" #--------------------------------------------------------------------------------------------------------------------- else From 18f59f829b33f91651adddc1c7921f2b981dac12 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 18:50:53 +0200 Subject: [PATCH 164/220] removed deployFacetAndAddToDLADiamond and updated deployFacetAndAddToDiamond --- deployments/arbitrum.lda.staging.json | 13 -- deployments/optimism.lda.staging.json | 13 -- script/deploy/deployAllLDAContracts.sh | 18 +-- script/deploy/deployFacetAndAddToDiamond.sh | 51 +++++-- .../deploy/deployFacetAndAddToLDADiamond.sh | 124 ------------------ script/deploy/deploySingleContract.sh | 24 ---- script/scriptMaster.sh | 13 +- 7 files changed, 46 insertions(+), 210 deletions(-) delete mode 100644 deployments/arbitrum.lda.staging.json delete mode 100644 deployments/optimism.lda.staging.json delete mode 100644 script/deploy/deployFacetAndAddToLDADiamond.sh diff --git a/deployments/arbitrum.lda.staging.json b/deployments/arbitrum.lda.staging.json deleted file mode 100644 index d11a3f6e1..000000000 --- a/deployments/arbitrum.lda.staging.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", - "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", - "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", - "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", - "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", - "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", - "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41" -} \ No newline at end of file diff --git a/deployments/optimism.lda.staging.json b/deployments/optimism.lda.staging.json deleted file mode 100644 index 07b73728f..000000000 --- a/deployments/optimism.lda.staging.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", - "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", - "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", - "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", - "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", - "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", - "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" -} \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 641d9c82a..7df58dbe7 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -254,13 +254,6 @@ deployAllLDAContracts() { local DEPLOY_SCRIPT_PATH="script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol" local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - # For LDA contracts, modify FILE_SUFFIX to include "lda." - if [[ "$ENVIRONMENT" == "production" ]]; then - FILE_SUFFIX="lda." - else - FILE_SUFFIX="lda.staging." - fi - # Get required deployment variables local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") local CREATE3_FACTORY_ADDRESS=$(getCreate3FactoryAddress "$NETWORK") @@ -280,17 +273,8 @@ deployAllLDAContracts() { echo "[info] $LDA_DIAMOND_CONTRACT_NAME deployed to $NETWORK at address $ADDRESS" - # Save contract in network-specific deployment files + # Save contract in regular network deployment file only saveContract "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$ADDRESS" "$FILE_SUFFIX" - - # Also save to the regular network deployment file for complete network tracking - local REGULAR_FILE_SUFFIX - if [[ "$ENVIRONMENT" == "production" ]]; then - REGULAR_FILE_SUFFIX="" - else - REGULAR_FILE_SUFFIX="staging." - fi - saveContract "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$ADDRESS" "$REGULAR_FILE_SUFFIX" # check if last command was executed successfully, otherwise exit script with error message checkFailure $? "deploy contract $LDA_DIAMOND_CONTRACT_NAME to network $NETWORK" diff --git a/script/deploy/deployFacetAndAddToDiamond.sh b/script/deploy/deployFacetAndAddToDiamond.sh index b46a4025a..2b3a5e940 100755 --- a/script/deploy/deployFacetAndAddToDiamond.sh +++ b/script/deploy/deployFacetAndAddToDiamond.sh @@ -10,6 +10,7 @@ function deployFacetAndAddToDiamond() { source script/helperFunctions.sh source script/deploy/deploySingleContract.sh source script/tasks/diamondUpdatePeriphery.sh + source script/tasks/ldaDiamondUpdateFacet.sh # read function arguments into variables @@ -73,12 +74,23 @@ function deployFacetAndAddToDiamond() { echo "[info] selected diamond type: $DIAMOND_CONTRACT_NAME" fi - # get diamond address from deployments script - local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "./deployments/${NETWORK}.${FILE_SUFFIX}json") + # get diamond address from deployments script (handle both regular and LDA diamonds) + local DIAMOND_ADDRESS + local DEPLOYMENT_FILE + + if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then + # For LDA diamond, look in regular deployment file (not .lda.diamond.json) + DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" + DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$DEPLOYMENT_FILE") + else + # For regular diamonds, use regular deployment file + DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" + DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$DEPLOYMENT_FILE") + fi # if no diamond address was found, throw an error and exit this script if [[ "$DIAMOND_ADDRESS" == "null" ]]; then - error "could not find address for $DIAMOND_CONTRACT_NAME on network $NETWORK in file './deployments/${NETWORK}.${FILE_SUFFIX}json' - exiting script now" + error "could not find address for $DIAMOND_CONTRACT_NAME on network $NETWORK in file '$DEPLOYMENT_FILE' - exiting script now" return 1 fi @@ -99,8 +111,13 @@ function deployFacetAndAddToDiamond() { echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying $FACET_CONTRACT_NAME for $DIAMOND_CONTRACT_NAME now...." - # deploy facet - deploySingleContract "$FACET_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" false "$DIAMOND_CONTRACT_NAME" + # deploy facet (determine if LDA based on diamond type) + local IS_LDA_CONTRACT="false" + if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then + IS_LDA_CONTRACT="true" + fi + + deploySingleContract "$FACET_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" false "$IS_LDA_CONTRACT" # check if function call was successful if [ $? -ne 0 ] @@ -112,13 +129,23 @@ function deployFacetAndAddToDiamond() { # prepare update script name local UPDATE_SCRIPT="Update$FACET_CONTRACT_NAME" - # update diamond - diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" true - - if [ $? -ne 0 ] - then - warning "this call was not successful: diamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" - return 1 + # update diamond (use appropriate function based on diamond type) + if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then + # Use LDA-specific update function + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" true + + if [ $? -ne 0 ]; then + warning "this call was not successful: ldaDiamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" + return 1 + fi + else + # Use regular diamond update function + diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" true + + if [ $? -ne 0 ]; then + warning "this call was not successful: diamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" + return 1 + fi fi echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $FACET_CONTRACT_NAME successfully deployed and added to $DIAMOND_CONTRACT_NAME" diff --git a/script/deploy/deployFacetAndAddToLDADiamond.sh b/script/deploy/deployFacetAndAddToLDADiamond.sh deleted file mode 100644 index da32e2ca6..000000000 --- a/script/deploy/deployFacetAndAddToLDADiamond.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/bin/bash - -# deploys an LDA facet contract and adds it to LiFiDEXAggregatorDiamond contract -function deployFacetAndAddToLDADiamond() { - # load env variables - source .env - - # load config & helper functions - source script/config.sh - source script/helperFunctions.sh - source script/deploy/deploySingleContract.sh - source script/tasks/ldaDiamondUpdateFacet.sh - - # read function arguments into variables - local NETWORK="$1" - local ENVIRONMENT="$2" - local FACET_CONTRACT_NAME="$3" - local DIAMOND_CONTRACT_NAME="$4" - local VERSION="$5" - - # if no NETWORK was passed to this function, ask user to select it - if [[ -z "$NETWORK" ]]; then - checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" - NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") - checkRequiredVariablesInDotEnv $NETWORK - fi - - # if no ENVIRONMENT was passed to this function, determine it - if [[ -z "$ENVIRONMENT" ]]; then - if [[ "$PRODUCTION" == "true" ]]; then - # make sure that PRODUCTION was selected intentionally by user - echo " " - echo " " - printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!! ATTENTION !!!!!!!!!!!!!!!!!!!!!!!!"; - printf '\033[33m%s\033[0m\n' "The config environment variable PRODUCTION is set to true"; - printf '\033[33m%s\033[0m\n' "This means you will be deploying LDA contracts to production"; - printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; - echo " " - printf '\033[33m%s\033[0m\n' "Last chance: Do you want to skip?"; - PROD_SELECTION=$(gum choose \ - "yes" \ - "no" \ - ) - - if [[ $PROD_SELECTION != "no" ]]; then - echo "...exiting script" - exit 0 - fi - - ENVIRONMENT="production" - else - ENVIRONMENT="staging" - fi - fi - - # get file suffix based on value in variable ENVIRONMENT - FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - - # if no FACET_CONTRACT_NAME was passed to this function, ask user to select it - if [[ -z "$FACET_CONTRACT_NAME" ]]; then - echo "" - echo "Please select which LDA facet you would like to deploy" - local SCRIPT=$(ls -1 script/deploy/facets/LDA/ | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy LDA Script") - FACET_CONTRACT_NAME=$(echo $SCRIPT | sed -e 's/Deploy//') - fi - - # if no DIAMOND_CONTRACT_NAME was passed to this function, default to LDADiamond - if [[ -z "$DIAMOND_CONTRACT_NAME" ]]; then - DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" - fi - - # get LDA diamond address from deployments script (using .lda.diamond. file suffix) - local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" - local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$LDA_DEPLOYMENT_FILE") - - # if no diamond address was found, throw an error and exit this script - if [[ "$DIAMOND_ADDRESS" == "null" ]]; then - error "could not find address for $DIAMOND_CONTRACT_NAME on network $NETWORK in file '$LDA_DEPLOYMENT_FILE' - exiting script now" - return 1 - fi - - # if no VERSION was passed to this function, we assume that the latest version should be deployed - if [[ -z "$VERSION" ]]; then - VERSION=$(getCurrentContractVersion "$FACET_CONTRACT_NAME") - fi - - # logging for debug purposes - echo "" - echoDebug "in function deployFacetAndAddToLDADiamond" - echoDebug "NETWORK=$NETWORK" - echoDebug "FILE_SUFFIX=$FILE_SUFFIX" - echoDebug "FACET_CONTRACT_NAME=$FACET_CONTRACT_NAME" - echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" - echoDebug "ENVIRONMENT=$ENVIRONMENT" - echoDebug "VERSION=$VERSION" - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying LDA $FACET_CONTRACT_NAME for $DIAMOND_CONTRACT_NAME now...." - - # deploy LDA facet (using LDA-specific deploy script directory) - deploySingleContract "$FACET_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" false "$DIAMOND_CONTRACT_NAME" "LDA" - - # check if function call was successful - if [ $? -ne 0 ] - then - warning "this call was not successful: deploySingleContract $FACET_CONTRACT_NAME $NETWORK $ENVIRONMENT $VERSION false $DIAMOND_CONTRACT_NAME LDA" - return 1 - fi - - # prepare LDA update script name - local UPDATE_SCRIPT="Update$FACET_CONTRACT_NAME" - - # update LDA diamond - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" true - - if [ $? -ne 0 ] - then - warning "this call was not successful: ldaDiamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" - return 1 - fi - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA $FACET_CONTRACT_NAME successfully deployed and added to $DIAMOND_CONTRACT_NAME" - return 0 -} - diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index fbe8a86e3..bbd0bd270 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -151,15 +151,6 @@ deploySingleContract() { # get file suffix based on value in variable ENVIRONMENT FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - - # For LDA contracts, modify FILE_SUFFIX to include "lda." - if [[ "$IS_LDA_CONTRACT" == "true" ]]; then - if [[ "$ENVIRONMENT" == "production" ]]; then - FILE_SUFFIX="lda." - else - FILE_SUFFIX="lda.staging." - fi - fi if [[ -z "$GAS_ESTIMATE_MULTIPLIER" ]]; then GAS_ESTIMATE_MULTIPLIER=130 # this is foundry's default value @@ -413,21 +404,6 @@ deploySingleContract() { # save contract in network-specific deployment files saveContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$FILE_SUFFIX" - - # For LDA contracts, also save to the regular network deployment file - # This ensures all contracts deployed to a network are tracked in the main deployment log - if [[ "$IS_LDA_CONTRACT" == "true" ]]; then - # Get the regular file suffix (without "lda." prefix) - local REGULAR_FILE_SUFFIX - if [[ "$ENVIRONMENT" == "production" ]]; then - REGULAR_FILE_SUFFIX="" - else - REGULAR_FILE_SUFFIX="staging." - fi - - echo "[info] Also saving LDA contract $CONTRACT to regular deployment file for complete network tracking" - saveContract "$NETWORK" "$CONTRACT" "$ADDRESS" "$REGULAR_FILE_SUFFIX" - fi return 0 } diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index b22c5e15c..ceeecaa50 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -42,7 +42,6 @@ scriptMaster() { source script/deploy/deployAllLDAContracts.sh source script/helperFunctions.sh source script/deploy/deployFacetAndAddToDiamond.sh - source script/deploy/deployFacetAndAddToLDADiamond.sh source script/deploy/deployPeripheryContracts.sh source script/deploy/deployUpgradesToSAFE.sh for script in script/tasks/*.sh; do [ -f "$script" ] && source "$script"; done # sources all script in folder script/tasks/ @@ -210,13 +209,13 @@ scriptMaster() { if [[ "$ADD_TO_DIAMOND" == "yes"* ]]; then echo "[info] selected option: $ADD_TO_DIAMOND" - # determine the diamond type and call appropriate function + # determine the diamond type and call unified function if [[ "$ADD_TO_DIAMOND" == *"LiFiDEXAggregatorDiamond"* ]]; then - deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDEXAggregatorDiamond" "$VERSION" + deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDEXAggregatorDiamond" "$VERSION" elif [[ "$ADD_TO_DIAMOND" == *"LiFiDiamondImmutable"* ]]; then - deployAndAddContractToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamondImmutable" "$VERSION" + deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamondImmutable" "$VERSION" else - deployAndAddContractToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamond" "$VERSION" + deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamond" "$VERSION" fi else # just deploy the contract (determine if LDA based on IS_LDA_CONTRACT) @@ -641,8 +640,8 @@ scriptMaster() { # check if contract should be added after deployment if [[ "$ADD_TO_DIAMOND" == "yes"* ]]; then echo "[info] selected option: $ADD_TO_DIAMOND" - # deploy LDA facet and add to LDA diamond - deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDEXAggregatorDiamond" "$VERSION" + # deploy LDA facet and add to LDA diamond using unified function + deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDEXAggregatorDiamond" "$VERSION" else # just deploy the LDA contract deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "true" From 5e6d2801cac06c924fe11957c76acb3a68801e5b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 19:00:54 +0200 Subject: [PATCH 165/220] removed IS_LDA_CONTRACT flag --- script/deploy/deployFacetAndAddToDiamond.sh | 9 ++------- script/deploy/deploySingleContract.sh | 18 +++++++++++++++--- script/scriptMaster.sh | 11 +++++------ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/script/deploy/deployFacetAndAddToDiamond.sh b/script/deploy/deployFacetAndAddToDiamond.sh index 2b3a5e940..81222e43f 100755 --- a/script/deploy/deployFacetAndAddToDiamond.sh +++ b/script/deploy/deployFacetAndAddToDiamond.sh @@ -111,13 +111,8 @@ function deployFacetAndAddToDiamond() { echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying $FACET_CONTRACT_NAME for $DIAMOND_CONTRACT_NAME now...." - # deploy facet (determine if LDA based on diamond type) - local IS_LDA_CONTRACT="false" - if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then - IS_LDA_CONTRACT="true" - fi - - deploySingleContract "$FACET_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" false "$IS_LDA_CONTRACT" + # deploy facet (deploySingleContract will auto-detect if it's LDA based on contract name) + deploySingleContract "$FACET_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" false # check if function call was successful if [ $? -ne 0 ] diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index bbd0bd270..e55fc3ad6 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -16,7 +16,6 @@ deploySingleContract() { local ENVIRONMENT="$3" local VERSION="$4" local EXIT_ON_ERROR="$5" - local IS_LDA_CONTRACT="$6" # load env variables source .env @@ -68,6 +67,19 @@ deploySingleContract() { FILE_EXTENSION=".s.sol" + # Helper function to check if contract is LDA-related + isLDAContract() { + local contract_name="$1" + # Check if contract name contains LDA-related patterns or is in LDA facets list + if [[ "$contract_name" == "LiFiDEXAggregatorDiamond" ]] || + [[ "$contract_name" == *"LDA"* ]] || + [[ -f "script/deploy/facets/LDA/Deploy${contract_name}.s.sol" ]]; then + return 0 # true + else + return 1 # false + fi + } + # Determine deployment script directory based on network type and contract type # We need to support 4 combinations: # 1. Regular + Non-zkEVM = script/deploy/facets/ @@ -76,14 +88,14 @@ deploySingleContract() { # 4. LDA + zkEVM = script/deploy/zksync/LDA/ if isZkEvmNetwork "$NETWORK"; then - if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + if isLDAContract "$CONTRACT"; then DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/LDA/" else DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/" fi FILE_EXTENSION=".zksync.s.sol" else - if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + if isLDAContract "$CONTRACT"; then DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" else DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index ceeecaa50..4e6f8b797 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -169,13 +169,11 @@ scriptMaster() { # get user-selected deploy script and contract from list CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') - # Determine if this is an LDA contract and set the appropriate directory + # Set appropriate directory (deploySingleContract will auto-detect LDA based on contract name) if echo "$LDA_SCRIPTS" | grep -q "^$SCRIPT$"; then DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" - IS_LDA_CONTRACT="true" else DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" - IS_LDA_CONTRACT="false" fi # check if new contract should be added to diamond after deployment @@ -183,7 +181,8 @@ scriptMaster() { if [[ ! "$CONTRACT" == "LiFiDiamond"* && ! "$CONTRACT" == "LiFiDEXAggregatorDiamond" ]]; then echo "" echo "Do you want to add this contract to a diamond after deployment?" - if [[ "$IS_LDA_CONTRACT" == "true" ]]; then + # Check if this is an LDA contract based on script directory + if echo "$LDA_SCRIPTS" | grep -q "^Deploy${CONTRACT}$"; then # For LDA contracts, offer LDA diamond option ADD_TO_DIAMOND=$( gum choose \ @@ -218,8 +217,8 @@ scriptMaster() { deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamond" "$VERSION" fi else - # just deploy the contract (determine if LDA based on IS_LDA_CONTRACT) - deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false "$IS_LDA_CONTRACT" + # just deploy the contract (deploySingleContract will auto-detect LDA based on contract name) + deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "" false fi # check if last command was executed successfully, otherwise exit script with error message From 0a91cb34276063afcbadb7134dabbfac63cffb3c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 19:28:12 +0200 Subject: [PATCH 166/220] improved script, removed create3factory deployment, removed staging, transfer ownership to multisig --- script/deploy/deployAllLDAContracts.sh | 556 +++++++++---------------- 1 file changed, 186 insertions(+), 370 deletions(-) diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 7df58dbe7..0c690c401 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -1,24 +1,52 @@ #!/bin/bash -# Function to check if all LDA core facets are deployed -checkLDACoreFacetsExist() { +# Simple LDA deployment script - deploys LDA diamond and all facets in one go +deployAllLDAContracts() { + echo "[info] =====================================================================" + echo "[info] Starting LiFi DEX Aggregator (LDA) Diamond deployment" + echo "[info] =====================================================================" + + # load required resources + source script/config.sh + source script/helperFunctions.sh + source script/deploy/deployFacetAndAddToDiamond.sh + source script/tasks/ldaDiamondUpdateFacet.sh + + # read function arguments into variables local NETWORK="$1" local ENVIRONMENT="$2" - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start checkLDACoreFacetsExist" - + + # load env variables + source .env + # get file suffix based on value in variable ENVIRONMENT local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + + # logging for debug purposes + echo "" + echoDebug "in function deployAllLDAContracts" + echoDebug "NETWORK=$NETWORK" + echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echo "" + + # LDA Diamond contract name + local LDA_DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" + + # ========================================================================= + # STEP 1: Check prerequisites - ensure core facets are deployed + # ========================================================================= + echo "" + echo "[info] STEP 1: Checking LDA core facets availability..." # Check if regular network deployment file exists (where core facets should be) local REGULAR_DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" if [[ ! -f "$REGULAR_DEPLOYMENT_FILE" ]]; then echo "" - echo "[ERROR] ❌ LiFiDEXAggregator deployment failed!" + echo "[ERROR] ❌ LiFi DEX Aggregator (LDA) Diamond deployment failed!" echo "[ERROR] Regular network deployment file not found: $REGULAR_DEPLOYMENT_FILE" - echo "[ERROR] LDA core facets are deployed as part of the regular LiFi Diamond deployment." + echo "[ERROR] LDA requires core facets from the regular LiFi Diamond deployment." echo "[ERROR] Please deploy the regular LiFi Diamond first using option 3 in the script master menu." - echo "[ERROR] This will deploy the core facets that LDA Diamond requires." echo "" return 1 fi @@ -37,416 +65,204 @@ checkLDACoreFacetsExist() { LDA_CORE_FACETS+=("$facet") done <<< "$LDA_CORE_FACETS_JSON" - echo "[info] Found ${#LDA_CORE_FACETS[@]} LDA core facets in config: ${LDA_CORE_FACETS[*]}" - echo "[info] Checking for core facets in regular deployment file: $REGULAR_DEPLOYMENT_FILE" + echo "[info] Checking for ${#LDA_CORE_FACETS[@]} LDA core facets: ${LDA_CORE_FACETS[*]}" local MISSING_FACETS=() - # Check each LDA core facet exists in regular deployment logs (not LDA-specific logs) + # Check each LDA core facet exists in regular deployment logs for FACET_NAME in "${LDA_CORE_FACETS[@]}"; do - echo "[info] Checking if LDA core facet exists: $FACET_NAME" - - # Check if facet address exists in regular deployment logs (shared with regular LiFi Diamond) local FACET_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_NAME") if [[ -z "$FACET_ADDRESS" ]]; then - echo "[error] LDA core facet $FACET_NAME not found in regular deployment logs for network $NETWORK" + echo "[error] ❌ LDA core facet $FACET_NAME not found" MISSING_FACETS+=("$FACET_NAME") else - echo "[info] ✅ LDA core facet $FACET_NAME found at address: $FACET_ADDRESS" + echo "[info] ✅ LDA core facet $FACET_NAME found at: $FACET_ADDRESS" fi done # If any facets are missing, fail the deployment if [[ ${#MISSING_FACETS[@]} -gt 0 ]]; then echo "" - echo "[ERROR] ❌ LiFiDEXAggregator deployment failed!" - echo "[ERROR] The following LDA core facets are missing from network $NETWORK regular deployment logs:" - for missing_facet in "${MISSING_FACETS[@]}"; do - echo "[ERROR] - $missing_facet" - done - echo "" - echo "[ERROR] LDA core facets are deployed as part of the regular LiFi Diamond deployment." - echo "[ERROR] Please deploy the regular LiFi Diamond first using option 3 in the script master menu." - echo "[ERROR] This will deploy the core facets (DiamondCutFacet, DiamondLoupeFacet, OwnershipFacet) that LDA Diamond requires." + echo "[ERROR] ❌ LDA deployment failed!" + echo "[ERROR] Missing core facets: ${MISSING_FACETS[*]}" + echo "[ERROR] Please deploy the regular LiFi Diamond first." echo "" return 1 fi - echo "[info] ✅ All LDA core facets are available in regular deployment logs" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< checkLDACoreFacetsExist completed" - - return 0 -} + echo "[info] ✅ All LDA core facets are available" -# Function to get LDA facet contract names from the LDA Facets directory -getLDAFacetContractNames() { - local LDA_FACETS_PATH="src/Periphery/LDA/Facets/" + # ========================================================================= + # STEP 2: Deploy LDA Diamond + # ========================================================================= + echo "" + echo "[info] STEP 2: Deploying LDA Diamond..." + + # get current LDA diamond contract version + local VERSION=$(getCurrentContractVersion "$LDA_DIAMOND_CONTRACT_NAME") - # Check if the LDA Facets directory exists - if [ ! -d "$LDA_FACETS_PATH" ]; then - error "LDA Facets directory not found: $LDA_FACETS_PATH" + # Call the deploy script directly to avoid infinite loop + local DEPLOY_SCRIPT_PATH="script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol" + + # Get required deployment variables + local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") + local CREATE3_FACTORY_ADDRESS=$(getCreate3FactoryAddress "$NETWORK") + local SALT_INPUT="$BYTECODE""$SALT" + local DEPLOYSALT=$(cast keccak "$SALT_INPUT") + + # Deploy the LDA diamond using forge script directly + echo "[info] Deploying $LDA_DIAMOND_CONTRACT_NAME..." + local RAW_RETURN_DATA=$(DEPLOYSALT=$DEPLOYSALT CREATE3_FACTORY_ADDRESS=$CREATE3_FACTORY_ADDRESS NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT=$DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS=$DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") DIAMOND_TYPE=$DIAMOND_TYPE forge script "$DEPLOY_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "${GAS_ESTIMATE_MULTIPLIER:-130}") + + # Extract deployed address + local LDA_DIAMOND_ADDRESS=$(extractDeployedAddressFromRawReturnData "$RAW_RETURN_DATA" "$NETWORK") + if [[ -z "$LDA_DIAMOND_ADDRESS" || "$LDA_DIAMOND_ADDRESS" == "null" ]]; then + error "❌ Failed to deploy $LDA_DIAMOND_CONTRACT_NAME - could not extract address" return 1 fi - # Get contract names using the existing helper function - local LDA_FACETS=$(getContractNamesInFolder "$LDA_FACETS_PATH") - echo "$LDA_FACETS" -} - -deployAllLDAContracts() { - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployAllLDAContracts" - - # load required resources - source script/config.sh - source script/helperFunctions.sh - source script/deploy/deployAndStoreCREATE3Factory.sh - source script/deploy/deployFacetAndAddToLDADiamond.sh - source script/tasks/ldaDiamondUpdateFacet.sh - - # read function arguments into variables - local NETWORK="$1" - local ENVIRONMENT="$2" - - # load env variables - source .env - - # get file suffix based on value in variable ENVIRONMENT - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + echo "[info] ✅ $LDA_DIAMOND_CONTRACT_NAME deployed at: $LDA_DIAMOND_ADDRESS" + + # Save contract in regular network deployment file + saveContract "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$LDA_DIAMOND_ADDRESS" "$FILE_SUFFIX" - # logging for debug purposes - echo "" - echoDebug "in function deployAllLDAContracts" - echoDebug "NETWORK=$NETWORK" - echoDebug "ENVIRONMENT=$ENVIRONMENT" - echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + # ========================================================================= + # STEP 3: Add core facets to LDA Diamond + # ========================================================================= echo "" - - # Ask user where to start the LDA deployment process - echo "Which LDA deployment stage would you like to start from?" - START_FROM=$( - gum choose \ - "1) Initial setup and CREATE3Factory deployment" \ - "2) Check LDA core facets availability" \ - "3) Deploy LDA diamond" \ - "4) Update LDA diamond with core facets" \ - "5) Deploy non-core LDA facets and add to diamond" \ - "6) Update LDA diamond deployment logs" \ - "7) Run LDA health check only" \ - "8) Ownership transfer to timelock (production only)" - ) - - # Extract the stage number from the selection - if [[ "$START_FROM" == *"1)"* ]]; then - START_STAGE=1 - elif [[ "$START_FROM" == *"2)"* ]]; then - START_STAGE=2 - elif [[ "$START_FROM" == *"3)"* ]]; then - START_STAGE=3 - elif [[ "$START_FROM" == *"4)"* ]]; then - START_STAGE=4 - elif [[ "$START_FROM" == *"5)"* ]]; then - START_STAGE=5 - elif [[ "$START_FROM" == *"6)"* ]]; then - START_STAGE=6 - elif [[ "$START_FROM" == *"7)"* ]]; then - START_STAGE=7 - elif [[ "$START_FROM" == *"8)"* ]]; then - START_STAGE=8 - else - error "invalid selection: $START_FROM - exiting script now" - exit 1 + echo "[info] STEP 3: Adding core facets to LDA Diamond..." + + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" false + + if [ $? -ne 0 ]; then + error "❌ Failed to add core facets to LDA Diamond" + return 1 fi + + echo "[info] ✅ Core facets added to LDA Diamond" - echo "Starting LDA deployment from stage $START_STAGE: $START_FROM" + # ========================================================================= + # STEP 4: Deploy and add LDA-specific facets + # ========================================================================= echo "" - - # LDA Diamond contract name - local LDA_DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" - - # Stage 1: Initial setup and CREATE3Factory deployment - if [[ $START_STAGE -le 1 ]]; then - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 1: Initial setup and CREATE3Factory deployment" - - # make sure that proposals are sent to diamond directly (for production deployments) - if [[ "$SEND_PROPOSALS_DIRECTLY_TO_DIAMOND" == "false" ]]; then - echo "SEND_PROPOSALS_DIRECTLY_TO_DIAMOND is set to false in your .env file" - echo "This script requires SEND_PROPOSALS_DIRECTLY_TO_DIAMOND to be true for PRODUCTION deployments" - echo "Would you like to set it to true for this execution? (y/n)" - read -r response - if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - export SEND_PROPOSALS_DIRECTLY_TO_DIAMOND=true - echo "SEND_PROPOSALS_DIRECTLY_TO_DIAMOND set to true for this execution" - else - echo "Continuing with SEND_PROPOSALS_DIRECTLY_TO_DIAMOND=false (STAGING deployment???)" - fi - fi - - # add RPC URL to MongoDB if needed - CREATE3_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.create3Factory") - if [[ -z "$CREATE3_ADDRESS" || "$CREATE3_ADDRESS" == "null" ]]; then - echo "" - echo "Adding RPC URL from networks.json to MongoDB and fetching all URLs" - bun add-network-rpc --network "$NETWORK" --rpc-url "$(getRpcUrlFromNetworksJson "$NETWORK")" - bun fetch-rpcs - # reload .env file to have the new RPC URL available - source .env - fi - - # get deployer wallet balance - BALANCE=$(getDeployerBalance "$NETWORK" "$ENVIRONMENT") - echo "Deployer Wallet Balance: $BALANCE" - if [[ -z "$BALANCE" || "$BALANCE" == "0" ]]; then - echo "Deployer wallet does not have any balance in network $NETWORK. Please fund the wallet and try again" - exit 1 - fi - - echo "[info] deployer wallet balance in this network: $BALANCE" - echo "" - checkRequiredVariablesInDotEnv "$NETWORK" - - echo "isZkEVM: $(isZkEvmNetwork "$NETWORK")" - - # deploy CREATE3Factory if needed - if isZkEvmNetwork "$NETWORK"; then - echo "zkEVM network detected, skipping CREATE3Factory deployment" - else - if [[ -z "$CREATE3_ADDRESS" || "$CREATE3_ADDRESS" == "null" ]]; then - deployAndStoreCREATE3Factory "$NETWORK" "$ENVIRONMENT" - checkFailure $? "deploy CREATE3Factory to network $NETWORK" - echo "" - else - echo "CREATE3Factory already deployed for $NETWORK (address: $CREATE3_ADDRESS), skipping CREATE3Factory deployment." - fi - fi - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 1 completed" - fi - - # Stage 2: Check LDA core facets availability (instead of deploying them) - if [[ $START_STAGE -le 2 ]]; then - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 2: Check LDA core facets availability" - - # check if LDA core facets are available in deployment logs - checkLDACoreFacetsExist "$NETWORK" "$ENVIRONMENT" + echo "[info] STEP 4: Deploying and adding LDA-specific facets..." + + # Get all LDA facet contract names from the LDA Facets directory + local LDA_FACETS_PATH="src/Periphery/LDA/Facets/" + echo "[info] Getting LDA facets from directory: $LDA_FACETS_PATH" + + # prepare regExp to exclude LDA core facets (they're already added) + local EXCLUDED_LDA_FACETS_REGEXP="^($(echo "${LDA_CORE_FACETS[@]}" | tr ' ' '|'))$" + + echo "[info] Excluding core facets: ${LDA_CORE_FACETS[*]}" + + # Deploy all non-core LDA facets and add to diamond + for FACET_NAME in $(getContractNamesInFolder "$LDA_FACETS_PATH"); do + echo "[info] Processing LDA facet: $FACET_NAME" - # check if last command was executed successfully, otherwise exit script with error message - checkFailure $? "verify LDA core facets availability for network $NETWORK" - echo "" - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 2 completed" - fi - - # Stage 3: Deploy LDA diamond - if [[ $START_STAGE -le 3 ]]; then - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 3: Deploy LDA diamond" - - # get current LDA diamond contract version - local VERSION=$(getCurrentContractVersion "$LDA_DIAMOND_CONTRACT_NAME") - - # deploy LDA diamond directly (avoid infinite loop with deploySingleContract special case) - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying $LDA_DIAMOND_CONTRACT_NAME now" + # Skip if this is a core facet (already handled) + if [[ "$FACET_NAME" =~ $EXCLUDED_LDA_FACETS_REGEXP ]]; then + echo "[info] Skipping core facet: $FACET_NAME (already added)" + continue + fi - # Call the deploy script directly instead of going through deploySingleContract - # to avoid the infinite loop caused by the special case detection - local DEPLOY_SCRIPT_PATH="script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol" - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + echo "[info] Deploying and adding LDA facet: $FACET_NAME" - # Get required deployment variables - local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") - local CREATE3_FACTORY_ADDRESS=$(getCreate3FactoryAddress "$NETWORK") - local SALT_INPUT="$BYTECODE""$SALT" - local DEPLOYSALT=$(cast keccak "$SALT_INPUT") - local CONTRACT_ADDRESS=$(getContractAddressFromSalt "$DEPLOYSALT" "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$ENVIRONMENT") + # get current contract version + local FACET_VERSION=$(getCurrentContractVersion "$FACET_NAME") - # Deploy the LDA diamond using forge script directly with all required environment variables - local RAW_RETURN_DATA=$(DEPLOYSALT=$DEPLOYSALT CREATE3_FACTORY_ADDRESS=$CREATE3_FACTORY_ADDRESS NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT=$DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS=$DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") DIAMOND_TYPE=$DIAMOND_TYPE forge script "$DEPLOY_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "${GAS_ESTIMATE_MULTIPLIER:-130}") + # deploy LDA facet and add to diamond using unified function + deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$LDA_DIAMOND_CONTRACT_NAME" "$FACET_VERSION" - # Extract deployed address - local ADDRESS=$(extractDeployedAddressFromRawReturnData "$RAW_RETURN_DATA" "$NETWORK") - if [[ -z "$ADDRESS" || "$ADDRESS" == "null" ]]; then - error "failed to deploy $LDA_DIAMOND_CONTRACT_NAME - could not extract address" + # check if deployment was successful + if [ $? -eq 0 ]; then + echo "[info] ✅ LDA facet $FACET_NAME deployed and added" + else + error "❌ Failed to deploy LDA facet $FACET_NAME" return 1 fi - - echo "[info] $LDA_DIAMOND_CONTRACT_NAME deployed to $NETWORK at address $ADDRESS" - - # Save contract in regular network deployment file only - saveContract "$NETWORK" "$LDA_DIAMOND_CONTRACT_NAME" "$ADDRESS" "$FILE_SUFFIX" - - # check if last command was executed successfully, otherwise exit script with error message - checkFailure $? "deploy contract $LDA_DIAMOND_CONTRACT_NAME to network $NETWORK" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $LDA_DIAMOND_CONTRACT_NAME successfully deployed" + done + + echo "[info] ✅ All LDA facets deployed and added" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 3 completed" + # ========================================================================= + # STEP 5: Create LDA diamond deployment logs + # ========================================================================= + echo "" + echo "[info] STEP 5: Creating LDA diamond deployment logs..." + + # Update LDA diamond logs to create/populate the .lda.diamond.json file + updateLDADiamondLogs "$ENVIRONMENT" "$NETWORK" + + if [ $? -ne 0 ]; then + error "❌ Failed to update LDA diamond logs" + return 1 fi + + echo "[info] ✅ LDA diamond logs created" - # Stage 4: Update LDA diamond with core facets - if [[ $START_STAGE -le 4 ]]; then - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 4: Update LDA diamond with core facets" - - # update LDA diamond with core facets - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now updating core facets in LDA diamond contract" - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" false - - # check if last command was executed successfully, otherwise exit script with error message - checkFailure $? "update LDA core facets in $LDA_DIAMOND_CONTRACT_NAME on network $NETWORK" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA core facets update completed" - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 4 completed" + # ========================================================================= + # STEP 6: Run health check + # ========================================================================= + echo "" + echo "[info] STEP 6: Running LDA health check..." + + bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" + + if [ $? -ne 0 ]; then + warning "⚠️ LDA health check failed - please review the output above" + echo "Continuing with ownership transfer..." + else + echo "[info] ✅ LDA health check passed" fi - # Stage 5: Deploy non-core LDA facets and add to diamond - if [[ $START_STAGE -le 5 ]]; then + # ========================================================================= + # STEP 7: Transfer ownership to multisig (production only) + # ========================================================================= + if [[ "$ENVIRONMENT" == "production" ]]; then echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 5: Deploy non-core LDA facets and add to diamond" - - # Get all LDA facet contract names from the LDA Facets directory - local LDA_FACETS_PATH="src/Periphery/LDA/Facets/" - echo "[info] Getting LDA facets from directory: $LDA_FACETS_PATH" + echo "[info] STEP 7: Transferring LDA Diamond ownership to multisig..." - # Read LDA core facets to exclude them from non-core deployment - local GLOBAL_CONFIG_PATH="./config/global.json" - local LDA_CORE_FACETS_JSON=$(jq -r '.ldaCoreFacets[]' "$GLOBAL_CONFIG_PATH") - local LDA_CORE_FACETS=() - while IFS= read -r facet; do - LDA_CORE_FACETS+=("$facet") - done <<< "$LDA_CORE_FACETS_JSON" - - # prepare regExp to exclude LDA core facets - local EXCLUDED_LDA_FACETS_REGEXP="^($(echo "${LDA_CORE_FACETS[@]}" | tr ' ' '|'))$" - - echo "[info] LDA core facets to exclude: ${LDA_CORE_FACETS[*]}" - echo "[info] Exclusion regex: $EXCLUDED_LDA_FACETS_REGEXP" - - # Deploy all non-core LDA facets and add to diamond - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now deploying non-core LDA facets and adding to diamond contract" - - # loop through LDA facet contract names - for FACET_NAME in $(getContractNamesInFolder "$LDA_FACETS_PATH"); do - echo "[info] Processing LDA facet: $FACET_NAME" - - # Skip if this is a core facet (already handled in previous stages) - if [[ "$FACET_NAME" =~ $EXCLUDED_LDA_FACETS_REGEXP ]]; then - echo "[info] Skipping LDA core facet: $FACET_NAME (already handled)" - continue - fi - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying non-core LDA facet: $FACET_NAME" - - # get current contract version - local FACET_VERSION=$(getCurrentContractVersion "$FACET_NAME") - - # deploy LDA facet and add to diamond - deployFacetAndAddToLDADiamond "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$LDA_DIAMOND_CONTRACT_NAME" "$FACET_VERSION" - - # check if deployment was successful - if [ $? -eq 0 ]; then - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA facet $FACET_NAME successfully deployed and added to $LDA_DIAMOND_CONTRACT_NAME" - else - error "failed to deploy and add LDA facet $FACET_NAME to $LDA_DIAMOND_CONTRACT_NAME on network $NETWORK" - return 1 - fi - done - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< non-core LDA facets deployment completed" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 5 completed" - fi - - # Stage 6: Update LDA diamond deployment logs - if [[ $START_STAGE -le 6 ]]; then - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 6: Update LDA diamond deployment logs" - echo "[info] Updating LDA diamond logs to populate .lda.diamond.json file..." + # Get SAFE address from networks.json + local SAFE_ADDRESS=$(jq -r ".${NETWORK}.safeAddress" "./config/networks.json") + if [[ -z "$SAFE_ADDRESS" || "$SAFE_ADDRESS" == "null" ]]; then + error "❌ SAFE address not found in networks.json for network $NETWORK" + return 1 + fi - # Update LDA diamond logs to create/populate the .lda.diamond.json file - updateLDADiamondLogs "$ENVIRONMENT" "$NETWORK" + echo "[info] Transferring LDA Diamond ownership to multisig: $SAFE_ADDRESS" - # check if last command was executed successfully - checkFailure $? "update LDA diamond logs for network $NETWORK" + # Transfer ownership directly to multisig (not timelock) + cast send "$LDA_DIAMOND_ADDRESS" "transferOwnership(address)" "$SAFE_ADDRESS" --private-key "$(getPrivateKey "$NETWORK" "$ENVIRONMENT")" --rpc-url "$(getRPCUrl "$NETWORK")" --legacy - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LDA diamond logs updated successfully" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 6 completed" - fi - - # Stage 7: Run LDA health check - if [[ $START_STAGE -le 7 ]]; then - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 7: Run LDA health check only" - bun script/deploy/ldaHealthCheck.ts --network "$NETWORK" --environment "$ENVIRONMENT" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 7 completed" - - # Pause and ask user if they want to continue with ownership transfer (for production) - if [[ "$ENVIRONMENT" == "production" && $START_STAGE -eq 7 ]]; then - echo "" - echo "Health check completed. Do you want to continue with ownership transfer to timelock?" - echo "This should only be done if the health check shows only diamond ownership errors." - echo "Continue with stage 8 (ownership transfer)? (y/n)" - read -r response - if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - echo "Proceeding with stage 8..." - else - echo "Skipping stage 8 - ownership transfer cancelled by user" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" - return - fi - fi - fi - - # Stage 8: Ownership transfer to timelock (production only) - if [[ $START_STAGE -le 8 ]]; then - if [[ "$ENVIRONMENT" == "production" ]]; then - echo "" - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> STAGE 8: Ownership transfer to timelock (production only)" - - # make sure SAFE_ADDRESS is available (if starting in stage 8 it's not available yet) - SAFE_ADDRESS=$(getValueFromJSONFile "./config/networks.json" "$NETWORK.safeAddress") - if [[ -z "$SAFE_ADDRESS" || "$SAFE_ADDRESS" == "null" ]]; then - echo "SAFE address not found in networks.json. Cannot prepare ownership transfer to Timelock" - exit 1 - fi - - # ------------------------------------------------------------ - # Prepare ownership transfer to Timelock - echo "" - echo "Preparing LDA Diamond ownership transfer to Timelock" - TIMELOCK_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "LiFiTimelockController") - if [[ -z "$TIMELOCK_ADDRESS" ]]; then - echo "Timelock address not found. Cannot prepare ownership transfer to Timelock" - exit 1 - fi - - # get LDA diamond address - LDA_DIAMOND_ADDRESS=$(getContractAddressFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME") - if [[ -z "$LDA_DIAMOND_ADDRESS" ]]; then - echo "LDA Diamond address not found. Cannot prepare ownership transfer to Timelock" - exit 1 - fi - - # initiate ownership transfer - echo "Initiating LDA Diamond ownership transfer to LiFiTimelockController ($TIMELOCK_ADDRESS)" - cast send "$LDA_DIAMOND_ADDRESS" "transferOwnership(address)" "$TIMELOCK_ADDRESS" --private-key "$PRIVATE_KEY_PRODUCTION" --rpc-url "$(getRPCUrl "$NETWORK")" --legacy - echo "LDA Diamond ownership transfer to LiFiTimelockController ($TIMELOCK_ADDRESS) initiated" - echo "" - - echo "" - echo "Proposing LDA Diamond ownership transfer acceptance tx to multisig ($SAFE_ADDRESS) via LiFiTimelockController ($TIMELOCK_ADDRESS)" - # propose tx with calldata 0x79ba5097 = confirmOwnershipTransfer() to LDA diamond (propose to multisig and wrap in timelock calldata with --timelock flag) - bun script/deploy/safe/propose-to-safe.ts --to "$LDA_DIAMOND_ADDRESS" --calldata 0x79ba5097 --network "$NETWORK" --rpcUrl "$(getRPCUrl "$NETWORK")" --privateKey "$PRIVATE_KEY_PRODUCTION" --timelock - echo "LDA Diamond ownership transfer acceptance proposed to multisig ($SAFE_ADDRESS) via LiFiTimelockController ($TIMELOCK_ADDRESS)" - echo "" - # ------------------------------------------------------------ + if [ $? -eq 0 ]; then + echo "[info] ✅ LDA Diamond ownership transferred to multisig: $SAFE_ADDRESS" else - echo "Stage 8 skipped - ownership transfer to timelock is only for production environment" + error "❌ Failed to transfer LDA Diamond ownership" + return 1 fi - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< STAGE 8 completed" + else + echo "" + echo "[info] STEP 7: Skipping ownership transfer (staging environment)" fi + # ========================================================================= + # DEPLOYMENT COMPLETE + # ========================================================================= echo "" - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAllLDAContracts completed" -} + echo "[info] =====================================================================" + echo "[info] ✅ LiFi DEX Aggregator (LDA) Diamond deployment COMPLETE!" + echo "[info] =====================================================================" + echo "[info] LDA Diamond Address: $LDA_DIAMOND_ADDRESS" + echo "[info] Network: $NETWORK" + echo "[info] Environment: $ENVIRONMENT" + if [[ "$ENVIRONMENT" == "production" ]]; then + echo "[info] Owner: $SAFE_ADDRESS (multisig)" + else + echo "[info] Owner: $(getDeployerAddress "" "$ENVIRONMENT") (deployer)" + fi + echo "[info] =====================================================================" + + return 0 +} \ No newline at end of file From 97d1382a8d5ca385820898ddf8d51c35ef3f589d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 21:09:53 +0200 Subject: [PATCH 167/220] Update audit verification condition in gh workflow to ensure it only runs for PRs targeting the main branch --- .github/workflows/versionControlAndAuditCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/versionControlAndAuditCheck.yml b/.github/workflows/versionControlAndAuditCheck.yml index 88aa6b08a..a36b371d8 100644 --- a/.github/workflows/versionControlAndAuditCheck.yml +++ b/.github/workflows/versionControlAndAuditCheck.yml @@ -308,7 +308,7 @@ jobs: # ------------------------ end of version control checker ------------------------ audit-verification: - if: ${{ github.event.pull_request.draft == false }} + if: ${{ github.event.pull_request.draft == false && github.event.pull_request.base.ref == 'main' }} needs: version-control runs-on: ubuntu-latest env: From 761106b37b76a48ee57878c90ef3110ba00e14a5 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 21:19:13 +0200 Subject: [PATCH 168/220] updated description --- .github/workflows/versionControlAndAuditCheck.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/versionControlAndAuditCheck.yml b/.github/workflows/versionControlAndAuditCheck.yml index a36b371d8..967f97238 100644 --- a/.github/workflows/versionControlAndAuditCheck.yml +++ b/.github/workflows/versionControlAndAuditCheck.yml @@ -1,14 +1,14 @@ # This git action combines version control and audit checks -# Version Control: +# Version Control (runs on all branches): # - will check all modified or new contracts in src/* # - makes sure that all contracts that have changes in audit-relevant code require a contract version update # - relevant changes => anything except for single (//) or multi-line (/*) comments and changes in solidity pragma # - fails if contract version was not updated despite relevant changes # - fails if a new contract was added without a ///custom:version tag # - will update the PR title to contain contract names with version changes (incl. their new version) -# Audit Checker: +# Audit Checker (runs only on PRs targeting main branch): # - will check all modified or new contracts in src/*, except for interfaces (src/Interfaces/**) as these do not require an audit -# - will only run if version-control job passed successfully +# - will only run if version-control job passed successfully AND PR targets main branch # - will remove the (protected) "AuditCompleted" label in the beginning to prevent erroneous states # - reuses the list of relevant contracts identified by the version-control job # - will assign "AuditRequired" label if relevant contracts were identified, otherwise it wil assign "AuditNotRequired" From 8ec8cd1c28610fab295c58e9a9379116be62f194 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 5 Sep 2025 22:47:44 +0200 Subject: [PATCH 169/220] updstes --- src/LiFiDiamond.sol | 2 +- .../EmergencyPauseFacet.fork.t.sol | 108 ++++++++++-------- test/solidity/LiFiDiamond.t.sol | 2 +- test/solidity/Periphery/GasZipPeriphery.t.sol | 2 +- .../LDA/LiFiDEXAggregatorDiamond.t.sol | 2 - test/solidity/utils/BaseDiamondTest.sol | 1 - test/solidity/utils/CommonDiamondTest.sol | 3 +- 7 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/LiFiDiamond.sol b/src/LiFiDiamond.sol index 3f619a683..04d6e9a1e 100644 --- a/src/LiFiDiamond.sol +++ b/src/LiFiDiamond.sol @@ -5,7 +5,7 @@ import { LibDiamond } from "./Libraries/LibDiamond.sol"; import { IDiamondCut } from "./Interfaces/IDiamondCut.sol"; import { InvalidConfig } from "./Errors/GenericErrors.sol"; -/// @title LIFIDiamond +/// @title LiFiDiamond /// @author LI.FI (https://li.fi) /// @notice Base EIP-2535 Diamond Proxy Contract. /// @custom:version 1.0.1 diff --git a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol index cdb093b70..47af655e1 100644 --- a/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol +++ b/test/solidity/Facets/EmergencyPauseFacet/EmergencyPauseFacet.fork.t.sol @@ -37,7 +37,7 @@ contract EmergencyPauseFacetPRODTest is TestBase { 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE; address internal constant USER_DIAMOND_OWNER_MAINNET = 0x37347dD595C49212C5FC2D95EA10d1085896f51E; - TestEmergencyPauseFacet internal emergencyPauseFacetTest; + TestEmergencyPauseFacet internal emergencyPauseFacetExtended; address[] internal blacklist = new address[](0); function setUp() public override { @@ -47,17 +47,23 @@ contract EmergencyPauseFacetPRODTest is TestBase { initTestBase(); // deploy EmergencyPauseFacet - emergencyPauseFacetTest = new TestEmergencyPauseFacet(USER_PAUSER); + emergencyPauseFacetExtended = new TestEmergencyPauseFacet(USER_PAUSER); // prepare diamondCut bytes4[] memory functionSelectors = new bytes4[](3); - functionSelectors[0] = emergencyPauseFacetTest.removeFacet.selector; - functionSelectors[1] = emergencyPauseFacetTest.pauseDiamond.selector; - functionSelectors[2] = emergencyPauseFacetTest.unpauseDiamond.selector; + functionSelectors[0] = emergencyPauseFacetExtended + .removeFacet + .selector; + functionSelectors[1] = emergencyPauseFacetExtended + .pauseDiamond + .selector; + functionSelectors[2] = emergencyPauseFacetExtended + .unpauseDiamond + .selector; cut.push( LibDiamond.FacetCut({ - facetAddress: address(emergencyPauseFacetTest), + facetAddress: address(emergencyPauseFacetExtended), action: LibDiamond.FacetCutAction.Add, functionSelectors: functionSelectors }) @@ -72,13 +78,13 @@ contract EmergencyPauseFacetPRODTest is TestBase { ); // store diamond in local TestEmergencyPauseFacet variable - emergencyPauseFacetTest = TestEmergencyPauseFacet( + emergencyPauseFacetExtended = TestEmergencyPauseFacet( payable(address(ADDRESS_DIAMOND_MAINNET)) ); // set facet address in TestBase setFacetAddressInTestBase( - address(emergencyPauseFacetTest), + address(emergencyPauseFacetExtended), "EmergencyPauseFacet" ); @@ -92,14 +98,14 @@ contract EmergencyPauseFacetPRODTest is TestBase { true, true, true, - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ); emit EmergencyPaused(USER_PAUSER); // pause the contract - emergencyPauseFacetTest.pauseDiamond(); + emergencyPauseFacetExtended.pauseDiamond(); // try to get a list of all registered facets via DiamondLoupe vm.expectRevert(DiamondIsPaused.selector); - DiamondLoupeFacet(address(emergencyPauseFacetTest)).facets(); + DiamondLoupeFacet(address(emergencyPauseFacetExtended)).facets(); } function test_DiamondOwnerCanPauseDiamond() public { @@ -110,33 +116,33 @@ contract EmergencyPauseFacetPRODTest is TestBase { true, true, true, - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ); emit EmergencyPaused(USER_DIAMOND_OWNER_MAINNET); // pause the contract - emergencyPauseFacetTest.pauseDiamond(); + emergencyPauseFacetExtended.pauseDiamond(); // try to get a list of all registered facets via DiamondLoupe vm.expectRevert(DiamondIsPaused.selector); - DiamondLoupeFacet(address(emergencyPauseFacetTest)).facets(); + DiamondLoupeFacet(address(emergencyPauseFacetExtended)).facets(); } function test_UnauthorizedWalletCannotPauseDiamond() public { vm.startPrank(USER_SENDER); vm.expectRevert(UnAuthorized.selector); // pause the contract - emergencyPauseFacetTest.pauseDiamond(); + emergencyPauseFacetExtended.pauseDiamond(); vm.startPrank(USER_RECEIVER); vm.expectRevert(UnAuthorized.selector); // pause the contract - emergencyPauseFacetTest.pauseDiamond(); + emergencyPauseFacetExtended.pauseDiamond(); } function test_DiamondOwnerCanUnpauseDiamond() public { IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); // pause diamond first @@ -150,15 +156,15 @@ contract EmergencyPauseFacetPRODTest is TestBase { true, true, true, - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ); emit EmergencyUnpaused(USER_DIAMOND_OWNER_MAINNET); - emergencyPauseFacetTest.unpauseDiamond(blacklist); + emergencyPauseFacetExtended.unpauseDiamond(blacklist); // make sure diamond works normal again and has all facets reinstated IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); assertTrue(initialFacets.length == finalFacets.length); @@ -171,26 +177,26 @@ contract EmergencyPauseFacetPRODTest is TestBase { // try to pause the diamond with various wallets vm.startPrank(USER_PAUSER); vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacetTest.unpauseDiamond(blacklist); + emergencyPauseFacetExtended.unpauseDiamond(blacklist); vm.startPrank(USER_RECEIVER); vm.expectRevert(OnlyContractOwner.selector); - emergencyPauseFacetTest.unpauseDiamond(blacklist); + emergencyPauseFacetExtended.unpauseDiamond(blacklist); // make sure diamond is still paused vm.expectRevert(DiamondIsPaused.selector); - DiamondLoupeFacet(address(emergencyPauseFacetTest)).facets(); + DiamondLoupeFacet(address(emergencyPauseFacetExtended)).facets(); } function test_DiamondOwnerCanRemoveFacet() public { // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); // get PeripheryRegistryFacet address address facetAddress = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facetAddress( PeripheryRegistryFacet(address(emergencyPauseFacet)) .registerPeripheryContract @@ -205,26 +211,27 @@ contract EmergencyPauseFacetPRODTest is TestBase { true, true, true, - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ); emit EmergencyFacetRemoved(facetAddress, USER_DIAMOND_OWNER_MAINNET); - emergencyPauseFacetTest.removeFacet(facetAddress); + emergencyPauseFacetExtended.removeFacet(facetAddress); // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); // ensure that one facet less is registered now assertTrue(initialFacets.length == finalFacets.length + 1); // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress assertTrue( - DiamondLoupeFacet(address(emergencyPauseFacetTest)).facetAddress( - PeripheryRegistryFacet(address(emergencyPauseFacet)) - .registerPeripheryContract - .selector - ) == address(0) + DiamondLoupeFacet(address(emergencyPauseFacetExtended)) + .facetAddress( + PeripheryRegistryFacet(address(emergencyPauseFacet)) + .registerPeripheryContract + .selector + ) == address(0) ); vm.stopPrank(); @@ -233,14 +240,14 @@ contract EmergencyPauseFacetPRODTest is TestBase { function test_PauserWalletCanRemoveFacet() public { // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); // get PeripheryRegistryFacet address address facetAddress = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facetAddress( - PeripheryRegistryFacet(address(emergencyPauseFacetTest)) + PeripheryRegistryFacet(address(emergencyPauseFacetExtended)) .registerPeripheryContract .selector ); @@ -253,26 +260,27 @@ contract EmergencyPauseFacetPRODTest is TestBase { true, true, true, - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ); emit EmergencyFacetRemoved(facetAddress, USER_PAUSER); - emergencyPauseFacetTest.removeFacet(facetAddress); + emergencyPauseFacetExtended.removeFacet(facetAddress); // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); // ensure that one facet less is registered now assertTrue(initialFacets.length == finalFacets.length + 1); // ensure that PeripheryRegistryFacet function selector is not associated to any facetAddress assertTrue( - DiamondLoupeFacet(address(emergencyPauseFacetTest)).facetAddress( - PeripheryRegistryFacet(address(emergencyPauseFacetTest)) - .registerPeripheryContract - .selector - ) == address(0) + DiamondLoupeFacet(address(emergencyPauseFacetExtended)) + .facetAddress( + PeripheryRegistryFacet( + address(emergencyPauseFacetExtended) + ).registerPeripheryContract.selector + ) == address(0) ); vm.stopPrank(); @@ -281,14 +289,14 @@ contract EmergencyPauseFacetPRODTest is TestBase { function test_UnauthorizedWalletCannotRemoveFacet() public { // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory initialFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); // get PeripheryRegistryFacet address address facetAddress = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facetAddress( - PeripheryRegistryFacet(address(emergencyPauseFacetTest)) + PeripheryRegistryFacet(address(emergencyPauseFacetExtended)) .registerPeripheryContract .selector ); @@ -296,15 +304,15 @@ contract EmergencyPauseFacetPRODTest is TestBase { // try to remove facet vm.startPrank(USER_SENDER); vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacetTest.removeFacet(facetAddress); + emergencyPauseFacetExtended.removeFacet(facetAddress); vm.startPrank(USER_RECEIVER); vm.expectRevert(UnAuthorized.selector); - emergencyPauseFacetTest.removeFacet(facetAddress); + emergencyPauseFacetExtended.removeFacet(facetAddress); // get a list of all registered facet addresses IDiamondLoupe.Facet[] memory finalFacets = DiamondLoupeFacet( - address(emergencyPauseFacetTest) + address(emergencyPauseFacetExtended) ).facets(); // ensure that number of facets remains unchanged diff --git a/test/solidity/LiFiDiamond.t.sol b/test/solidity/LiFiDiamond.t.sol index 0b6cc8794..d1b1bd5ee 100644 --- a/test/solidity/LiFiDiamond.t.sol +++ b/test/solidity/LiFiDiamond.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; diff --git a/test/solidity/Periphery/GasZipPeriphery.t.sol b/test/solidity/Periphery/GasZipPeriphery.t.sol index 399ce42fd..d0259067c 100644 --- a/test/solidity/Periphery/GasZipPeriphery.t.sol +++ b/test/solidity/Periphery/GasZipPeriphery.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { GasZipPeriphery } from "lifi/Periphery/GasZipPeriphery.sol"; diff --git a/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol index 30ab6660e..627f5177d 100644 --- a/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol +++ b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol @@ -6,9 +6,7 @@ import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { CommonDiamondTest } from "../../utils/CommonDiamondTest.sol"; -/// @title DEXAggregatorDiamondTest /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. -/// @dev Child test suites inherit this to get a ready-to-cut diamond and helper to assemble facets. contract LiFiDEXAggregatorDiamondTest is CommonDiamondTest { LiFiDEXAggregatorDiamond public ldaDiamond; diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol index 3cf330cb0..b98df4729 100644 --- a/test/solidity/utils/BaseDiamondTest.sol +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -8,7 +8,6 @@ import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { Test } from "forge-std/Test.sol"; import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; -/// @title BaseDiamondTest /// @notice Minimal helper to compose a test Diamond and add facets/selectors for test scenarios. /// @dev Provides overloads to add facets with or without init calldata. /// This contract is used by higher-level LDA test scaffolding to assemble the test Diamond. diff --git a/test/solidity/utils/CommonDiamondTest.sol b/test/solidity/utils/CommonDiamondTest.sol index cb1b27c29..a6ee89257 100644 --- a/test/solidity/utils/CommonDiamondTest.sol +++ b/test/solidity/utils/CommonDiamondTest.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; @@ -10,7 +10,6 @@ import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { BaseDiamondTest } from "./BaseDiamondTest.sol"; -/// @title CommonDiamondTest /// @notice Base contract with common diamond test functions to reduce code duplication /// @dev Provides standard test patterns that work with any diamond implementation abstract contract CommonDiamondTest is BaseDiamondTest { From ea98621a75f0e2ffe35dc54fafd5993d85397488 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sat, 6 Sep 2025 14:58:12 +0200 Subject: [PATCH 170/220] updates --- deployments/_deployments_log_file.json | 327 +++++++++++++----- deployments/bsc.lda.diamond.staging.json | 58 ++++ deployments/bsc.staging.json | 13 +- deployments/optimism.lda.diamond.staging.json | 2 +- deployments/optimism.staging.json | 4 +- script/deploy/deployAllLDAContracts.sh | 45 ++- .../facets/LDA/DeployCoreRouteFacet.s.sol | 18 - .../LDA/DeployLiFiDEXAggregatorDiamond.s.sol | 48 +-- script/deploy/ldaHealthCheck.ts | 24 +- .../LDA/DeployCoreRouteFacet.zksync.s.sol | 18 - ...eployLiFiDEXAggregatorDiamond.zksync.s.sol | 49 ++- script/scriptMaster.sh | 45 ++- script/tasks/ldaDiamondUpdateFacet.sh | 26 +- 13 files changed, 448 insertions(+), 229 deletions(-) create mode 100644 deployments/bsc.lda.diamond.staging.json mode change 100644 => 100755 script/deploy/ldaHealthCheck.ts diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index f573973f1..20793f6fd 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -483,11 +483,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xd6731f2729e24F3eAeDe8d10aFBd908460c021c5", + "ADDRESS": "0xA7F7479222659c38E60981aEe4b48080f367C2f8", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 19:46:31", + "TIMESTAMP": "2025-09-06 13:01:01", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -1541,11 +1541,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xB03d1c11529d248727aE1fE1570f8Fd664968e31", + "ADDRESS": "0xEF948675ABD792E38b4caCA7334A480A9257C8d2", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:37:53", + "TIMESTAMP": "2025-09-06 13:02:19", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -2612,11 +2612,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x1773bB176Ee1A7774bDeaf8fff92991bC005A56A", + "ADDRESS": "0xa5b5CC96a7a20c37fd7665984063FEc0eE4823d7", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:39:18", + "TIMESTAMP": "2025-09-06 13:06:18", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -39834,50 +39834,65 @@ ] } }, - "optimism": { + "base": { "staging": { "1.0.0": [ { - "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "ADDRESS": "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:10:35", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", - "SALT": "", + "TIMESTAMP": "2025-09-06 14:25:02", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { + "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0xaFAf106f4D8eDdF68deE9F06dA0CB12BE1f6BD51", + "ADDRESS": "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 18:42:03", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", - "SALT": "", + "TIMESTAMP": "2025-09-06 14:46:49", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" + "ZK_SOLC_VERSION": "" } ] } }, - "base": { + "optimism": { "staging": { "1.0.0": [ { - "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "ADDRESS": "0xCaC140589393C15DA1C8E45942B3b8228CBA95C8", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:51:51", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", - "SALT": "", + "TIMESTAMP": "2025-09-06 10:18:57", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345680", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x73988E03aA07b41C71DfF1E148405Cb7631655E6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 12:21:20", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.15" + } + ] + } } }, "UniV3StyleFacet": { @@ -39890,7 +39905,7 @@ "TIMESTAMP": "2025-09-04 14:27:13", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -39915,11 +39930,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x0B960e4a64C325062B78f1A2FaE51E7bF6594E50", + "ADDRESS": "0x2999f682a6e005DbA4b69e3733D49C9De27acC42", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:54:50", + "TIMESTAMP": "2025-09-06 12:46:45", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -39930,11 +39945,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "ADDRESS": "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:59:04", + "TIMESTAMP": "2025-09-06 14:29:44", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:55:33", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -39952,7 +39982,7 @@ "TIMESTAMP": "2025-09-04 14:26:48", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -39977,11 +40007,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xde90fA5Cbf867bF04954E53f124C3cE1e1417426", + "ADDRESS": "0xEe014D9B6AF188909E6cE88c37D19bA703799F39", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:53:31", + "TIMESTAMP": "2025-09-06 12:39:36", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -39992,11 +40022,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "ADDRESS": "0x098C5Fbb3A212650b903e42973A1381E6D00f585", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:58:03", + "TIMESTAMP": "2025-09-06 14:29:03", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x098C5Fbb3A212650b903e42973A1381E6D00f585", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:54:54", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -40039,11 +40084,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xD825ea1B88b1b10d0adD49a92c610E912Cb2fafF", + "ADDRESS": "0xc9d130cA48f858eC429bd4E0c56A44528D54e78f", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:50:53", + "TIMESTAMP": "2025-09-06 12:32:26", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -40054,11 +40099,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "ADDRESS": "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:55:58", + "TIMESTAMP": "2025-09-06 14:27:49", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:54:14", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -40101,11 +40161,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xE3bE94EFf5ce759Cb4BbDe2c51c8900072F22f0b", + "ADDRESS": "0xCA5C3AE030173A446e1C2443BA6EB6c3B478d47D", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:47:49", + "TIMESTAMP": "2025-09-06 12:27:19", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -40116,11 +40176,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "ADDRESS": "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:53:55", + "TIMESTAMP": "2025-09-06 14:26:26", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:53:32", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -40159,21 +40234,6 @@ ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xacB28A8DF1D8073d014F14D4509C2b3a30220b32", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 19:25:21", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de6200000000000000000000000020305d7ceaf95d4baa29621ef39661fd756bc024", - "SALT": "", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "base": { "staging": { "1.0.0": [ @@ -40225,11 +40285,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x6ac283a65406eef58c417ad7B83E10C75108d7C5", + "ADDRESS": "0xbfd10Ec90a8C4f3e2075812c3F5B402d55E6302c", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 18:40:40", + "TIMESTAMP": "2025-09-06 12:18:48", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -40240,11 +40300,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "ADDRESS": "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:50:50", + "TIMESTAMP": "2025-09-06 14:24:22", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:46:12", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -40287,11 +40362,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x44BD7D613c16480287CFDDe44820dA22E2a7F848", + "ADDRESS": "0xb2a55d3b13277A86dD5Cd32D03CbcDF98B20576f", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 18:43:50", + "TIMESTAMP": "2025-09-06 12:24:43", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -40302,11 +40377,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "ADDRESS": "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:52:53", + "TIMESTAMP": "2025-09-06 14:25:45", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:52:46", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -40349,11 +40439,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xfBCE7EFcaeEf1DD2D84344FbbE050E5b11f2A931", + "ADDRESS": "0xB3DbFeD58830eD2493c533248942C01006097bdC", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:49:06", + "TIMESTAMP": "2025-09-06 12:29:53", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -40364,11 +40454,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "ADDRESS": "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:54:57", + "TIMESTAMP": "2025-09-06 14:27:17", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:53:53", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -40411,11 +40516,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xe6d5cd6aE1a72471D2862FED04Bd8cfF978FFB45", + "ADDRESS": "0x6A5C8b4f932e7Ab7ADAaE1fB201c601B4585aC93", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:52:11", + "TIMESTAMP": "2025-09-06 12:35:43", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -40426,11 +40531,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "ADDRESS": "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:57:01", + "TIMESTAMP": "2025-09-06 14:28:31", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:54:35", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } @@ -40473,11 +40593,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x73bFe1D0DFe7933de9ea25F50bC8eacE80bB6613", + "ADDRESS": "0x5f9Bbf7BE25927567822Debf0385d8a5Db2e2473", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 17:56:57", + "TIMESTAMP": "2025-09-06 12:50:10", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "1.5.15" } @@ -40488,11 +40608,26 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "ADDRESS": "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:59:57", + "TIMESTAMP": "2025-09-06 14:30:27", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "12345123", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 14:55:56", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "12345123", "VERIFIED": "false", "ZK_SOLC_VERSION": "" } diff --git a/deployments/bsc.lda.diamond.staging.json b/deployments/bsc.lda.diamond.staging.json new file mode 100644 index 000000000..6170f4a91 --- /dev/null +++ b/deployments/bsc.lda.diamond.staging.json @@ -0,0 +1,58 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": { + "0x06045F5FA6EA7c6AcEb104b55BcD6C3dE3a08831": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0x8938CEa23C3c5eAABb895765f5B0b2b07D680402": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x53d4Bcd5BEa4e863376b7eA43D7465351a4d71B0": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25": { + "Name": "AlgebraFacet", + "Version": "1.0.0" + }, + "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f": { + "Name": "CoreRouteFacet", + "Version": "1.0.0" + }, + "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803": { + "Name": "CurveFacet", + "Version": "1.0.0" + }, + "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3": { + "Name": "IzumiV3Facet", + "Version": "1.0.0" + }, + "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942": { + "Name": "KatanaV3Facet", + "Version": "1.0.0" + }, + "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc": { + "Name": "NativeWrapperFacet", + "Version": "1.0.0" + }, + "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833": { + "Name": "SyncSwapV2Facet", + "Version": "1.0.0" + }, + "0x098C5Fbb3A212650b903e42973A1381E6D00f585": { + "Name": "UniV2StyleFacet", + "Version": "1.0.0" + }, + "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5": { + "Name": "UniV3StyleFacet", + "Version": "1.0.0" + }, + "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4": { + "Name": "VelodromeV2Facet", + "Version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/deployments/bsc.staging.json b/deployments/bsc.staging.json index 5b5014a94..ba67a57af 100644 --- a/deployments/bsc.staging.json +++ b/deployments/bsc.staging.json @@ -33,5 +33,16 @@ "EmergencyPauseFacet": "0xf03AFcA857918BE01EBD6C6800Fc2974b8a9eBA2", "StargateFacetV2": "0x089153117bffd37CBbE0c604dAE8e493D4743fA8", "LiFiDEXAggregator": "0x6140b987d6b51fd75b66c3b07733beb5167c42fc", - "GasZipPeriphery": "0x46d8Aa20D5aD98927Cf885De9eBf9436E8E551c2" + "GasZipPeriphery": "0x46d8Aa20D5aD98927Cf885De9eBf9436E8E551c2", + "LiFiDEXAggregatorDiamond": "0x881CE612e3390d997Bb5f13749aF2Ee9B55DECF0", + "AlgebraFacet": "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25", + "CoreRouteFacet": "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f", + "CurveFacet": "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803", + "IzumiV3Facet": "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3", + "KatanaV3Facet": "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942", + "NativeWrapperFacet": "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc", + "SyncSwapV2Facet": "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833", + "UniV2StyleFacet": "0x098C5Fbb3A212650b903e42973A1381E6D00f585", + "UniV3StyleFacet": "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5", + "VelodromeV2Facet": "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4" } \ No newline at end of file diff --git a/deployments/optimism.lda.diamond.staging.json b/deployments/optimism.lda.diamond.staging.json index 07905ba44..fb16d2ca1 100644 --- a/deployments/optimism.lda.diamond.staging.json +++ b/deployments/optimism.lda.diamond.staging.json @@ -17,7 +17,7 @@ "Name": "AlgebraFacet", "Version": "1.0.0" }, - "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef": { + "0xCaC140589393C15DA1C8E45942B3b8228CBA95C8": { "Name": "CoreRouteFacet", "Version": "1.0.0" }, diff --git a/deployments/optimism.staging.json b/deployments/optimism.staging.json index 4037b0882..416ba0da9 100644 --- a/deployments/optimism.staging.json +++ b/deployments/optimism.staging.json @@ -49,7 +49,6 @@ "GlacisFacet": "0x36e1375B0755162d720276dFF6893DF02bd49225", "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", @@ -57,5 +56,6 @@ "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" + "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "CoreRouteFacet": "0xCaC140589393C15DA1C8E45942B3b8228CBA95C8" } \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 0c690c401..73ee8cd3d 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -102,18 +102,39 @@ deployAllLDAContracts() { # get current LDA diamond contract version local VERSION=$(getCurrentContractVersion "$LDA_DIAMOND_CONTRACT_NAME") - # Call the deploy script directly to avoid infinite loop - local DEPLOY_SCRIPT_PATH="script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol" - - # Get required deployment variables - local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") - local CREATE3_FACTORY_ADDRESS=$(getCreate3FactoryAddress "$NETWORK") - local SALT_INPUT="$BYTECODE""$SALT" - local DEPLOYSALT=$(cast keccak "$SALT_INPUT") - - # Deploy the LDA diamond using forge script directly - echo "[info] Deploying $LDA_DIAMOND_CONTRACT_NAME..." - local RAW_RETURN_DATA=$(DEPLOYSALT=$DEPLOYSALT CREATE3_FACTORY_ADDRESS=$CREATE3_FACTORY_ADDRESS NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT=$DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS=$DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") DIAMOND_TYPE=$DIAMOND_TYPE forge script "$DEPLOY_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "${GAS_ESTIMATE_MULTIPLIER:-130}") + # Determine the correct deploy script path based on network type + if isZkEvmNetwork "$NETWORK"; then + local DEPLOY_SCRIPT_PATH="script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol" + echo "[info] Deploying $LDA_DIAMOND_CONTRACT_NAME using ZkSync script..." + + # Compile contracts with ZkSync compiler first + echo "[info] Compiling contracts with ZkSync compiler..." + FOUNDRY_PROFILE=zksync ./foundry-zksync/forge build --zksync + checkFailure $? "compile contracts with ZkSync compiler" + + # Get required deployment variables for ZkSync deployment + local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") + local SALT_INPUT="$BYTECODE""$SALT" + local DEPLOYSALT=$(cast keccak "$SALT_INPUT") + + # For ZkSync networks, pass the regular FILE_SUFFIX so the script can construct the correct path to read DiamondCutFacet + # The ZkSync script will use this to read from the regular deployment file (e.g., zksync.staging.json) + + # For ZkSync networks, use ZkSync-specific deployment with DEPLOYSALT and regular file suffix + local RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync DEPLOYSALT=$DEPLOYSALT NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT=$DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS=$DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") DIAMOND_TYPE=$DIAMOND_TYPE ./foundry-zksync/forge script "$DEPLOY_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --skip-simulation --slow --zksync --gas-estimate-multiplier "${GAS_ESTIMATE_MULTIPLIER:-130}") + else + local DEPLOY_SCRIPT_PATH="script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol" + + # Get required deployment variables for CREATE3 deployment + local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") + local CREATE3_FACTORY_ADDRESS=$(getCreate3FactoryAddress "$NETWORK") + local SALT_INPUT="$BYTECODE""$SALT" + local DEPLOYSALT=$(cast keccak "$SALT_INPUT") + + echo "[info] Deploying $LDA_DIAMOND_CONTRACT_NAME using CREATE3 factory..." + # Deploy the LDA diamond using CREATE3 factory + local RAW_RETURN_DATA=$(DEPLOYSALT=$DEPLOYSALT CREATE3_FACTORY_ADDRESS=$CREATE3_FACTORY_ADDRESS NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT=$DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS=$DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") DIAMOND_TYPE=$DIAMOND_TYPE forge script "$DEPLOY_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --legacy --slow --gas-estimate-multiplier "${GAS_ESTIMATE_MULTIPLIER:-130}") + fi # Extract deployed address local LDA_DIAMOND_ADDRESS=$(extractDeployedAddressFromRawReturnData "$RAW_RETURN_DATA" "$NETWORK") diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index 5fda51651..6ed262add 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -18,22 +18,4 @@ contract DeployScript is DeployLDAScriptBase { deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); } - - function getConstructorArgs() internal override returns (bytes memory) { - // get path of global config file - string memory globalConfigPath = string.concat( - root, - "/config/global.json" - ); - - // read file into json variable - string memory globalConfigJson = vm.readFile(globalConfigPath); - - // extract refundWallet address as owner - address refundWalletAddress = globalConfigJson.readAddress( - ".refundWallet" - ); - - return abi.encode(refundWalletAddress); - } } diff --git a/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol index aef1609c1..82c7e57b6 100644 --- a/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol +++ b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol @@ -25,41 +25,31 @@ contract DeployScript is DeployScriptBase { function getConstructorArgs() internal override returns (bytes memory) { // LDA Diamond uses DiamondCutFacet from regular deployment (shared with regular LiFi Diamond) - // Need to construct regular deployment path by removing "lda." prefix from fileSuffix + // Construct path to regular deployment file: .json or ..json - string memory regularFileSuffix; - bytes memory fileSuffixBytes = bytes(fileSuffix); + string memory regularPath; - // Check if fileSuffix starts with "lda." and remove it - if ( - fileSuffixBytes.length >= 4 && - fileSuffixBytes[0] == "l" && - fileSuffixBytes[1] == "d" && - fileSuffixBytes[2] == "a" && - fileSuffixBytes[3] == "." - ) { - // Extract everything after "lda." by creating new bytes array - bytes memory remainingBytes = new bytes( - fileSuffixBytes.length - 4 + // Check if fileSuffix is provided (non-production environment) + if (bytes(fileSuffix).length > 0) { + // Non-production: network..json (e.g., network.staging.json, network.testnet.json) + regularPath = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" ); - for (uint256 i = 4; i < fileSuffixBytes.length; i++) { - remainingBytes[i - 4] = fileSuffixBytes[i]; - } - regularFileSuffix = string(remainingBytes); } else { - // If no "lda." prefix, use as is - regularFileSuffix = fileSuffix; + // Production: network.json + regularPath = string.concat( + root, + "/deployments/", + network, + ".json" + ); } - string memory regularPath = string.concat( - root, - "/deployments/", - network, - ".", - regularFileSuffix, - "json" - ); - emit log_named_string("regularPath", regularPath); // Get DiamondCutFacet address from regular deployment file diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts old mode 100644 new mode 100755 index db5c64579..583649d80 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -90,7 +90,9 @@ const main = defineCommand({ const stagingFile = `../../deployments/${network.toLowerCase()}.staging.json` consola.info(`Loading staging deployment file: ${stagingFile}`) try { - const { default: contracts } = await import(stagingFile) + const { default: contracts } = await import(stagingFile, { + with: { type: 'json' }, + }) mainDeployedContracts = contracts consola.info( `Successfully loaded ${ @@ -109,7 +111,9 @@ const main = defineCommand({ consola.info( `Falling back to main deployment file: ${mainDeploymentFile}` ) - const { default: contracts } = await import(mainDeploymentFile) + const { default: contracts } = await import(mainDeploymentFile, { + with: { type: 'json' }, + }) mainDeployedContracts = contracts consola.info( `Successfully loaded ${ @@ -135,7 +139,9 @@ const main = defineCommand({ // Production - use main deployment file else try { - const { default: contracts } = await import(mainDeploymentFile) + const { default: contracts } = await import(mainDeploymentFile, { + with: { type: 'json' }, + }) mainDeployedContracts = contracts } catch (error) { consola.error( @@ -156,7 +162,9 @@ const main = defineCommand({ > = {} try { - const { default: ldaData } = await import(ldaDeploymentFile) + const { default: ldaData } = await import(ldaDeploymentFile, { + with: { type: 'json' }, + }) ldaFacetInfo = ldaData } catch (error) { consola.error(`Failed to load LDA facet file: ${ldaDeploymentFile}`) @@ -167,8 +175,12 @@ const main = defineCommand({ } // Load global config for LDA core facets - const globalConfig = await import('../../config/global.json') - const networksConfigModule = await import('../../config/networks.json') + const globalConfig = await import('../../config/global.json', { + with: { type: 'json' }, + }) + const networksConfigModule = await import('../../config/networks.json', { + with: { type: 'json' }, + }) const networksConfig = networksConfigModule.default // Get LDA core facets from global config diff --git a/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol index d9fbfbcd3..6aa115a57 100644 --- a/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol @@ -18,22 +18,4 @@ contract DeployScript is DeployScriptBase { deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); } - - function getConstructorArgs() internal override returns (bytes memory) { - // get path of global config file - string memory globalConfigPath = string.concat( - root, - "/config/global.json" - ); - - // read file into json variable - string memory globalConfigJson = vm.readFile(globalConfigPath); - - // extract refundWallet address as owner - address refundWalletAddress = globalConfigJson.readAddress( - ".refundWallet" - ); - - return abi.encode(refundWalletAddress); - } } diff --git a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol index 93a45528f..338a2591d 100644 --- a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol @@ -25,41 +25,32 @@ contract DeployScript is DeployScriptBase { function getConstructorArgs() internal override returns (bytes memory) { // LDA Diamond uses DiamondCutFacet from regular deployment (shared with regular LiFi Diamond) - // Need to construct regular deployment path by removing "lda." prefix from fileSuffix + // Construct path to regular deployment file: .json or ..json - string memory regularFileSuffix; - bytes memory fileSuffixBytes = bytes(fileSuffix); + string memory regularPath; - // Check if fileSuffix starts with "lda." and remove it - if ( - fileSuffixBytes.length >= 4 && - fileSuffixBytes[0] == "l" && - fileSuffixBytes[1] == "d" && - fileSuffixBytes[2] == "a" && - fileSuffixBytes[3] == "." - ) { - // Extract everything after "lda." by creating new bytes array - bytes memory remainingBytes = new bytes( - fileSuffixBytes.length - 4 + // Check if fileSuffix is provided (non-production environment) + if (bytes(fileSuffix).length > 0) { + // Non-production: network..json (e.g., network.staging.json, network.testnet.json) + // Note: fileSuffix already includes trailing dot (e.g., "staging.") + regularPath = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" ); - for (uint256 i = 4; i < fileSuffixBytes.length; i++) { - remainingBytes[i - 4] = fileSuffixBytes[i]; - } - regularFileSuffix = string(remainingBytes); } else { - // If no "lda." prefix, use as is - regularFileSuffix = fileSuffix; + // Production: network.json + regularPath = string.concat( + root, + "/deployments/", + network, + ".json" + ); } - string memory regularPath = string.concat( - root, - "/deployments/", - network, - ".", - regularFileSuffix, - "json" - ); - emit log_named_string("regularPath", regularPath); // Get DiamondCutFacet address from regular deployment file diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index 4e6f8b797..10a998507 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -150,12 +150,16 @@ scriptMaster() { # We need to make sure that the zksync fork of foundry is available before # we can deploy contracts to zksync. if isZkEvmNetwork "$NETWORK"; then - # Use zksync specific scripts - DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/" # Check if the foundry-zksync binaries exist, if not fetch them install_foundry_zksync - # get user-selected deploy script and contract from list - SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") + + # Combine regular ZkSync contracts and LDA ZkSync contracts in the selection + REGULAR_ZKSYNC_SCRIPTS=$(ls -1 "script/deploy/zksync/" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy') + LDA_ZKSYNC_SCRIPTS=$(ls -1 "script/deploy/zksync/LDA/" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy') + + # Combine both lists and let user select + ALL_ZKSYNC_SCRIPTS=$(echo -e "$REGULAR_ZKSYNC_SCRIPTS\n$LDA_ZKSYNC_SCRIPTS") + SCRIPT=$(echo "$ALL_ZKSYNC_SCRIPTS" | gum filter --placeholder "Deploy Script") else # Combine regular LiFi contracts and LDA contracts in the selection REGULAR_SCRIPTS=$(ls -1 "script/deploy/facets/" | sed -e 's/\.s.sol$//' | grep 'Deploy') @@ -169,11 +173,21 @@ scriptMaster() { # get user-selected deploy script and contract from list CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') - # Set appropriate directory (deploySingleContract will auto-detect LDA based on contract name) - if echo "$LDA_SCRIPTS" | grep -q "^$SCRIPT$"; then - DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" + # Set appropriate directory based on network type and script location + if isZkEvmNetwork "$NETWORK"; then + # For ZkSync networks, check if it's an LDA script + if echo "$LDA_ZKSYNC_SCRIPTS" | grep -q "^$SCRIPT$"; then + DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/LDA/" + else + DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/" + fi else - DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" + # For regular networks, check if it's an LDA script + if echo "$LDA_SCRIPTS" | grep -q "^$SCRIPT$"; then + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" + else + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" + fi fi # check if new contract should be added to diamond after deployment @@ -181,8 +195,19 @@ scriptMaster() { if [[ ! "$CONTRACT" == "LiFiDiamond"* && ! "$CONTRACT" == "LiFiDEXAggregatorDiamond" ]]; then echo "" echo "Do you want to add this contract to a diamond after deployment?" - # Check if this is an LDA contract based on script directory - if echo "$LDA_SCRIPTS" | grep -q "^Deploy${CONTRACT}$"; then + # Check if this is an LDA contract based on network type and script + IS_LDA_CONTRACT=false + if isZkEvmNetwork "$NETWORK"; then + if echo "$LDA_ZKSYNC_SCRIPTS" | grep -q "^Deploy${CONTRACT}$"; then + IS_LDA_CONTRACT=true + fi + else + if echo "$LDA_SCRIPTS" | grep -q "^Deploy${CONTRACT}$"; then + IS_LDA_CONTRACT=true + fi + fi + + if [[ "$IS_LDA_CONTRACT" == "true" ]]; then # For LDA contracts, offer LDA diamond option ADD_TO_DIAMOND=$( gum choose \ diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh index f8ed2dbfd..7b135d780 100644 --- a/script/tasks/ldaDiamondUpdateFacet.sh +++ b/script/tasks/ldaDiamondUpdateFacet.sh @@ -65,8 +65,12 @@ function ldaDiamondUpdateFacet() { UPDATE_SCRIPT="$SCRIPT" fi - # set LDA-specific script directory - LDA_UPDATE_SCRIPT_PATH="script/deploy/facets/LDA/${UPDATE_SCRIPT}.s.sol" + # set LDA-specific script directory (use ZkSync path for ZkSync networks) + if isZkEvmNetwork "$NETWORK"; then + LDA_UPDATE_SCRIPT_PATH="script/deploy/zksync/LDA/${UPDATE_SCRIPT}.zksync.s.sol" + else + LDA_UPDATE_SCRIPT_PATH="script/deploy/facets/LDA/${UPDATE_SCRIPT}.s.sol" + fi # check if LDA update script exists if ! checkIfFileExists "$LDA_UPDATE_SCRIPT_PATH" >/dev/null; then @@ -74,8 +78,7 @@ function ldaDiamondUpdateFacet() { return 1 fi - # get LDA diamond address from LDA diamond deployment file - local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" + local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$LDA_DEPLOYMENT_FILE") # if no diamond address was found, throw an error and exit this script @@ -116,8 +119,16 @@ function ldaDiamondUpdateFacet() { # ensure that gas price is below maximum threshold (for mainnet only) doNotContinueUnlessGasIsBelowThreshold "$NETWORK" - # try to execute call - RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") + # try to execute call (use ZkSync forge for ZkSync networks) + if isZkEvmNetwork "$NETWORK"; then + # For ZkSync networks, use ZkSync-specific forge and compile first + echo "[info] Compiling contracts with ZkSync compiler for LDA diamond update..." + FOUNDRY_PROFILE=zksync ./foundry-zksync/forge build --zksync + RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") ./foundry-zksync/forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --skip-simulation --slow --zksync --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") + else + # For regular networks, use regular forge + RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") + fi local RETURN_CODE=$? @@ -144,7 +155,8 @@ function ldaDiamondUpdateFacet() { if [[ $FACETS != "{}" ]]; then echo "[info] LDA diamond update was successful" if [[ $SHOW_LOGS == "true" ]]; then - echo "[info] Updated diamond now has $(echo "$FACETS" | jq -r '. | length') facets" + FACET_COUNT=$(echo "$FACETS" | jq -r '. | length' 2>/dev/null || echo "unknown") + echo "[info] Updated diamond now has $FACET_COUNT facets" fi return 0 # exit the loop if the operation was successful fi From 7c7bad7e3f3c75bad4d3cd7371b5932b3e76480b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sat, 6 Sep 2025 18:26:39 +0200 Subject: [PATCH 171/220] changes --- .eslintrc.cjs | 1 - deployments/_deployments_log_file.json | 245 +++--------------- deployments/base.staging.json | 13 +- deployments/bsc.lda.diamond.staging.json | 20 +- deployments/bsc.staging.json | 22 +- script/deploy/deployAllLDAContracts.sh | 2 +- script/deploy/deploySingleContract.sh | 14 +- .../facets/LDA/DeployAlgebraFacet.s.sol | 6 +- .../facets/LDA/DeployCoreRouteFacet.s.sol | 6 +- .../deploy/facets/LDA/DeployCurveFacet.s.sol | 6 +- .../facets/LDA/DeployIzumiV3Facet.s.sol | 6 +- .../facets/LDA/DeployKatanaV3Facet.s.sol | 6 +- .../LDA/DeployLiFiDEXAggregatorDiamond.s.sol | 40 +-- .../facets/LDA/DeployNativeWrapperFacet.s.sol | 10 +- .../facets/LDA/DeploySyncSwapV2Facet.s.sol | 6 +- .../facets/LDA/DeployUniV2StyleFacet.s.sol | 6 +- .../facets/LDA/DeployUniV3StyleFacet.s.sol | 6 +- .../facets/LDA/DeployVelodromeV2Facet.s.sol | 6 +- .../facets/LDA/utils/DeployLDAScriptBase.sol | 76 ------ .../deploy/facets/utils/DeployScriptBase.sol | 2 +- script/deploy/ldaHealthCheck.ts | 63 +++-- ...eployLiFiDEXAggregatorDiamond.zksync.s.sol | 39 +-- 22 files changed, 157 insertions(+), 444 deletions(-) delete mode 100644 script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 0b06f663f..4bd8cd2d5 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -28,7 +28,6 @@ module.exports = { 'no-empty-function': 'off', eqeqeq: ['error', 'always'], 'prefer-const': 'error', - curly: ['error', 'multi'], // Allow single-line statements without braces 'no-template-curly-in-string': 'error', // Warns about `${var}` in regular strings 'no-throw-literal': 'error', // Requires throwing Error objects instead of literals '@typescript-eslint/return-await': ['error', 'in-try-catch'], // More nuanced control over return await diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 20793f6fd..e9fd7c05e 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39834,31 +39834,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:25:02", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f", + "ADDRESS": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:46:49", + "TIMESTAMP": "2025-09-06 17:37:00", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -39941,31 +39926,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:29:44", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5", + "ADDRESS": "0x86bf822B011313e24332B7c2afae6dB55fF9A325", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:55:33", + "TIMESTAMP": "2025-09-06 18:08:28", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40018,31 +39988,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x098C5Fbb3A212650b903e42973A1381E6D00f585", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:29:03", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0x098C5Fbb3A212650b903e42973A1381E6D00f585", + "ADDRESS": "0x7f46986bCd79dCB121eeB692262303EB91b3dCD8", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:54:54", + "TIMESTAMP": "2025-09-06 18:02:47", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40095,31 +40050,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:27:49", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc", + "ADDRESS": "0xD076794b7731995135EAa9a55F10A4d2fFC4f431", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:54:14", + "TIMESTAMP": "2025-09-06 17:43:09", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40172,31 +40112,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:26:26", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3", + "ADDRESS": "0x373F947eecC311Ee1abd4C5278BaCf2d9275D286", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:53:32", + "TIMESTAMP": "2025-09-06 17:40:05", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40233,21 +40158,6 @@ } ] } - }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 23:49:44", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000d3f8e9697ecc837753ca29aa10c039ead8d1025d", - "SALT": "", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } } }, "AlgebraFacet": { @@ -40296,31 +40206,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:24:22", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25", + "ADDRESS": "0xa392B9e5f22e0a28a9De5C213D4CB627A97eE14e", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:46:12", + "TIMESTAMP": "2025-09-06 17:35:27", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40373,31 +40268,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:25:45", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803", + "ADDRESS": "0x8a1E84Af1c25b6b4f829e5f05559C76E481543E8", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:52:46", + "TIMESTAMP": "2025-09-06 17:38:32", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40450,31 +40330,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:27:17", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942", + "ADDRESS": "0xd96561d0bBb6E0D32Cc50C3e37F17B6831bF1798", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:53:53", + "TIMESTAMP": "2025-09-06 17:41:36", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40527,31 +40392,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:28:31", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833", + "ADDRESS": "0x067d334ee93DAC0A87b9e9191878C8e70F85E90f", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:54:35", + "TIMESTAMP": "2025-09-06 17:44:42", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40604,31 +40454,16 @@ ] } }, - "base": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:30:27", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ { - "ADDRESS": "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4", + "ADDRESS": "0x597E0b62Ae957452264A19D59c8C30818755D56c", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 14:55:56", + "TIMESTAMP": "2025-09-06 18:10:01", "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] diff --git a/deployments/base.staging.json b/deployments/base.staging.json index 7935b0026..e63b33a7a 100644 --- a/deployments/base.staging.json +++ b/deployments/base.staging.json @@ -16,16 +16,5 @@ "WithdrawFacet": "0x6C4f0E7Ef3564f6bc6FE5590B927dC053A005b04", "LiFiDiamond": "0x947330863B5BA5E134fE8b73e0E1c7Eed90446C7", "MayanFacet": "0xA77abE6A3E0Fe927d503c68fCF9B26e11A8bB1A4", - "GlacisFacet": "0xB4735037e3a6C40a47061A111Fd4E912c9b8e106", - "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", - "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", - "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", - "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", - "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", - "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", - "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374" + "GlacisFacet": "0xB4735037e3a6C40a47061A111Fd4E912c9b8e106" } \ No newline at end of file diff --git a/deployments/bsc.lda.diamond.staging.json b/deployments/bsc.lda.diamond.staging.json index 6170f4a91..2762de00f 100644 --- a/deployments/bsc.lda.diamond.staging.json +++ b/deployments/bsc.lda.diamond.staging.json @@ -13,43 +13,43 @@ "Name": "OwnershipFacet", "Version": "1.0.0" }, - "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25": { + "0xa392B9e5f22e0a28a9De5C213D4CB627A97eE14e": { "Name": "AlgebraFacet", "Version": "1.0.0" }, - "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f": { + "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240": { "Name": "CoreRouteFacet", "Version": "1.0.0" }, - "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803": { + "0x8a1E84Af1c25b6b4f829e5f05559C76E481543E8": { "Name": "CurveFacet", "Version": "1.0.0" }, - "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3": { + "0x373F947eecC311Ee1abd4C5278BaCf2d9275D286": { "Name": "IzumiV3Facet", "Version": "1.0.0" }, - "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942": { + "0xd96561d0bBb6E0D32Cc50C3e37F17B6831bF1798": { "Name": "KatanaV3Facet", "Version": "1.0.0" }, - "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc": { + "0xD076794b7731995135EAa9a55F10A4d2fFC4f431": { "Name": "NativeWrapperFacet", "Version": "1.0.0" }, - "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833": { + "0x067d334ee93DAC0A87b9e9191878C8e70F85E90f": { "Name": "SyncSwapV2Facet", "Version": "1.0.0" }, - "0x098C5Fbb3A212650b903e42973A1381E6D00f585": { + "0x7f46986bCd79dCB121eeB692262303EB91b3dCD8": { "Name": "UniV2StyleFacet", "Version": "1.0.0" }, - "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5": { + "0x86bf822B011313e24332B7c2afae6dB55fF9A325": { "Name": "UniV3StyleFacet", "Version": "1.0.0" }, - "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4": { + "0x597E0b62Ae957452264A19D59c8C30818755D56c": { "Name": "VelodromeV2Facet", "Version": "1.0.0" } diff --git a/deployments/bsc.staging.json b/deployments/bsc.staging.json index ba67a57af..de2108d9d 100644 --- a/deployments/bsc.staging.json +++ b/deployments/bsc.staging.json @@ -34,15 +34,15 @@ "StargateFacetV2": "0x089153117bffd37CBbE0c604dAE8e493D4743fA8", "LiFiDEXAggregator": "0x6140b987d6b51fd75b66c3b07733beb5167c42fc", "GasZipPeriphery": "0x46d8Aa20D5aD98927Cf885De9eBf9436E8E551c2", - "LiFiDEXAggregatorDiamond": "0x881CE612e3390d997Bb5f13749aF2Ee9B55DECF0", - "AlgebraFacet": "0x333363586Ff2dEa2a3791beFcc7D169D2AE13c25", - "CoreRouteFacet": "0x768da7f151a99E746Fbbb4d8D6785a2ee328972f", - "CurveFacet": "0x68B0996A57dC60BC6358e23a02e46D9Fd5Bf7803", - "IzumiV3Facet": "0xe23b098834bEA96e79fF1Ef6f52EC37c37709fd3", - "KatanaV3Facet": "0x7F276261b4e1Fe615694c0FaAeBf7CbE10B21942", - "NativeWrapperFacet": "0x52fC2aC27F6C4c532fE2B8D124c62D9103453ECc", - "SyncSwapV2Facet": "0xA7dC3f8e88C2468be1D5C9B3559dF845B1596833", - "UniV2StyleFacet": "0x098C5Fbb3A212650b903e42973A1381E6D00f585", - "UniV3StyleFacet": "0xA47F2C0b1fF018668C2D99DcF34e709A46078cC5", - "VelodromeV2Facet": "0x2dd5ea4b12A0D6fB689f0FD8B5287D4FF31B73d4" + "LiFiDEXAggregatorDiamond": "0x098C13F72aCd0e751eCb13eC9667ccA625911034", + "AlgebraFacet": "0xa392B9e5f22e0a28a9De5C213D4CB627A97eE14e", + "CoreRouteFacet": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", + "CurveFacet": "0x8a1E84Af1c25b6b4f829e5f05559C76E481543E8", + "IzumiV3Facet": "0x373F947eecC311Ee1abd4C5278BaCf2d9275D286", + "KatanaV3Facet": "0xd96561d0bBb6E0D32Cc50C3e37F17B6831bF1798", + "NativeWrapperFacet": "0xD076794b7731995135EAa9a55F10A4d2fFC4f431", + "SyncSwapV2Facet": "0x067d334ee93DAC0A87b9e9191878C8e70F85E90f", + "UniV2StyleFacet": "0x7f46986bCd79dCB121eeB692262303EB91b3dCD8", + "UniV3StyleFacet": "0x86bf822B011313e24332B7c2afae6dB55fF9A325", + "VelodromeV2Facet": "0x597E0b62Ae957452264A19D59c8C30818755D56c" } \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 73ee8cd3d..55b141727 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -254,7 +254,7 @@ deployAllLDAContracts() { echo "[info] Transferring LDA Diamond ownership to multisig: $SAFE_ADDRESS" - # Transfer ownership directly to multisig (not timelock) + # Transfer ownership directly to multisig cast send "$LDA_DIAMOND_ADDRESS" "transferOwnership(address)" "$SAFE_ADDRESS" --private-key "$(getPrivateKey "$NETWORK" "$ENVIRONMENT")" --rpc-url "$(getRPCUrl "$NETWORK")" --legacy if [ $? -eq 0 ]; then diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index e55fc3ad6..227e6528b 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -67,6 +67,13 @@ deploySingleContract() { FILE_EXTENSION=".s.sol" + # Determine deployment script directory based on network type and contract type + # We need to support 4 combinations: + # 1. Regular + Non-zkEVM = script/deploy/facets/ + # 2. Regular + zkEVM = script/deploy/zksync/ + # 3. LDA + Non-zkEVM = script/deploy/facets/LDA/ + # 4. LDA + zkEVM = script/deploy/zksync/LDA/ + # Helper function to check if contract is LDA-related isLDAContract() { local contract_name="$1" @@ -79,13 +86,6 @@ deploySingleContract() { return 1 # false fi } - - # Determine deployment script directory based on network type and contract type - # We need to support 4 combinations: - # 1. Regular + Non-zkEVM = script/deploy/facets/ - # 2. Regular + zkEVM = script/deploy/zksync/ - # 3. LDA + Non-zkEVM = script/deploy/facets/LDA/ - # 4. LDA + zkEVM = script/deploy/zksync/LDA/ if isZkEvmNetwork "$NETWORK"; then if isLDAContract "$CONTRACT"; then diff --git a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol index 2ee324755..96529ff29 100644 --- a/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol +++ b/script/deploy/facets/LDA/DeployAlgebraFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { AlgebraFacet } from "lifi/Periphery/LDA/Facets/AlgebraFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("AlgebraFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("AlgebraFacet") {} function run() public returns (AlgebraFacet deployed) { deployed = AlgebraFacet(deploy(type(AlgebraFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index 6ed262add..6aa115a57 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { stdJson } from "forge-std/Script.sol"; import { CoreRouteFacet } from "lifi/Periphery/LDA/Facets/CoreRouteFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { +contract DeployScript is DeployScriptBase { using stdJson for string; - constructor() DeployLDAScriptBase("CoreRouteFacet") {} + constructor() DeployScriptBase("CoreRouteFacet") {} function run() public diff --git a/script/deploy/facets/LDA/DeployCurveFacet.s.sol b/script/deploy/facets/LDA/DeployCurveFacet.s.sol index 9f723542a..3d4c674da 100644 --- a/script/deploy/facets/LDA/DeployCurveFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCurveFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { CurveFacet } from "lifi/Periphery/LDA/Facets/CurveFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("CurveFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("CurveFacet") {} function run() public returns (CurveFacet deployed) { deployed = CurveFacet(deploy(type(CurveFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol index 201f91926..79b86480b 100644 --- a/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol +++ b/script/deploy/facets/LDA/DeployIzumiV3Facet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { IzumiV3Facet } from "lifi/Periphery/LDA/Facets/IzumiV3Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("IzumiV3Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("IzumiV3Facet") {} function run() public returns (IzumiV3Facet deployed) { deployed = IzumiV3Facet(deploy(type(IzumiV3Facet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol b/script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol index 3eb0af671..8ab1003b6 100644 --- a/script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol +++ b/script/deploy/facets/LDA/DeployKatanaV3Facet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { KatanaV3Facet } from "lifi/Periphery/LDA/Facets/KatanaV3Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("KatanaV3Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("KatanaV3Facet") {} function run() public returns (KatanaV3Facet deployed) { deployed = KatanaV3Facet(deploy(type(KatanaV3Facet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol index 82c7e57b6..05095d784 100644 --- a/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol +++ b/script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol @@ -25,41 +25,19 @@ contract DeployScript is DeployScriptBase { function getConstructorArgs() internal override returns (bytes memory) { // LDA Diamond uses DiamondCutFacet from regular deployment (shared with regular LiFi Diamond) - // Construct path to regular deployment file: .json or ..json - - string memory regularPath; - - // Check if fileSuffix is provided (non-production environment) - if (bytes(fileSuffix).length > 0) { - // Non-production: network..json (e.g., network.staging.json, network.testnet.json) - regularPath = string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - } else { - // Production: network.json - regularPath = string.concat( - root, - "/deployments/", - network, - ".json" - ); - } - - emit log_named_string("regularPath", regularPath); - - // Get DiamondCutFacet address from regular deployment file + string memory path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); address diamondCut = _getConfigContractAddress( - regularPath, + path, ".DiamondCutFacet" ); - emit log_named_address("diamondCut", diamondCut); - return abi.encode(deployerAddress, diamondCut); } } diff --git a/script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol b/script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol index fe4a98a9e..901c20b27 100644 --- a/script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol +++ b/script/deploy/facets/LDA/DeployNativeWrapperFacet.s.sol @@ -1,13 +1,15 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { NativeWrapperFacet } from "lifi/Periphery/LDA/Facets/NativeWrapperFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("NativeWrapperFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("NativeWrapperFacet") {} function run() public returns (NativeWrapperFacet deployed) { - deployed = NativeWrapperFacet(deploy(type(NativeWrapperFacet).creationCode)); + deployed = NativeWrapperFacet( + deploy(type(NativeWrapperFacet).creationCode) + ); } } diff --git a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol index f5ba26cbd..45f252e84 100644 --- a/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeploySyncSwapV2Facet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { SyncSwapV2Facet } from "lifi/Periphery/LDA/Facets/SyncSwapV2Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("SyncSwapV2Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("SyncSwapV2Facet") {} function run() public returns (SyncSwapV2Facet deployed) { deployed = SyncSwapV2Facet(deploy(type(SyncSwapV2Facet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol index 88982d4ea..ccbccfc48 100644 --- a/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV2StyleFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { UniV2StyleFacet } from "lifi/Periphery/LDA/Facets/UniV2StyleFacet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("UniV2StyleFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("UniV2StyleFacet") {} function run() public returns (UniV2StyleFacet deployed) { deployed = UniV2StyleFacet(deploy(type(UniV2StyleFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol index fe5e99377..fc825770f 100644 --- a/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol +++ b/script/deploy/facets/LDA/DeployUniV3StyleFacet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; import { UniV3StyleFacet } from "lifi/Periphery/LDA/Facets/UniV3StyleFacet.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("UniV3StyleFacet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("UniV3StyleFacet") {} function run() public returns (UniV3StyleFacet deployed) { deployed = UniV3StyleFacet(deploy(type(UniV3StyleFacet).creationCode)); diff --git a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol index 82491b8d1..7acd360c6 100644 --- a/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol +++ b/script/deploy/facets/LDA/DeployVelodromeV2Facet.s.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; -import { DeployLDAScriptBase } from "./utils/DeployLDAScriptBase.sol"; import { VelodromeV2Facet } from "lifi/Periphery/LDA/Facets/VelodromeV2Facet.sol"; +import { DeployScriptBase } from "../utils/DeployScriptBase.sol"; -contract DeployScript is DeployLDAScriptBase { - constructor() DeployLDAScriptBase("VelodromeV2Facet") {} +contract DeployScript is DeployScriptBase { + constructor() DeployScriptBase("VelodromeV2Facet") {} function run() public returns (VelodromeV2Facet deployed) { deployed = VelodromeV2Facet( diff --git a/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol b/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol deleted file mode 100644 index 48ad7ef41..000000000 --- a/script/deploy/facets/LDA/utils/DeployLDAScriptBase.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { ScriptBase } from "../../utils/ScriptBase.sol"; -import { CREATE3Factory } from "create3-factory/CREATE3Factory.sol"; -import { LibAsset } from "lifi/Libraries/LibAsset.sol"; - -contract DeployLDAScriptBase is ScriptBase { - address internal predicted; - CREATE3Factory internal factory; - bytes32 internal salt; - - constructor(string memory contractName) { - address factoryAddress = vm.envAddress("CREATE3_FACTORY_ADDRESS"); - string memory saltPrefix = vm.envString("DEPLOYSALT"); - bool deployToDefaultLDADiamondAddress = vm.envOr( - "DEPLOY_TO_DEFAULT_LDA_DIAMOND_ADDRESS", - false - ); - - // Special handling for LDADiamond if default address deployment is enabled - // This allows for deterministic LDA diamond addresses similar to LiFi diamond - if ( - keccak256(abi.encodePacked(contractName)) == - keccak256(abi.encodePacked("LDADiamond")) && - deployToDefaultLDADiamondAddress - ) { - // Use a different salt for LDA diamond to avoid conflicts with LiFi diamond - salt = vm.envOr( - "DEFAULT_LDA_DIAMOND_ADDRESS_DEPLOYSALT", - keccak256(abi.encodePacked(saltPrefix, "LDA", contractName)) - ); - } else { - // For all other LDA contracts, use standard salt with LDA prefix to avoid conflicts - salt = keccak256( - abi.encodePacked(saltPrefix, "LDA", contractName) - ); - } - - factory = CREATE3Factory(factoryAddress); - predicted = factory.getDeployed(deployerAddress, salt); - } - - function getConstructorArgs() internal virtual returns (bytes memory) {} - - function deploy( - bytes memory creationCode - ) internal virtual returns (address payable deployed) { - bytes memory constructorArgs = getConstructorArgs(); - - vm.startBroadcast(deployerPrivateKey); - emit log_named_address("LI.FI LDA: Predicted Address: ", predicted); - - if (LibAsset.isContract(predicted)) { - emit log("LI.FI LDA: Contract is already deployed"); - return payable(predicted); - } - - // @DEV: activate on demand when deployment fails (e.g. to try manual deployment) - // reproduce and log calldata that is sent to CREATE3 - // bytes memory create3Calldata = abi.encodeWithSelector( - // CREATE3Factory.deploy.selector, - // salt, - // bytes.concat(creationCode, constructorArgs) - // ); - // emit log("LI.FI LDA: Will send this calldata to CREATE3Factory now: "); - // emit log_bytes(create3Calldata); - // emit log(" "); - - deployed = payable( - factory.deploy(salt, bytes.concat(creationCode, constructorArgs)) - ); - - vm.stopBroadcast(); - } -} diff --git a/script/deploy/facets/utils/DeployScriptBase.sol b/script/deploy/facets/utils/DeployScriptBase.sol index 47191423c..c1c2853be 100644 --- a/script/deploy/facets/utils/DeployScriptBase.sol +++ b/script/deploy/facets/utils/DeployScriptBase.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; import { ScriptBase } from "./ScriptBase.sol"; diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index 583649d80..f6cf89417 100755 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -5,6 +5,8 @@ import { execSync } from 'child_process' import { defineCommand, runMain } from 'citty' import { consola } from 'consola' +import type { SupportedChain } from '../common/types.js' + const errors: string[] = [] // Helper function to get RPC URL from networks.json @@ -322,9 +324,12 @@ const main = defineCommand({ logError(`LDA Non-Core Facet ${facet} not registered in Diamond`) else consola.success(`LDA Non-Core Facet ${facet} registered in Diamond`) - // Basic ownership check using cast + // ╭─────────────────────────────────────────────────────────╮ + // │ Check LDA Diamond ownership │ + // ╰─────────────────────────────────────────────────────────╯ + consola.box('Checking LDA Diamond ownership...') + try { - consola.box('Checking LDA Diamond ownership...') const owner = execSync( `cast call "${diamondAddress}" "owner() returns (address)" --rpc-url "${rpcUrl}"`, { encoding: 'utf8', stdio: 'pipe' } @@ -332,39 +337,41 @@ const main = defineCommand({ consola.info(`LiFiDEXAggregatorDiamond current owner: ${owner}`) - // Check if timelock is deployed and compare (timelock is in main deployments, not LDA deployments) - const timelockAddress = mainDeployedContracts['LiFiTimelockController'] - if (timelockAddress) { - consola.info(`Found LiFiTimelockController at: ${timelockAddress}`) - if (owner.toLowerCase() === timelockAddress.toLowerCase()) - consola.success( - 'LiFiDEXAggregatorDiamond is owned by LiFiTimelockController' - ) - else if (environment === 'production') - consola.error( - `LiFiDEXAggregatorDiamond owner is ${owner}, expected ${timelockAddress}` + // Get expected multisig address from networks.json + const expectedMultisigAddress = + networksConfig[network.toLowerCase() as SupportedChain]?.safeAddress + + if (!expectedMultisigAddress) { + if (environment === 'production') { + logError( + `No multisig address (safeAddress) found in networks.json for network ${network}` ) - else { + } else { consola.warn( - `LiFiDEXAggregatorDiamond owner is ${owner}, expected ${timelockAddress} for production` - ) - consola.info( - 'For staging environment, ownership transfer to timelock is typically done later' + `No multisig address (safeAddress) found in networks.json for network ${network}` ) + consola.info('For staging environment, this is acceptable') } } else { - if (environment === 'production') - consola.error( - 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' - ) - else - consola.warn( - 'LiFiTimelockController not found in main deployments, so LDA diamond ownership cannot be verified' - ) - consola.info( - 'Note: LiFiTimelockController should be deployed as shared infrastructure before LDA deployment' + `Expected multisig address from networks.json: ${expectedMultisigAddress}` ) + + if (owner.toLowerCase() === expectedMultisigAddress.toLowerCase()) { + consola.success( + `✅ LiFiDEXAggregatorDiamond is correctly owned by multisig: ${expectedMultisigAddress}` + ) + } else { + if (environment === 'production') { + logError( + `❌ LiFiDEXAggregatorDiamond ownership mismatch! Current owner: ${owner}, Expected multisig: ${expectedMultisigAddress}` + ) + } else { + consola.info( + 'For staging environment, ownership transfer to multisig is not done' + ) + } + } } } catch (error) { logError( diff --git a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol index 338a2591d..cd992b664 100644 --- a/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployLiFiDEXAggregatorDiamond.zksync.s.sol @@ -25,37 +25,16 @@ contract DeployScript is DeployScriptBase { function getConstructorArgs() internal override returns (bytes memory) { // LDA Diamond uses DiamondCutFacet from regular deployment (shared with regular LiFi Diamond) - // Construct path to regular deployment file: .json or ..json - - string memory regularPath; - - // Check if fileSuffix is provided (non-production environment) - if (bytes(fileSuffix).length > 0) { - // Non-production: network..json (e.g., network.staging.json, network.testnet.json) - // Note: fileSuffix already includes trailing dot (e.g., "staging.") - regularPath = string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - } else { - // Production: network.json - regularPath = string.concat( - root, - "/deployments/", - network, - ".json" - ); - } - - emit log_named_string("regularPath", regularPath); - - // Get DiamondCutFacet address from regular deployment file + string memory path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); address diamondCut = _getConfigContractAddress( - regularPath, + path, ".DiamondCutFacet" ); From 05ad30963a70e2aaf506c1ab3e7f0855ebdbfc58 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sat, 6 Sep 2025 18:38:30 +0200 Subject: [PATCH 172/220] changes in deploy logs --- deployments/_deployments_log_file.json | 194 +++---------------------- deployments/arbitrum.staging.json | 2 +- 2 files changed, 23 insertions(+), 173 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index e9fd7c05e..cb41591cc 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39823,12 +39823,12 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "ADDRESS": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:02:46", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f", - "SALT": "", - "VERIFIED": "false", + "TIMESTAMP": "2025-09-06 18:31:22", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345125", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -39858,26 +39858,11 @@ "TIMESTAMP": "2025-09-06 10:18:57", "CONSTRUCTOR_ARGS": "0x", "SALT": "12345680", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } - }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x73988E03aA07b41C71DfF1E148405Cb7631655E6", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:21:20", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } } }, "UniV3StyleFacet": { @@ -39905,27 +39890,12 @@ "TIMESTAMP": "2025-09-04 17:13:26", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x2999f682a6e005DbA4b69e3733D49C9De27acC42", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:46:45", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -39967,27 +39937,12 @@ "TIMESTAMP": "2025-09-04 17:13:01", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xEe014D9B6AF188909E6cE88c37D19bA703799F39", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:39:36", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -40014,7 +39969,7 @@ "TIMESTAMP": "2025-09-04 14:26:09", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40029,27 +39984,12 @@ "TIMESTAMP": "2025-09-04 17:12:11", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xc9d130cA48f858eC429bd4E0c56A44528D54e78f", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:32:26", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -40076,7 +40016,7 @@ "TIMESTAMP": "2025-09-04 14:05:55", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40091,27 +40031,12 @@ "TIMESTAMP": "2025-09-04 17:11:22", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xCA5C3AE030173A446e1C2443BA6EB6c3B478d47D", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:27:19", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -40170,7 +40095,7 @@ "TIMESTAMP": "2025-09-04 14:02:26", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40185,27 +40110,12 @@ "TIMESTAMP": "2025-09-04 17:10:11", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xbfd10Ec90a8C4f3e2075812c3F5B402d55E6302c", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:18:48", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -40232,7 +40142,7 @@ "TIMESTAMP": "2025-09-04 14:05:34", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40247,27 +40157,12 @@ "TIMESTAMP": "2025-09-04 17:10:59", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xb2a55d3b13277A86dD5Cd32D03CbcDF98B20576f", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:24:43", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -40294,7 +40189,7 @@ "TIMESTAMP": "2025-09-04 14:08:38", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40309,27 +40204,12 @@ "TIMESTAMP": "2025-09-04 17:11:45", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xB3DbFeD58830eD2493c533248942C01006097bdC", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:29:53", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -40356,7 +40236,7 @@ "TIMESTAMP": "2025-09-04 14:26:30", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40371,27 +40251,12 @@ "TIMESTAMP": "2025-09-04 17:12:36", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x6A5C8b4f932e7Ab7ADAaE1fB201c601B4585aC93", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:35:43", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ @@ -40418,7 +40283,7 @@ "TIMESTAMP": "2025-09-04 14:27:34", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40433,27 +40298,12 @@ "TIMESTAMP": "2025-09-04 17:13:53", "CONSTRUCTOR_ARGS": "0x", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] } }, - "zksync": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x5f9Bbf7BE25927567822Debf0385d8a5Db2e2473", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 12:50:10", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] - } - }, "bsc": { "staging": { "1.0.0": [ diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 37216a6e1..77992f683 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -61,7 +61,7 @@ "RelayDepositoryFacet": "0x004E291b9244C811B0BE00cA2C179d54FAA5073D", "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef", + "CoreRouteFacet": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", From 35241848ede9eb0f55ec024e56da365a8d1eed92 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sat, 6 Sep 2025 19:38:18 +0200 Subject: [PATCH 173/220] changes and new deployemnt --- deployments/_deployments_log_file.json | 86 +++++++------------ deployments/bsc.lda.diamond.staging.json | 20 ++--- deployments/bsc.staging.json | 22 ++--- script/deploy/deployAllLDAContracts.sh | 23 ++--- script/deploy/deployFacetAndAddToDiamond.sh | 13 +-- .../facets/LDA/DeployCoreRouteFacet.s.sol | 7 +- .../facets/LDA/UpdateLDACoreFacets.s.sol | 52 +---------- .../LDA/DeployCoreRouteFacet.zksync.s.sol | 7 +- .../LDA/UpdateLDACoreFacets.zksync.s.sol | 82 +----------------- 9 files changed, 75 insertions(+), 237 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index cb41591cc..d5c145f9a 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -1537,19 +1537,6 @@ "VERIFIED": "true" } ] - }, - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xEF948675ABD792E38b4caCA7334A480A9257C8d2", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 13:02:19", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] } }, "base": { @@ -2608,19 +2595,6 @@ "VERIFIED": "true" } ] - }, - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xa5b5CC96a7a20c37fd7665984063FEc0eE4823d7", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 13:06:18", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] } }, "base": { @@ -39838,11 +39812,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", + "ADDRESS": "0x07497D2C0B32537B0162dc35a4794F4B089a53Aa", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 17:37:00", + "TIMESTAMP": "2025-09-06 19:20:08", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -39900,11 +39874,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x86bf822B011313e24332B7c2afae6dB55fF9A325", + "ADDRESS": "0x2AA70A1115F9bA6008bD04511AAF7d81E5251d21", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 18:08:28", + "TIMESTAMP": "2025-09-06 19:30:56", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -39947,11 +39921,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x7f46986bCd79dCB121eeB692262303EB91b3dCD8", + "ADDRESS": "0x8ee5946F6d0818557cc49A862470D89A34b986d0", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 18:02:47", + "TIMESTAMP": "2025-09-06 19:29:24", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -39994,11 +39968,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xD076794b7731995135EAa9a55F10A4d2fFC4f431", + "ADDRESS": "0xd13d6DF1ef271dC887Df5499e4059e1AeC42Ea11", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 17:43:09", + "TIMESTAMP": "2025-09-06 19:26:17", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40041,11 +40015,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x373F947eecC311Ee1abd4C5278BaCf2d9275D286", + "ADDRESS": "0xC1D230b7De7Eba42E9f4d0F20512193875Ff6F26", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 17:40:05", + "TIMESTAMP": "2025-09-06 19:23:13", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40120,11 +40094,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xa392B9e5f22e0a28a9De5C213D4CB627A97eE14e", + "ADDRESS": "0x5363c2db9eB0e9080574915b4C486B9851BA4326", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 17:35:27", + "TIMESTAMP": "2025-09-06 19:18:35", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40167,11 +40141,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x8a1E84Af1c25b6b4f829e5f05559C76E481543E8", + "ADDRESS": "0x7B383eD28261835e63D2aed3b4A415B29438354B", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 17:38:32", + "TIMESTAMP": "2025-09-06 19:21:41", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40214,11 +40188,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xd96561d0bBb6E0D32Cc50C3e37F17B6831bF1798", + "ADDRESS": "0x2a528a7dDa49D45f9B766e131460a36e060861B2", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 17:41:36", + "TIMESTAMP": "2025-09-06 19:24:46", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40261,11 +40235,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x067d334ee93DAC0A87b9e9191878C8e70F85E90f", + "ADDRESS": "0xdFc21dcEc5220bfaB06D865312991F6058f5dfC6", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 17:44:42", + "TIMESTAMP": "2025-09-06 19:27:50", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40308,11 +40282,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x597E0b62Ae957452264A19D59c8C30818755D56c", + "ADDRESS": "0x8CFB30c9A05357DAd207600A577f5251Fa7bCeab", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 18:10:01", + "TIMESTAMP": "2025-09-06 19:32:31", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345121", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } diff --git a/deployments/bsc.lda.diamond.staging.json b/deployments/bsc.lda.diamond.staging.json index 2762de00f..f4cc3aeb5 100644 --- a/deployments/bsc.lda.diamond.staging.json +++ b/deployments/bsc.lda.diamond.staging.json @@ -13,43 +13,43 @@ "Name": "OwnershipFacet", "Version": "1.0.0" }, - "0xa392B9e5f22e0a28a9De5C213D4CB627A97eE14e": { + "0x5363c2db9eB0e9080574915b4C486B9851BA4326": { "Name": "AlgebraFacet", "Version": "1.0.0" }, - "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240": { + "0x07497D2C0B32537B0162dc35a4794F4B089a53Aa": { "Name": "CoreRouteFacet", "Version": "1.0.0" }, - "0x8a1E84Af1c25b6b4f829e5f05559C76E481543E8": { + "0x7B383eD28261835e63D2aed3b4A415B29438354B": { "Name": "CurveFacet", "Version": "1.0.0" }, - "0x373F947eecC311Ee1abd4C5278BaCf2d9275D286": { + "0xC1D230b7De7Eba42E9f4d0F20512193875Ff6F26": { "Name": "IzumiV3Facet", "Version": "1.0.0" }, - "0xd96561d0bBb6E0D32Cc50C3e37F17B6831bF1798": { + "0x2a528a7dDa49D45f9B766e131460a36e060861B2": { "Name": "KatanaV3Facet", "Version": "1.0.0" }, - "0xD076794b7731995135EAa9a55F10A4d2fFC4f431": { + "0xd13d6DF1ef271dC887Df5499e4059e1AeC42Ea11": { "Name": "NativeWrapperFacet", "Version": "1.0.0" }, - "0x067d334ee93DAC0A87b9e9191878C8e70F85E90f": { + "0xdFc21dcEc5220bfaB06D865312991F6058f5dfC6": { "Name": "SyncSwapV2Facet", "Version": "1.0.0" }, - "0x7f46986bCd79dCB121eeB692262303EB91b3dCD8": { + "0x8ee5946F6d0818557cc49A862470D89A34b986d0": { "Name": "UniV2StyleFacet", "Version": "1.0.0" }, - "0x86bf822B011313e24332B7c2afae6dB55fF9A325": { + "0x2AA70A1115F9bA6008bD04511AAF7d81E5251d21": { "Name": "UniV3StyleFacet", "Version": "1.0.0" }, - "0x597E0b62Ae957452264A19D59c8C30818755D56c": { + "0x8CFB30c9A05357DAd207600A577f5251Fa7bCeab": { "Name": "VelodromeV2Facet", "Version": "1.0.0" } diff --git a/deployments/bsc.staging.json b/deployments/bsc.staging.json index de2108d9d..45029f3b5 100644 --- a/deployments/bsc.staging.json +++ b/deployments/bsc.staging.json @@ -34,15 +34,15 @@ "StargateFacetV2": "0x089153117bffd37CBbE0c604dAE8e493D4743fA8", "LiFiDEXAggregator": "0x6140b987d6b51fd75b66c3b07733beb5167c42fc", "GasZipPeriphery": "0x46d8Aa20D5aD98927Cf885De9eBf9436E8E551c2", - "LiFiDEXAggregatorDiamond": "0x098C13F72aCd0e751eCb13eC9667ccA625911034", - "AlgebraFacet": "0xa392B9e5f22e0a28a9De5C213D4CB627A97eE14e", - "CoreRouteFacet": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", - "CurveFacet": "0x8a1E84Af1c25b6b4f829e5f05559C76E481543E8", - "IzumiV3Facet": "0x373F947eecC311Ee1abd4C5278BaCf2d9275D286", - "KatanaV3Facet": "0xd96561d0bBb6E0D32Cc50C3e37F17B6831bF1798", - "NativeWrapperFacet": "0xD076794b7731995135EAa9a55F10A4d2fFC4f431", - "SyncSwapV2Facet": "0x067d334ee93DAC0A87b9e9191878C8e70F85E90f", - "UniV2StyleFacet": "0x7f46986bCd79dCB121eeB692262303EB91b3dCD8", - "UniV3StyleFacet": "0x86bf822B011313e24332B7c2afae6dB55fF9A325", - "VelodromeV2Facet": "0x597E0b62Ae957452264A19D59c8C30818755D56c" + "LiFiDEXAggregatorDiamond": "0x9257FA485C5714975247ca5C47A2cB0c4FA5A773", + "AlgebraFacet": "0x5363c2db9eB0e9080574915b4C486B9851BA4326", + "CoreRouteFacet": "0x07497D2C0B32537B0162dc35a4794F4B089a53Aa", + "CurveFacet": "0x7B383eD28261835e63D2aed3b4A415B29438354B", + "IzumiV3Facet": "0xC1D230b7De7Eba42E9f4d0F20512193875Ff6F26", + "KatanaV3Facet": "0x2a528a7dDa49D45f9B766e131460a36e060861B2", + "NativeWrapperFacet": "0xd13d6DF1ef271dC887Df5499e4059e1AeC42Ea11", + "SyncSwapV2Facet": "0xdFc21dcEc5220bfaB06D865312991F6058f5dfC6", + "UniV2StyleFacet": "0x8ee5946F6d0818557cc49A862470D89A34b986d0", + "UniV3StyleFacet": "0x2AA70A1115F9bA6008bD04511AAF7d81E5251d21", + "VelodromeV2Facet": "0x8CFB30c9A05357DAd207600A577f5251Fa7bCeab" } \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 55b141727..5373591c2 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -1,6 +1,14 @@ #!/bin/bash -# Simple LDA deployment script - deploys LDA diamond and all facets in one go +# LiFi DEX Aggregator (LDA) full deployment script +# - checks prerequisites of core facets (defined in ldaCoreFacets in global.json) +# - deploys LDA diamond contract +# - adds core facets to LDA diamond +# - deploys and adds LDA-specific facets +# - creates LDA deployment logs +# - runs health check +# - transfers ownership to multisig (production only) + deployAllLDAContracts() { echo "[info] =====================================================================" echo "[info] Starting LiFi DEX Aggregator (LDA) Diamond deployment" @@ -101,6 +109,9 @@ deployAllLDAContracts() { # get current LDA diamond contract version local VERSION=$(getCurrentContractVersion "$LDA_DIAMOND_CONTRACT_NAME") + local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") + local SALT_INPUT="$BYTECODE""$SALT" + local DEPLOYSALT=$(cast keccak "$SALT_INPUT") # Determine the correct deploy script path based on network type if isZkEvmNetwork "$NETWORK"; then @@ -112,13 +123,6 @@ deployAllLDAContracts() { FOUNDRY_PROFILE=zksync ./foundry-zksync/forge build --zksync checkFailure $? "compile contracts with ZkSync compiler" - # Get required deployment variables for ZkSync deployment - local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") - local SALT_INPUT="$BYTECODE""$SALT" - local DEPLOYSALT=$(cast keccak "$SALT_INPUT") - - # For ZkSync networks, pass the regular FILE_SUFFIX so the script can construct the correct path to read DiamondCutFacet - # The ZkSync script will use this to read from the regular deployment file (e.g., zksync.staging.json) # For ZkSync networks, use ZkSync-specific deployment with DEPLOYSALT and regular file suffix local RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync DEPLOYSALT=$DEPLOYSALT NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT=$DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS=$DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") DIAMOND_TYPE=$DIAMOND_TYPE ./foundry-zksync/forge script "$DEPLOY_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --skip-simulation --slow --zksync --gas-estimate-multiplier "${GAS_ESTIMATE_MULTIPLIER:-130}") @@ -126,10 +130,7 @@ deployAllLDAContracts() { local DEPLOY_SCRIPT_PATH="script/deploy/facets/LDA/DeployLiFiDEXAggregatorDiamond.s.sol" # Get required deployment variables for CREATE3 deployment - local BYTECODE=$(getBytecodeFromArtifact "$LDA_DIAMOND_CONTRACT_NAME") local CREATE3_FACTORY_ADDRESS=$(getCreate3FactoryAddress "$NETWORK") - local SALT_INPUT="$BYTECODE""$SALT" - local DEPLOYSALT=$(cast keccak "$SALT_INPUT") echo "[info] Deploying $LDA_DIAMOND_CONTRACT_NAME using CREATE3 factory..." # Deploy the LDA diamond using CREATE3 factory diff --git a/script/deploy/deployFacetAndAddToDiamond.sh b/script/deploy/deployFacetAndAddToDiamond.sh index 81222e43f..797ec7995 100755 --- a/script/deploy/deployFacetAndAddToDiamond.sh +++ b/script/deploy/deployFacetAndAddToDiamond.sh @@ -76,17 +76,8 @@ function deployFacetAndAddToDiamond() { # get diamond address from deployments script (handle both regular and LDA diamonds) local DIAMOND_ADDRESS - local DEPLOYMENT_FILE - - if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then - # For LDA diamond, look in regular deployment file (not .lda.diamond.json) - DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" - DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$DEPLOYMENT_FILE") - else - # For regular diamonds, use regular deployment file - DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" - DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$DEPLOYMENT_FILE") - fi + local DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" + local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$DEPLOYMENT_FILE") # if no diamond address was found, throw an error and exit this script if [[ "$DIAMOND_ADDRESS" == "null" ]]; then diff --git a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol index 6aa115a57..52d89c99e 100644 --- a/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol +++ b/script/deploy/facets/LDA/DeployCoreRouteFacet.s.sol @@ -10,12 +10,7 @@ contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CoreRouteFacet") {} - function run() - public - returns (CoreRouteFacet deployed, bytes memory constructorArgs) - { - constructorArgs = getConstructorArgs(); - + function run() public returns (CoreRouteFacet deployed) { deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); } } diff --git a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol index e56b46608..68fdc08f3 100644 --- a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol +++ b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol @@ -8,44 +8,6 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; contract UpdateLDACoreFacets is UpdateLDAScriptBase { using stdJson for string; - /// @notice Get regular deployment file path (without lda. prefix) - function getRegularDeploymentPath() internal view returns (string memory) { - // Need to construct regular deployment path by removing "lda." prefix from fileSuffix - string memory regularFileSuffix; - bytes memory fileSuffixBytes = bytes(fileSuffix); - - // Check if fileSuffix starts with "lda." and remove it - if ( - fileSuffixBytes.length >= 4 && - fileSuffixBytes[0] == "l" && - fileSuffixBytes[1] == "d" && - fileSuffixBytes[2] == "a" && - fileSuffixBytes[3] == "." - ) { - // Extract everything after "lda." by creating new bytes array - bytes memory remainingBytes = new bytes( - fileSuffixBytes.length - 4 - ); - for (uint256 i = 4; i < fileSuffixBytes.length; i++) { - remainingBytes[i - 4] = fileSuffixBytes[i]; - } - regularFileSuffix = string(remainingBytes); - } else { - // If no "lda." prefix, use as is - regularFileSuffix = fileSuffix; - } - - return - string.concat( - root, - "/deployments/", - network, - ".", - regularFileSuffix, - "json" - ); - } - function run() public returns (address[] memory facets, bytes memory cutData) @@ -63,13 +25,6 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log("LDA core facets found in config/global.json: "); emit log_uint(ldaCoreFacets.length); - // Get regular deployment path for reading core facets - string memory regularDeploymentPath = getRegularDeploymentPath(); - emit log_named_string( - "Reading core facets from regular deployment file", - regularDeploymentPath - ); - bytes4[] memory exclude; // Check if the LDA loupe was already added to the diamond @@ -87,7 +42,7 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log("DiamondLoupeFacet does not exist on diamond yet"); // Read DiamondLoupeFacet from regular deployment file address ldaDiamondLoupeAddress = _getConfigContractAddress( - regularDeploymentPath, + path, ".DiamondLoupeFacet" ); bytes4[] memory loupeSelectors = getSelectors( @@ -129,12 +84,13 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log(facetName); // Read core facets from regular deployment file, not LDA file address facetAddress = _getConfigContractAddress( - regularDeploymentPath, + path, string.concat(".", facetName) ); + bytes4[] memory selectors = getSelectors(facetName, exclude); - // at this point we know for sure that LDA diamond loupe exists on diamond + // at this point we know for sure that diamond loupe exists on diamond buildDiamondCut(selectors, facetAddress); } diff --git a/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol index 6aa115a57..52d89c99e 100644 --- a/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol +++ b/script/deploy/zksync/LDA/DeployCoreRouteFacet.zksync.s.sol @@ -10,12 +10,7 @@ contract DeployScript is DeployScriptBase { constructor() DeployScriptBase("CoreRouteFacet") {} - function run() - public - returns (CoreRouteFacet deployed, bytes memory constructorArgs) - { - constructorArgs = getConstructorArgs(); - + function run() public returns (CoreRouteFacet deployed) { deployed = CoreRouteFacet(deploy(type(CoreRouteFacet).creationCode)); } } diff --git a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol index ac1fa8e54..a9a1477b7 100644 --- a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol +++ b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol @@ -8,50 +8,10 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; contract UpdateLDACoreFacets is UpdateLDAScriptBase { using stdJson for string; - /// @notice Get regular deployment file path (without lda. prefix) - function getRegularDeploymentPath() internal view returns (string memory) { - // Need to construct regular deployment path by removing "lda." prefix from fileSuffix - string memory regularFileSuffix; - bytes memory fileSuffixBytes = bytes(fileSuffix); - - // Check if fileSuffix starts with "lda." and remove it - if ( - fileSuffixBytes.length >= 4 && - fileSuffixBytes[0] == "l" && - fileSuffixBytes[1] == "d" && - fileSuffixBytes[2] == "a" && - fileSuffixBytes[3] == "." - ) { - // Extract everything after "lda." by creating new bytes array - bytes memory remainingBytes = new bytes( - fileSuffixBytes.length - 4 - ); - for (uint256 i = 4; i < fileSuffixBytes.length; i++) { - remainingBytes[i - 4] = fileSuffixBytes[i]; - } - regularFileSuffix = string(remainingBytes); - } else { - // If no "lda." prefix, use as is - regularFileSuffix = fileSuffix; - } - - return - string.concat( - root, - "/deployments/", - network, - ".", - regularFileSuffix, - "json" - ); - } - function run() public returns (address[] memory facets, bytes memory cutData) { - emit log("=== STARTING UpdateLDACoreFacets ==="); - // Read LDA core facets dynamically from global.json config string memory ldaGlobalConfigPath = string.concat( vm.projectRoot(), @@ -65,67 +25,43 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { emit log("LDA core facets found in config/global.json: "); emit log_uint(ldaCoreFacets.length); - // For zkSync, read core facets from the zkSync deployment file (same as regular UpdateCoreFacets) - emit log_named_string( - "Reading core facets from zkSync deployment file", - path - ); - bytes4[] memory exclude; + // Check if the LDA loupe was already added to the diamond bool loupeExists; - - emit log("=== CHECKING IF LOUPE EXISTS ==="); - try loupe.facetAddresses() returns (address[] memory existingFacets) { + try loupe.facetAddresses() returns (address[] memory) { // If call was successful, loupe exists on LDA diamond already emit log("DiamondLoupeFacet exists on diamond already"); - emit log_uint(existingFacets.length); loupeExists = true; - } catch Error(string memory reason) { - emit log("DiamondLoupeFacet check failed with reason:"); - emit log(reason); - } catch (bytes memory lowLevelData) { - emit log("DiamondLoupeFacet check failed with low level data:"); - emit log_bytes(lowLevelData); + } catch { + // No need to do anything, just making sure that the flow continues in both cases with try/catch } // Handle DiamondLoupeFacet separately as it needs special treatment if (!loupeExists) { - emit log("=== ADDING DIAMONDLOUPE FACET ==="); emit log("DiamondLoupeFacet does not exist on diamond yet"); // Read DiamondLoupeFacet from zkSync deployment file (same as regular script) address ldaDiamondLoupeAddress = _getConfigContractAddress( path, ".DiamondLoupeFacet" ); - emit log_named_address( - "DiamondLoupeFacet address", - ldaDiamondLoupeAddress - ); bytes4[] memory loupeSelectors = getSelectors( "DiamondLoupeFacet", exclude ); - emit log("DiamondLoupeFacet selectors:"); - for (uint256 i = 0; i < loupeSelectors.length; i++) { - emit log_bytes32(loupeSelectors[i]); - } buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); - emit log("=== EXECUTING DIAMONDCUT FOR LOUPE ==="); vm.startBroadcast(deployerPrivateKey); if (cut.length > 0) { cutter.diamondCut(cut, address(0), ""); } vm.stopBroadcast(); - emit log("=== DIAMONDCUT FOR LOUPE COMPLETED ==="); // Reset diamond cut variable to remove LDA diamondLoupe information delete cut; } - emit log("=== PROCESSING OTHER CORE FACETS ==="); // Process all LDA core facets dynamically for (uint256 i = 0; i < ldaCoreFacets.length; i++) { string memory facetName = ldaCoreFacets[i]; @@ -135,7 +71,6 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { keccak256(bytes(facetName)) == keccak256(bytes("DiamondLoupeFacet")) ) { - emit log("Skipping DiamondLoupeFacet"); continue; } // Skip DiamondCutFacet as it was already handled during LDA diamond deployment @@ -143,7 +78,6 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { keccak256(bytes(facetName)) == keccak256(bytes("DiamondCutFacet")) ) { - emit log("Skipping DiamondCutFacet"); continue; } @@ -154,13 +88,8 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { path, string.concat(".", facetName) ); - emit log_named_address("Facet address", facetAddress); bytes4[] memory selectors = getSelectors(facetName, exclude); - emit log("Facet selectors:"); - for (uint256 j = 0; j < selectors.length; j++) { - emit log_bytes32(selectors[j]); - } // at this point we know for sure that diamond loupe exists on diamond buildDiamondCut(selectors, facetAddress); @@ -182,15 +111,12 @@ contract UpdateLDACoreFacets is UpdateLDAScriptBase { return (facets, cutData); } - emit log("=== EXECUTING FINAL DIAMONDCUT ==="); vm.startBroadcast(deployerPrivateKey); if (cut.length > 0) { cutter.diamondCut(cut, address(0), ""); } vm.stopBroadcast(); - emit log("=== FINAL DIAMONDCUT COMPLETED ==="); facets = loupe.facetAddresses(); - emit log("=== UpdateLDACoreFacets COMPLETED ==="); } } From b67966b41992902d55a65a971006a307349b08cf Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sun, 7 Sep 2025 11:42:35 +0200 Subject: [PATCH 174/220] updates --- deployments/_deployments_log_file.json | 32 ++-- deployments/zksync.staging.json | 5 - script/deploy/deployAllLDAContracts.sh | 17 +- script/deploy/deployFacetAndAddToDiamond.sh | 5 +- script/deploy/deploySingleContract.sh | 5 +- .../facets/LDA/UpdateLDACoreFacets.s.sol | 110 +------------ script/deploy/facets/UpdateCoreFacets.s.sol | 117 +------------- .../deploy/facets/utils/BaseUpdateScript.sol | 137 ++++++++++++++++ script/deploy/ldaHealthCheck.ts | 6 + .../LDA/UpdateLDACoreFacets.zksync.s.sol | 111 +------------ .../zksync/LDA/utils/UpdateLDAScriptBase.sol | 4 +- .../zksync/UpdateCoreFacets.zksync.s.sol | 117 +------------- ...cUpdateScript.sol => BaseUpdateScript.sol} | 139 +++++++++++++++- .../deploy/zksync/utils/UpdateScriptBase.sol | 4 +- script/helperFunctions.sh | 148 +----------------- script/tasks/ldaDiamondUpdateFacet.sh | 21 --- 16 files changed, 323 insertions(+), 655 deletions(-) delete mode 100644 deployments/zksync.staging.json rename script/deploy/zksync/utils/{BaseZkSyncUpdateScript.sol => BaseUpdateScript.sol} (56%) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index d5c145f9a..3d6ccf205 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -479,19 +479,6 @@ "VERIFIED": "true" } ] - }, - "staging": { - "1.0.0": [ - { - "ADDRESS": "0xA7F7479222659c38E60981aEe4b48080f367C2f8", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 13:01:01", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "12345123", - "VERIFIED": "false", - "ZK_SOLC_VERSION": "1.5.15" - } - ] } }, "base": { @@ -40037,7 +40024,7 @@ "TIMESTAMP": "2025-09-04 21:54:15", "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000b2a8517734cdf985d53f303a1f7759a34fdc772f", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] @@ -40052,7 +40039,22 @@ "TIMESTAMP": "2025-09-04 17:09:40", "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62000000000000000000000000b2a8517734cdf985d53f303a1f7759a34fdc772f", "SALT": "", - "VERIFIED": "false", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } + }, + "bsc": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9257FA485C5714975247ca5C47A2cB0c4FA5A773", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-06 19:18:35", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345121", + "VERIFIED": "true", "ZK_SOLC_VERSION": "" } ] diff --git a/deployments/zksync.staging.json b/deployments/zksync.staging.json deleted file mode 100644 index a314a060b..000000000 --- a/deployments/zksync.staging.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "DiamondCutFacet": "0xd6731f2729e24F3eAeDe8d10aFBd908460c021c5", - "DiamondLoupeFacet": "0xB03d1c11529d248727aE1fE1570f8Fd664968e31", - "OwnershipFacet": "0x1773bB176Ee1A7774bDeaf8fff92991bC005A56A" -} \ No newline at end of file diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 5373591c2..372dd1a55 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -155,7 +155,7 @@ deployAllLDAContracts() { echo "" echo "[info] STEP 3: Adding core facets to LDA Diamond..." - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" false + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" if [ $? -ne 0 ]; then error "❌ Failed to add core facets to LDA Diamond" @@ -174,21 +174,10 @@ deployAllLDAContracts() { local LDA_FACETS_PATH="src/Periphery/LDA/Facets/" echo "[info] Getting LDA facets from directory: $LDA_FACETS_PATH" - # prepare regExp to exclude LDA core facets (they're already added) - local EXCLUDED_LDA_FACETS_REGEXP="^($(echo "${LDA_CORE_FACETS[@]}" | tr ' ' '|'))$" - - echo "[info] Excluding core facets: ${LDA_CORE_FACETS[*]}" - # Deploy all non-core LDA facets and add to diamond for FACET_NAME in $(getContractNamesInFolder "$LDA_FACETS_PATH"); do echo "[info] Processing LDA facet: $FACET_NAME" - # Skip if this is a core facet (already handled) - if [[ "$FACET_NAME" =~ $EXCLUDED_LDA_FACETS_REGEXP ]]; then - echo "[info] Skipping core facet: $FACET_NAME (already added)" - continue - fi - echo "[info] Deploying and adding LDA facet: $FACET_NAME" # get current contract version @@ -214,8 +203,8 @@ deployAllLDAContracts() { echo "" echo "[info] STEP 5: Creating LDA diamond deployment logs..." - # Update LDA diamond logs to create/populate the .lda.diamond.json file - updateLDADiamondLogs "$ENVIRONMENT" "$NETWORK" + # Update LDA diamond logs to create/populate the .lda.diamond.json file + updateDiamondLogs "$ENVIRONMENT" "$NETWORK" "LiFiDEXAggregatorDiamond" if [ $? -ne 0 ]; then error "❌ Failed to update LDA diamond logs" diff --git a/script/deploy/deployFacetAndAddToDiamond.sh b/script/deploy/deployFacetAndAddToDiamond.sh index 797ec7995..884f27f3b 100755 --- a/script/deploy/deployFacetAndAddToDiamond.sh +++ b/script/deploy/deployFacetAndAddToDiamond.sh @@ -75,7 +75,6 @@ function deployFacetAndAddToDiamond() { fi # get diamond address from deployments script (handle both regular and LDA diamonds) - local DIAMOND_ADDRESS local DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$DEPLOYMENT_FILE") @@ -102,7 +101,7 @@ function deployFacetAndAddToDiamond() { echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> deploying $FACET_CONTRACT_NAME for $DIAMOND_CONTRACT_NAME now...." - # deploy facet (deploySingleContract will auto-detect if it's LDA based on contract name) + # deploy facet deploySingleContract "$FACET_CONTRACT_NAME" "$NETWORK" "$ENVIRONMENT" "$VERSION" false # check if function call was successful @@ -118,7 +117,7 @@ function deployFacetAndAddToDiamond() { # update diamond (use appropriate function based on diamond type) if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then # Use LDA-specific update function - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" true + ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" if [ $? -ne 0 ]; then warning "this call was not successful: ldaDiamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index 227e6528b..358875012 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -67,8 +67,9 @@ deploySingleContract() { FILE_EXTENSION=".s.sol" - # Determine deployment script directory based on network type and contract type - # We need to support 4 combinations: + # Handle ZkEVM Chains + # We need to use zksync specific scripts that are able to be compiled for the zkvm. + # Supports 4 combinations: # 1. Regular + Non-zkEVM = script/deploy/facets/ # 2. Regular + zkEVM = script/deploy/zksync/ # 3. LDA + Non-zkEVM = script/deploy/facets/LDA/ diff --git a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol index 68fdc08f3..2fb2959f6 100644 --- a/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol +++ b/script/deploy/facets/LDA/UpdateLDACoreFacets.s.sol @@ -2,120 +2,12 @@ pragma solidity ^0.8.17; import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; contract UpdateLDACoreFacets is UpdateLDAScriptBase { - using stdJson for string; - function run() public returns (address[] memory facets, bytes memory cutData) { - // Read LDA core facets dynamically from global.json config - string memory ldaGlobalConfigPath = string.concat( - vm.projectRoot(), - "/config/global.json" - ); - string memory ldaGlobalConfig = vm.readFile(ldaGlobalConfigPath); - string[] memory ldaCoreFacets = ldaGlobalConfig.readStringArray( - ".ldaCoreFacets" - ); - - emit log("LDA core facets found in config/global.json: "); - emit log_uint(ldaCoreFacets.length); - - bytes4[] memory exclude; - - // Check if the LDA loupe was already added to the diamond - bool loupeExists; - try loupe.facetAddresses() returns (address[] memory) { - // If call was successful, loupe exists on LDA diamond already - emit log("DiamondLoupeFacet exists on diamond already"); - loupeExists = true; - } catch { - // No need to do anything, just making sure that the flow continues in both cases with try/catch - } - - // Handle DiamondLoupeFacet separately as it needs special treatment - if (!loupeExists) { - emit log("DiamondLoupeFacet does not exist on diamond yet"); - // Read DiamondLoupeFacet from regular deployment file - address ldaDiamondLoupeAddress = _getConfigContractAddress( - path, - ".DiamondLoupeFacet" - ); - bytes4[] memory loupeSelectors = getSelectors( - "DiamondLoupeFacet", - exclude - ); - - buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - // Reset diamond cut variable to remove LDA diamondLoupe information - delete cut; - } - - // Process all LDA core facets dynamically - for (uint256 i = 0; i < ldaCoreFacets.length; i++) { - string memory facetName = ldaCoreFacets[i]; - - // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondLoupeFacet")) - ) { - continue; - } - // Skip DiamondCutFacet as it was already handled during LDA diamond deployment - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondCutFacet")) - ) { - continue; - } - - emit log("Now adding LDA core facet: "); - emit log(facetName); - // Read core facets from regular deployment file, not LDA file - address facetAddress = _getConfigContractAddress( - path, - string.concat(".", facetName) - ); - - bytes4[] memory selectors = getSelectors(facetName, exclude); - - // at this point we know for sure that diamond loupe exists on diamond - buildDiamondCut(selectors, facetAddress); - } - - // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - address(0), - "" - ); - } - emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); - emit log_bytes(cutData); - emit log("=== END CALLDATA ==="); - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - facets = loupe.facetAddresses(); + return updateCoreFacets(".ldaCoreFacets"); } } diff --git a/script/deploy/facets/UpdateCoreFacets.s.sol b/script/deploy/facets/UpdateCoreFacets.s.sol index f6c8a76cc..68ad9d294 100644 --- a/script/deploy/facets/UpdateCoreFacets.s.sol +++ b/script/deploy/facets/UpdateCoreFacets.s.sol @@ -2,125 +2,12 @@ pragma solidity ^0.8.17; import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; - -contract DeployScript is UpdateScriptBase { - using stdJson for string; - - error FailedToReadCoreFacetsFromConfig(); +contract UpdateCoreFacets is UpdateScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) { - // Read core facets dynamically from global.json config - string memory globalConfigPath = string.concat( - vm.projectRoot(), - "/config/global.json" - ); - string memory globalConfig = vm.readFile(globalConfigPath); - string[] memory coreFacets = globalConfig.readStringArray( - ".coreFacets" - ); - - emit log("Core facets found in config/global.json: "); - emit log_uint(coreFacets.length); - - bytes4[] memory exclude; - - // Check if the loupe was already added to the diamond - bool loupeExists; - try loupe.facetAddresses() returns (address[] memory) { - // If call was successful, loupe exists on diamond already - emit log("Loupe exists on diamond already"); - loupeExists = true; - } catch { - // No need to do anything, just making sure that the flow continues in both cases with try/catch - } - - // Handle DiamondLoupeFacet separately as it needs special treatment - if (!loupeExists) { - emit log("Loupe does not exist on diamond yet"); - address diamondLoupeAddress = _getConfigContractAddress( - path, - ".DiamondLoupeFacet" - ); - bytes4[] memory loupeSelectors = getSelectors( - "DiamondLoupeFacet", - exclude - ); - - buildInitialCut(loupeSelectors, diamondLoupeAddress); - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - // Reset diamond cut variable to remove diamondLoupe information - delete cut; - } - - // Process all core facets dynamically - for (uint256 i = 0; i < coreFacets.length; i++) { - string memory facetName = coreFacets[i]; - - // Skip DiamondCutFacet and DiamondLoupeFacet as it was already handled - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondLoupeFacet")) - ) { - continue; - } - // Skip DiamondLoupeFacet as it was already handled - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondCutFacet")) - ) { - continue; - } - - emit log("Now adding facet: "); - emit log(facetName); - // Use _getConfigContractAddress which validates the contract exists - address facetAddress = _getConfigContractAddress( - path, - string.concat(".", facetName) - ); - bytes4[] memory selectors = getSelectors(facetName, exclude); - - // at this point we know for sure that diamond loupe exists on diamond - buildDiamondCut(selectors, facetAddress); - // if (loupeExists) { - // buildDiamondCut(selectors, facetAddress); - // } else { - // buildInitialCut(selectors, facetAddress); - // } - } - - // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - address(0), - "" - ); - } - emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); - emit log_bytes(cutData); - emit log("=== END CALLDATA ==="); - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - facets = loupe.facetAddresses(); + return updateCoreFacets(".coreFacets"); } } diff --git a/script/deploy/facets/utils/BaseUpdateScript.sol b/script/deploy/facets/utils/BaseUpdateScript.sol index 3a2df8fab..8231fad0f 100644 --- a/script/deploy/facets/utils/BaseUpdateScript.sol +++ b/script/deploy/facets/utils/BaseUpdateScript.sol @@ -47,6 +47,143 @@ abstract contract BaseUpdateScript is ScriptBase { loupe = DiamondLoupeFacet(diamond); } + /// @notice Updates multiple core facets from global.json configuration + /// @param configKey The key in global.json to read facets from (e.g., ".coreFacets" or ".ldaCoreFacets") + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function updateCoreFacets( + string memory configKey + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // Read core facets dynamically from global.json config + string memory globalConfigPath = string.concat( + vm.projectRoot(), + "/config/global.json" + ); + string memory globalConfig = vm.readFile(globalConfigPath); + string[] memory coreFacets = globalConfig.readStringArray(configKey); + + emit log_uint(coreFacets.length); + + bytes4[] memory exclude; + + // Check if the loupe was already added to the diamond + bool loupeExists = _checkLoupeExists(); + + // Handle DiamondLoupeFacet separately as it needs special treatment + if (!loupeExists) { + _handleLoupeInstallation(); + } + + // Process all core facets dynamically + for (uint256 i = 0; i < coreFacets.length; i++) { + string memory facetName = coreFacets[i]; + + // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled + if (_shouldSkipCoreFacet(facetName)) { + continue; + } + + emit log(facetName); + + address facetAddress = _getConfigContractAddress( + path, + string.concat(".", facetName) + ); + + bytes4[] memory selectors = getSelectors(facetName, exclude); + + // at this point we know for sure that diamond loupe exists on diamond + buildDiamondCut(selectors, facetAddress); + } + + // Handle noBroadcast mode and broadcasting + return _finalizeCut(); + } + + /// @notice Checks if DiamondLoupeFacet exists on the diamond + /// @return loupeExists True if loupe exists, false otherwise + function _checkLoupeExists() internal virtual returns (bool loupeExists) { + try loupe.facetAddresses() returns (address[] memory) { + // If call was successful, loupe exists on diamond already + emit log("DiamondLoupeFacet exists on diamond already"); + loupeExists = true; + } catch { + // No need to do anything, just making sure that the flow continues in both cases with try/catch + } + } + + /// @notice Handles DiamondLoupeFacet installation if it doesn't exist + function _handleLoupeInstallation() internal virtual { + emit log("DiamondLoupeFacet does not exist on diamond yet"); + address diamondLoupeAddress = _getConfigContractAddress( + path, + ".DiamondLoupeFacet" + ); + bytes4[] memory loupeSelectors = getSelectors( + "DiamondLoupeFacet", + new bytes4[](0) + ); + + buildInitialCut(loupeSelectors, diamondLoupeAddress); + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + // Reset diamond cut variable to remove diamondLoupe information + delete cut; + } + + /// @notice Determines if a facet should be skipped during core facets update + /// @param facetName The name of the facet to check + /// @return True if facet should be skipped, false otherwise + function _shouldSkipCoreFacet( + string memory facetName + ) internal pure virtual returns (bool) { + return (keccak256(bytes(facetName)) == + keccak256(bytes("DiamondLoupeFacet")) || + keccak256(bytes(facetName)) == + keccak256(bytes("DiamondCutFacet"))); + } + + /// @notice Finalizes the diamond cut operation + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function _finalizeCut() + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + address(0), + "" + ); + } + emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); + emit log_bytes(cutData); + emit log("=== END CALLDATA ==="); + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + facets = loupe.facetAddresses(); + } + function update( string memory name ) diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index f6cf89417..3c88eaf69 100755 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -1,5 +1,11 @@ #!/usr/bin/env node +// LiFi DEX Aggregator (LDA) diamond health check script +// - validates LDA diamond contract deployment +// - checks availability of LDA core facets (shared with regular LiFi Diamond) +// - verifies all facets are properly registered in the diamond +// - validates diamond ownership (multisig for production, deployer for staging) + import { execSync } from 'child_process' import { defineCommand, runMain } from 'citty' diff --git a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol index a9a1477b7..2fb2959f6 100644 --- a/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol +++ b/script/deploy/zksync/LDA/UpdateLDACoreFacets.zksync.s.sol @@ -2,121 +2,12 @@ pragma solidity ^0.8.17; import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; contract UpdateLDACoreFacets is UpdateLDAScriptBase { - using stdJson for string; - function run() public returns (address[] memory facets, bytes memory cutData) { - // Read LDA core facets dynamically from global.json config - string memory ldaGlobalConfigPath = string.concat( - vm.projectRoot(), - "/config/global.json" - ); - string memory ldaGlobalConfig = vm.readFile(ldaGlobalConfigPath); - string[] memory ldaCoreFacets = ldaGlobalConfig.readStringArray( - ".ldaCoreFacets" - ); - - emit log("LDA core facets found in config/global.json: "); - emit log_uint(ldaCoreFacets.length); - - bytes4[] memory exclude; - - // Check if the LDA loupe was already added to the diamond - bool loupeExists; - try loupe.facetAddresses() returns (address[] memory) { - // If call was successful, loupe exists on LDA diamond already - emit log("DiamondLoupeFacet exists on diamond already"); - loupeExists = true; - } catch { - // No need to do anything, just making sure that the flow continues in both cases with try/catch - } - - // Handle DiamondLoupeFacet separately as it needs special treatment - if (!loupeExists) { - emit log("DiamondLoupeFacet does not exist on diamond yet"); - // Read DiamondLoupeFacet from zkSync deployment file (same as regular script) - address ldaDiamondLoupeAddress = _getConfigContractAddress( - path, - ".DiamondLoupeFacet" - ); - - bytes4[] memory loupeSelectors = getSelectors( - "DiamondLoupeFacet", - exclude - ); - - buildInitialCut(loupeSelectors, ldaDiamondLoupeAddress); - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - // Reset diamond cut variable to remove LDA diamondLoupe information - delete cut; - } - - // Process all LDA core facets dynamically - for (uint256 i = 0; i < ldaCoreFacets.length; i++) { - string memory facetName = ldaCoreFacets[i]; - - // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondLoupeFacet")) - ) { - continue; - } - // Skip DiamondCutFacet as it was already handled during LDA diamond deployment - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondCutFacet")) - ) { - continue; - } - - emit log("Now adding LDA core facet: "); - emit log(facetName); - // Read core facets from zkSync deployment file (same as regular script) - address facetAddress = _getConfigContractAddress( - path, - string.concat(".", facetName) - ); - - bytes4[] memory selectors = getSelectors(facetName, exclude); - - // at this point we know for sure that diamond loupe exists on diamond - buildDiamondCut(selectors, facetAddress); - } - - // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - address(0), - "" - ); - } - emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); - emit log_bytes(cutData); - emit log("=== END CALLDATA ==="); - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - facets = loupe.facetAddresses(); + return updateCoreFacets(".ldaCoreFacets"); } } diff --git a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol index 36a728f7b..bbf48c4e4 100644 --- a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.17; import { stdJson } from "forge-std/StdJson.sol"; -import { BaseZkSyncUpdateScript } from "../../utils/BaseZkSyncUpdateScript.sol"; +import { BaseUpdateScript } from "../../utils/BaseUpdateScript.sol"; -contract UpdateLDAScriptBase is BaseZkSyncUpdateScript { +contract UpdateLDAScriptBase is BaseUpdateScript { using stdJson for string; function _getDiamondAddress() internal override returns (address) { diff --git a/script/deploy/zksync/UpdateCoreFacets.zksync.s.sol b/script/deploy/zksync/UpdateCoreFacets.zksync.s.sol index f6c8a76cc..68ad9d294 100644 --- a/script/deploy/zksync/UpdateCoreFacets.zksync.s.sol +++ b/script/deploy/zksync/UpdateCoreFacets.zksync.s.sol @@ -2,125 +2,12 @@ pragma solidity ^0.8.17; import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; - -contract DeployScript is UpdateScriptBase { - using stdJson for string; - - error FailedToReadCoreFacetsFromConfig(); +contract UpdateCoreFacets is UpdateScriptBase { function run() public returns (address[] memory facets, bytes memory cutData) { - // Read core facets dynamically from global.json config - string memory globalConfigPath = string.concat( - vm.projectRoot(), - "/config/global.json" - ); - string memory globalConfig = vm.readFile(globalConfigPath); - string[] memory coreFacets = globalConfig.readStringArray( - ".coreFacets" - ); - - emit log("Core facets found in config/global.json: "); - emit log_uint(coreFacets.length); - - bytes4[] memory exclude; - - // Check if the loupe was already added to the diamond - bool loupeExists; - try loupe.facetAddresses() returns (address[] memory) { - // If call was successful, loupe exists on diamond already - emit log("Loupe exists on diamond already"); - loupeExists = true; - } catch { - // No need to do anything, just making sure that the flow continues in both cases with try/catch - } - - // Handle DiamondLoupeFacet separately as it needs special treatment - if (!loupeExists) { - emit log("Loupe does not exist on diamond yet"); - address diamondLoupeAddress = _getConfigContractAddress( - path, - ".DiamondLoupeFacet" - ); - bytes4[] memory loupeSelectors = getSelectors( - "DiamondLoupeFacet", - exclude - ); - - buildInitialCut(loupeSelectors, diamondLoupeAddress); - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - // Reset diamond cut variable to remove diamondLoupe information - delete cut; - } - - // Process all core facets dynamically - for (uint256 i = 0; i < coreFacets.length; i++) { - string memory facetName = coreFacets[i]; - - // Skip DiamondCutFacet and DiamondLoupeFacet as it was already handled - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondLoupeFacet")) - ) { - continue; - } - // Skip DiamondLoupeFacet as it was already handled - if ( - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondCutFacet")) - ) { - continue; - } - - emit log("Now adding facet: "); - emit log(facetName); - // Use _getConfigContractAddress which validates the contract exists - address facetAddress = _getConfigContractAddress( - path, - string.concat(".", facetName) - ); - bytes4[] memory selectors = getSelectors(facetName, exclude); - - // at this point we know for sure that diamond loupe exists on diamond - buildDiamondCut(selectors, facetAddress); - // if (loupeExists) { - // buildDiamondCut(selectors, facetAddress); - // } else { - // buildInitialCut(selectors, facetAddress); - // } - } - - // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - address(0), - "" - ); - } - emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); - emit log_bytes(cutData); - emit log("=== END CALLDATA ==="); - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - facets = loupe.facetAddresses(); + return updateCoreFacets(".coreFacets"); } } diff --git a/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol b/script/deploy/zksync/utils/BaseUpdateScript.sol similarity index 56% rename from script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol rename to script/deploy/zksync/utils/BaseUpdateScript.sol index 3d446e632..db6934722 100644 --- a/script/deploy/zksync/utils/BaseZkSyncUpdateScript.sol +++ b/script/deploy/zksync/utils/BaseUpdateScript.sol @@ -7,7 +7,7 @@ import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -abstract contract BaseZkSyncUpdateScript is ScriptBase { +abstract contract BaseUpdateScript is ScriptBase { using stdJson for string; struct FunctionSignature { @@ -88,6 +88,143 @@ abstract contract BaseZkSyncUpdateScript is ScriptBase { vm.stopBroadcast(); } + /// @notice Updates multiple core facets from global.json configuration + /// @param configKey The key in global.json to read facets from (e.g., ".coreFacets" or ".ldaCoreFacets") + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function updateCoreFacets( + string memory configKey + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // Read core facets dynamically from global.json config + string memory globalConfigPath = string.concat( + vm.projectRoot(), + "/config/global.json" + ); + string memory globalConfig = vm.readFile(globalConfigPath); + string[] memory coreFacets = globalConfig.readStringArray(configKey); + + emit log_uint(coreFacets.length); + + bytes4[] memory exclude; + + // Check if the loupe was already added to the diamond + bool loupeExists = _checkLoupeExists(); + + // Handle DiamondLoupeFacet separately as it needs special treatment + if (!loupeExists) { + _handleLoupeInstallation(); + } + + // Process all core facets dynamically + for (uint256 i = 0; i < coreFacets.length; i++) { + string memory facetName = coreFacets[i]; + + // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled + if (_shouldSkipCoreFacet(facetName)) { + continue; + } + + emit log(facetName); + + address facetAddress = _getConfigContractAddress( + path, + string.concat(".", facetName) + ); + + bytes4[] memory selectors = getSelectors(facetName, exclude); + + // at this point we know for sure that diamond loupe exists on diamond + buildDiamondCut(selectors, facetAddress); + } + + // Handle noBroadcast mode and broadcasting + return _finalizeCut(); + } + + /// @notice Checks if DiamondLoupeFacet exists on the diamond + /// @return loupeExists True if loupe exists, false otherwise + function _checkLoupeExists() internal virtual returns (bool loupeExists) { + try loupe.facetAddresses() returns (address[] memory) { + // If call was successful, loupe exists on diamond already + emit log("DiamondLoupeFacet exists on diamond already"); + loupeExists = true; + } catch { + // No need to do anything, just making sure that the flow continues in both cases with try/catch + } + } + + /// @notice Handles DiamondLoupeFacet installation if it doesn't exist + function _handleLoupeInstallation() internal virtual { + emit log("DiamondLoupeFacet does not exist on diamond yet"); + address diamondLoupeAddress = _getConfigContractAddress( + path, + ".DiamondLoupeFacet" + ); + bytes4[] memory loupeSelectors = getSelectors( + "DiamondLoupeFacet", + new bytes4[](0) + ); + + buildInitialCut(loupeSelectors, diamondLoupeAddress); + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + // Reset diamond cut variable to remove diamondLoupe information + delete cut; + } + + /// @notice Determines if a facet should be skipped during core facets update + /// @param facetName The name of the facet to check + /// @return True if facet should be skipped, false otherwise + function _shouldSkipCoreFacet( + string memory facetName + ) internal pure virtual returns (bool) { + return (keccak256(bytes(facetName)) == + keccak256(bytes("DiamondLoupeFacet")) || + keccak256(bytes(facetName)) == + keccak256(bytes("DiamondCutFacet"))); + } + + /// @notice Finalizes the diamond cut operation + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function _finalizeCut() + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + address(0), + "" + ); + } + emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); + emit log_bytes(cutData); + emit log("=== END CALLDATA ==="); + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + facets = loupe.facetAddresses(); + } + function getExcludes() internal virtual returns (bytes4[] memory) {} function getCallData() internal virtual returns (bytes memory) {} diff --git a/script/deploy/zksync/utils/UpdateScriptBase.sol b/script/deploy/zksync/utils/UpdateScriptBase.sol index bfd701ef6..ab5e869c5 100644 --- a/script/deploy/zksync/utils/UpdateScriptBase.sol +++ b/script/deploy/zksync/utils/UpdateScriptBase.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.17; import { stdJson } from "forge-std/StdJson.sol"; -import { BaseZkSyncUpdateScript } from "./BaseZkSyncUpdateScript.sol"; +import { BaseUpdateScript } from "./BaseUpdateScript.sol"; -contract UpdateScriptBase is BaseZkSyncUpdateScript { +contract UpdateScriptBase is BaseUpdateScript { using stdJson for string; function _getDiamondAddress() internal override returns (address) { diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 6555234e6..41ed55768 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4457,6 +4457,7 @@ function updateDiamondLogs() { # read function arguments into variable local ENVIRONMENT=$1 local NETWORK=$2 + local DIAMOND_TYPE=${3:-"LiFiDiamond"} # Optional parameter, defaults to regular diamond # if no network was passed to this function, update all networks if [[ -z $NETWORK ]]; then @@ -4467,7 +4468,7 @@ function updateDiamondLogs() { fi echo "" - echo "Now updating all diamond logs on network(s): ${NETWORKS[*]}" + echo "Now updating all $DIAMOND_TYPE logs on network(s): ${NETWORKS[*]}" echo "" # ENVIRONMENTS=("production" "staging") @@ -4492,7 +4493,7 @@ function updateDiamondLogs() { echo " current ENVIRONMENT: $ENVIRONMENT" # Call the helper function in background for parallel execution - updateDiamondLogForNetwork "$NETWORK" "$ENVIRONMENT" & + updateDiamondLogForNetwork "$NETWORK" "$ENVIRONMENT" "$DIAMOND_TYPE" & # Store the PID and job info pids+=($!) @@ -4502,7 +4503,7 @@ function updateDiamondLogs() { done # Wait for all background jobs to complete and capture exit codes - echo "Waiting for all diamond log updates to complete..." + echo "Waiting for all $DIAMOND_TYPE log updates to complete..." local failed_jobs=() local job_count=${#pids[@]} @@ -4521,12 +4522,12 @@ function updateDiamondLogs() { # Check if any jobs failed if [ ${#failed_jobs[@]} -gt 0 ]; then - error "Some diamond log updates failed: ${failed_jobs[*]}" - echo "All diamond log updates completed with ${#failed_jobs[@]} failure(s) out of $job_count total jobs" + error "Some $DIAMOND_TYPE log updates failed: ${failed_jobs[*]}" + echo "All $DIAMOND_TYPE log updates completed with ${#failed_jobs[@]} failure(s) out of $job_count total jobs" playNotificationSound return 1 else - echo "All diamond log updates completed successfully ($job_count jobs)" + echo "All $DIAMOND_TYPE log updates completed successfully ($job_count jobs)" playNotificationSound return 0 fi @@ -4840,138 +4841,3 @@ function removeNetworkFromTargetStateJSON() { return 1 fi } - -# ==== LDA-SPECIFIC HELPER FUNCTIONS ==== - -# Deploy and add contract to LDA Diamond -deployAndAddContractToLDADiamond() { - local NETWORK="$1" - local ENVIRONMENT="$2" - local CONTRACT="$3" - local DIAMOND_NAME="$4" - local VERSION="$5" - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployAndAddContractToLDADiamond" - - # load required resources - source script/config.sh - source script/helperFunctions.sh - source script/deploy/deploySingleContract.sh - source script/tasks/ldaDiamondUpdateFacet.sh - - # Deploy the contract (LDA contract) - deploySingleContract "$CONTRACT" "$NETWORK" "$ENVIRONMENT" "$VERSION" false "true" - checkFailure $? "deploy contract $CONTRACT to network $NETWORK" - - # Add contract to LDA Diamond - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_NAME" "Update${CONTRACT}" false - checkFailure $? "add contract $CONTRACT to $DIAMOND_NAME on network $NETWORK" - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployAndAddContractToLDADiamond completed" - - return 0 -} - -# Deploy facet and add to LDA Diamond -deployFacetAndAddToLDADiamond() { - local NETWORK="$1" - local ENVIRONMENT="$2" - local FACET_NAME="$3" - local DIAMOND_NAME="$4" - local VERSION="$5" - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start deployFacetAndAddToLDADiamond for $FACET_NAME" - - # Deploy the facet and add it to LDA Diamond - deployAndAddContractToLDADiamond "$NETWORK" "$ENVIRONMENT" "$FACET_NAME" "$DIAMOND_NAME" "$VERSION" - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< deployFacetAndAddToLDADiamond completed for $FACET_NAME" - - return 0 -} - - -# Update LDA diamond logs -updateLDADiamondLogs() { - local ENVIRONMENT="$1" - local NETWORK="$2" - - echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start updateLDADiamondLogs" - - # if no network was passed to this function, update all networks - if [[ -z $NETWORK ]]; then - # get array with all network names - NETWORKS=($(getIncludedNetworksArray)) - else - NETWORKS=($NETWORK) - fi - - echo "" - echo "Now updating all LDA diamond logs on network(s): ${NETWORKS[*]}" - echo "" - - # ENVIRONMENTS=("production" "staging") - if [[ "$ENVIRONMENT" == "production" || -z "$ENVIRONMENT" ]]; then - ENVIRONMENTS=("production") - else - ENVIRONMENTS=("staging") - fi - - # Create arrays to store background job PIDs and their corresponding network/environment info - local pids=() - local job_info=() - local job_index=0 - - # loop through all networks - for NETWORK in "${NETWORKS[@]}"; do - echo "" - echo "current Network: $NETWORK" - - for ENVIRONMENT in "${ENVIRONMENTS[@]}"; do - echo " -----------------------" - echo " current ENVIRONMENT: $ENVIRONMENT" - - # Call the helper function in background for parallel execution with LDA diamond type - updateDiamondLogForNetwork "$NETWORK" "$ENVIRONMENT" "LiFiDEXAggregatorDiamond" & - - # Store the PID and job info - pids+=($!) - job_info+=("$NETWORK:$ENVIRONMENT") - job_index=$((job_index + 1)) - done - done - - # Wait for all background jobs to complete and capture exit codes - echo "Waiting for all LDA diamond log updates to complete..." - local failed_jobs=() - local job_count=${#pids[@]} - - for i in "${!pids[@]}"; do - local pid="${pids[$i]}" - local info="${job_info[$i]}" - - # Wait for this specific job and capture its exit code - if wait "$pid"; then - echo "[$info] Completed successfully" - else - echo "[$info] Failed with exit code $?" - failed_jobs+=("$info") - fi - done - - # Check if any jobs failed - if [ ${#failed_jobs[@]} -gt 0 ]; then - error "Some LDA diamond log updates failed: ${failed_jobs[*]}" - echo "All LDA diamond log updates completed with ${#failed_jobs[@]} failure(s) out of $job_count total jobs" - playNotificationSound - return 1 - else - echo "All LDA diamond log updates completed successfully ($job_count jobs)" - playNotificationSound - return 0 - fi - - echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< updateLDADiamondLogs completed" - - return 0 -} diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh index 7b135d780..7f72180bd 100644 --- a/script/tasks/ldaDiamondUpdateFacet.sh +++ b/script/tasks/ldaDiamondUpdateFacet.sh @@ -12,7 +12,6 @@ function ldaDiamondUpdateFacet() { local ENVIRONMENT="$2" local DIAMOND_CONTRACT_NAME="$3" local UPDATE_SCRIPT="$4" - local SHOW_LOGS="$5" # if no NETWORK was passed to this function, ask user to select it if [[ -z "$NETWORK" ]]; then @@ -94,22 +93,6 @@ function ldaDiamondUpdateFacet() { GAS_ESTIMATE_MULTIPLIER=130 # this is foundry's default value fi - # logging for debug purposes - if [[ $SHOW_LOGS == "true" ]]; then - echo "" - echoDebug "in function ldaDiamondUpdateFacet" - echoDebug "NETWORK=$NETWORK" - echoDebug "ENVIRONMENT=$ENVIRONMENT" - echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" - echoDebug "UPDATE_SCRIPT=$UPDATE_SCRIPT" - echoDebug "LDA_UPDATE_SCRIPT_PATH=$LDA_UPDATE_SCRIPT_PATH" - echoDebug "DIAMOND_ADDRESS=$DIAMOND_ADDRESS" - echoDebug "FILE_SUFFIX=$FILE_SUFFIX" - echoDebug "USE_LDA_DIAMOND=$USE_LDA_DIAMOND" - echoDebug "GAS_ESTIMATE_MULTIPLIER=$GAS_ESTIMATE_MULTIPLIER (default value: 130, set in .env for example to 200 for doubling Foundry's estimate)" - echo "" - fi - # execute LDA diamond update script local attempts=1 @@ -154,10 +137,6 @@ function ldaDiamondUpdateFacet() { FACETS=$(echo "$RETURN_DATA" | jq -r '.facets.value // "{}"') if [[ $FACETS != "{}" ]]; then echo "[info] LDA diamond update was successful" - if [[ $SHOW_LOGS == "true" ]]; then - FACET_COUNT=$(echo "$FACETS" | jq -r '. | length' 2>/dev/null || echo "unknown") - echo "[info] Updated diamond now has $FACET_COUNT facets" - fi return 0 # exit the loop if the operation was successful fi fi From 83ad6f6f342853f40d1dc5899903637ba1938eba Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sun, 7 Sep 2025 12:02:24 +0200 Subject: [PATCH 175/220] updates --- deployments/_deployments_log_file.json | 6 +++--- deployments/bsc.lda.diamond.staging.json | 2 +- deployments/bsc.staging.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 3d6ccf205..a14c0b517 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -40002,11 +40002,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xC1D230b7De7Eba42E9f4d0F20512193875Ff6F26", + "ADDRESS": "0x0e5898bAa8CAf208292a9f7b0C5DA7BbDFc95F4b", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 19:23:13", + "TIMESTAMP": "2025-09-07 12:00:18", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345121", + "SALT": "22345120", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } diff --git a/deployments/bsc.lda.diamond.staging.json b/deployments/bsc.lda.diamond.staging.json index f4cc3aeb5..f12e791f5 100644 --- a/deployments/bsc.lda.diamond.staging.json +++ b/deployments/bsc.lda.diamond.staging.json @@ -25,7 +25,7 @@ "Name": "CurveFacet", "Version": "1.0.0" }, - "0xC1D230b7De7Eba42E9f4d0F20512193875Ff6F26": { + "0x0e5898bAa8CAf208292a9f7b0C5DA7BbDFc95F4b": { "Name": "IzumiV3Facet", "Version": "1.0.0" }, diff --git a/deployments/bsc.staging.json b/deployments/bsc.staging.json index 45029f3b5..c640bafe0 100644 --- a/deployments/bsc.staging.json +++ b/deployments/bsc.staging.json @@ -38,7 +38,7 @@ "AlgebraFacet": "0x5363c2db9eB0e9080574915b4C486B9851BA4326", "CoreRouteFacet": "0x07497D2C0B32537B0162dc35a4794F4B089a53Aa", "CurveFacet": "0x7B383eD28261835e63D2aed3b4A415B29438354B", - "IzumiV3Facet": "0xC1D230b7De7Eba42E9f4d0F20512193875Ff6F26", + "IzumiV3Facet": "0x0e5898bAa8CAf208292a9f7b0C5DA7BbDFc95F4b", "KatanaV3Facet": "0x2a528a7dDa49D45f9B766e131460a36e060861B2", "NativeWrapperFacet": "0xd13d6DF1ef271dC887Df5499e4059e1AeC42Ea11", "SyncSwapV2Facet": "0xdFc21dcEc5220bfaB06D865312991F6058f5dfC6", From 3d201a46acea48b4cda084bc2a97916cf55ee03e Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sun, 7 Sep 2025 12:25:34 +0200 Subject: [PATCH 176/220] Refine LDA contract check in deploySingleContract script --- script/deploy/deploySingleContract.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index 358875012..dea2b75d1 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -78,9 +78,8 @@ deploySingleContract() { # Helper function to check if contract is LDA-related isLDAContract() { local contract_name="$1" - # Check if contract name contains LDA-related patterns or is in LDA facets list + # Check if contract name is LDA diamond or is in LDA facet if [[ "$contract_name" == "LiFiDEXAggregatorDiamond" ]] || - [[ "$contract_name" == *"LDA"* ]] || [[ -f "script/deploy/facets/LDA/Deploy${contract_name}.s.sol" ]]; then return 0 # true else From ed3f453e73346a77ecb36fb92732fde37aff53db Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sun, 7 Sep 2025 23:31:03 +0200 Subject: [PATCH 177/220] update --- script/scriptMaster.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index 10a998507..a59349055 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -220,7 +220,6 @@ scriptMaster() { gum choose \ "yes - to LiFiDiamond" \ "yes - to LiFiDiamondImmutable" \ - "yes - to LiFiDEXAggregatorDiamond" \ " no - do not update any diamond" ) fi From e6d7c0e2e6b050751cd0c52a1ba5bdb7610b8655 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 11:18:15 +0200 Subject: [PATCH 178/220] changed BaseDiamondTest changed to DiamondTestHelpers changed CommonDiamondTest.sol changed to BaseDiamondTest --- .../Facets/CBridge/CBridgeRefund.t.sol | 4 +- test/solidity/LiFiDiamond.t.sol | 4 +- .../LDA/LiFiDEXAggregatorDiamond.t.sol | 4 +- test/solidity/utils/BaseDiamondTest.sol | 277 ++++++++++++------ test/solidity/utils/CommonDiamondTest.sol | 206 ------------- test/solidity/utils/DiamondTestHelpers.sol | 120 ++++++++ 6 files changed, 315 insertions(+), 300 deletions(-) delete mode 100644 test/solidity/utils/CommonDiamondTest.sol create mode 100644 test/solidity/utils/DiamondTestHelpers.sol diff --git a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol index 5d55053cb..1802037b1 100644 --- a/test/solidity/Facets/CBridge/CBridgeRefund.t.sol +++ b/test/solidity/Facets/CBridge/CBridgeRefund.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import { DSTest } from "ds-test/test.sol"; -import { BaseDiamondTest } from "../../utils/BaseDiamondTest.sol"; +import { DiamondTestHelpers } from "../../utils/DiamondTestHelpers.sol"; import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { ERC20 } from "solmate/tokens/ERC20.sol"; import { WithdrawFacet } from "lifi/Facets/WithdrawFacet.sol"; @@ -12,7 +12,7 @@ import { UnAuthorized, NotAContract } from "lifi/Errors/GenericErrors.sol"; // Actual refund was processed at 25085299(Feb-18-2022 03:24:09 PM +UTC) // Run `forge test --match-path test\solidity\Facets\CBridgeRefund.t.sol --fork-url POLYGON_RPC_URL --fork-block-number 25085298` // or `forge test --match-contract CBridgeRefundTest --fork-url POLYGON_RPC_URL --fork-block-number 25085298` -contract CBridgeRefundTestPolygon is DSTest, BaseDiamondTest { +contract CBridgeRefundTestPolygon is DSTest, DiamondTestHelpers { address internal constant CBRIDGE_ADDRESS = 0x88DCDC47D2f83a99CF0000FDF667A468bB958a78; address internal constant LIFI_ADDRESS = diff --git a/test/solidity/LiFiDiamond.t.sol b/test/solidity/LiFiDiamond.t.sol index d1b1bd5ee..37dd18fa7 100644 --- a/test/solidity/LiFiDiamond.t.sol +++ b/test/solidity/LiFiDiamond.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.17; import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; -import { CommonDiamondTest } from "./utils/CommonDiamondTest.sol"; +import { BaseDiamondTest } from "./utils/BaseDiamondTest.sol"; import { InvalidConfig } from "lifi/Errors/GenericErrors.sol"; -contract LiFiDiamondTest is CommonDiamondTest { +contract LiFiDiamondTest is BaseDiamondTest { function setUp() public virtual override { super.setUp(); // Call createDiamond to get a fully configured diamond with all facets diff --git a/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol index 627f5177d..588f060e6 100644 --- a/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol +++ b/test/solidity/Periphery/LDA/LiFiDEXAggregatorDiamond.t.sol @@ -4,10 +4,10 @@ pragma solidity ^0.8.17; import { LiFiDEXAggregatorDiamond } from "lifi/Periphery/LDA/LiFiDEXAggregatorDiamond.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { CommonDiamondTest } from "../../utils/CommonDiamondTest.sol"; +import { BaseDiamondTest } from "../../utils/BaseDiamondTest.sol"; /// @notice Spins up a minimal LDA (LiFi DEX Aggregator) Diamond with loupe, ownership, and emergency pause facets for periphery tests. -contract LiFiDEXAggregatorDiamondTest is CommonDiamondTest { +contract LiFiDEXAggregatorDiamondTest is BaseDiamondTest { LiFiDEXAggregatorDiamond public ldaDiamond; function setUp() public virtual override { diff --git a/test/solidity/utils/BaseDiamondTest.sol b/test/solidity/utils/BaseDiamondTest.sol index b98df4729..befec8d51 100644 --- a/test/solidity/utils/BaseDiamondTest.sol +++ b/test/solidity/utils/BaseDiamondTest.sol @@ -1,120 +1,221 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; +import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { Test } from "forge-std/Test.sol"; -import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; - -/// @notice Minimal helper to compose a test Diamond and add facets/selectors for test scenarios. -/// @dev Provides overloads to add facets with or without init calldata. -/// This contract is used by higher-level LDA test scaffolding to assemble the test Diamond. -abstract contract BaseDiamondTest is Test, TestBaseRandomConstants { - LibDiamond.FacetCut[] internal cut; - - /// @notice Adds standard Diamond Loupe selectors to the `cut` buffer. - /// @param _diamondLoupe Address of a deployed `DiamondLoupeFacet`. - /// @dev Call this before invoking diamondCut with the buffered `cut`. - function _addDiamondLoupeSelectors(address _diamondLoupe) internal { - bytes4[] memory functionSelectors = new bytes4[](5); - functionSelectors[0] = DiamondLoupeFacet - .facetFunctionSelectors - .selector; - functionSelectors[1] = DiamondLoupeFacet.facets.selector; - functionSelectors[2] = DiamondLoupeFacet.facetAddress.selector; - functionSelectors[3] = DiamondLoupeFacet.facetAddresses.selector; - functionSelectors[4] = DiamondLoupeFacet.supportsInterface.selector; +import { DiamondTestHelpers } from "./DiamondTestHelpers.sol"; + +/// @notice Base contract with common diamond test functions to reduce code duplication +/// @dev Provides standard test patterns that work with any diamond implementation +abstract contract BaseDiamondTest is DiamondTestHelpers { + // Main diamond instance - accessible to all inheriting contracts including TestBase + LiFiDiamond internal diamond; + + // Common facet instances + DiamondCutFacet internal diamondCutFacet; + OwnershipFacet internal ownershipFacet; + DiamondLoupeFacet internal diamondLoupeFacet; + PeripheryRegistryFacet internal peripheryFacet; + EmergencyPauseFacet internal emergencyPauseFacet; + + // Events + event DiamondCut( + LibDiamond.FacetCut[] _diamondCut, + address _init, + bytes _calldata + ); + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + // Errors + error FunctionDoesNotExist(); + error ShouldNotReachThisCode(); + error InvalidDiamondSetup(); + error ExternalCallFailed(); + + function setUp() public virtual { + // Create the main LiFiDiamond that TestBase and other facet tests expect + diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); + } + + /// @notice Creates a fully configured diamond with all standard facets + /// @param _diamondOwner Owner address for the diamond + /// @param _pauserWallet Pauser wallet address for emergency pause facet + /// @return The created LiFiDiamond instance + function createDiamond( + address _diamondOwner, + address _pauserWallet + ) internal returns (LiFiDiamond) { + vm.startPrank(_diamondOwner); + + // Recreate facets with the specified pauser + diamondCutFacet = new DiamondCutFacet(); + diamondLoupeFacet = new DiamondLoupeFacet(); + ownershipFacet = new OwnershipFacet(); + peripheryFacet = new PeripheryRegistryFacet(); + emergencyPauseFacet = new EmergencyPauseFacet(_pauserWallet); + + // Create new diamond + diamond = new LiFiDiamond(_diamondOwner, address(diamondCutFacet)); + + // Add Diamond Loupe + _addDiamondLoupeSelectors(address(diamondLoupeFacet)); + // Add Ownership + _addOwnershipSelectors(address(ownershipFacet)); + + // Add PeripheryRegistry + bytes4[] memory functionSelectors = new bytes4[](2); + functionSelectors[0] = PeripheryRegistryFacet + .registerPeripheryContract + .selector; + functionSelectors[1] = PeripheryRegistryFacet + .getPeripheryContract + .selector; cut.push( LibDiamond.FacetCut({ - facetAddress: _diamondLoupe, + facetAddress: address(peripheryFacet), action: LibDiamond.FacetCutAction.Add, functionSelectors: functionSelectors }) ); - } - - /// @notice Adds standard Ownership selectors to the `cut` buffer. - /// @param _ownership Address of a deployed `OwnershipFacet`. - /// @dev Call this before invoking diamondCut with the buffered `cut`. - function _addOwnershipSelectors(address _ownership) internal { - bytes4[] memory functionSelectors = new bytes4[](4); - functionSelectors[0] = OwnershipFacet.transferOwnership.selector; - functionSelectors[1] = OwnershipFacet.cancelOwnershipTransfer.selector; - functionSelectors[2] = OwnershipFacet - .confirmOwnershipTransfer - .selector; - functionSelectors[3] = OwnershipFacet.owner.selector; + // Add EmergencyPause + functionSelectors = new bytes4[](3); + functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; + functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; + functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; cut.push( LibDiamond.FacetCut({ - facetAddress: _ownership, + facetAddress: address(emergencyPauseFacet), action: LibDiamond.FacetCutAction.Add, functionSelectors: functionSelectors }) ); + + DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); + delete cut; + vm.stopPrank(); + return diamond; } - /// @notice Adds a facet and function selectors to the target diamond. - /// @param _diamond Address of the diamond proxy. - /// @param _facet Address of the facet implementation to add. - /// @param _selectors Function selectors to expose in the diamond. - /// @dev Convenience overload with no initializer; see the 5-arg overload for init flows. - function addFacet( - address _diamond, - address _facet, - bytes4[] memory _selectors - ) public virtual { - _addFacet(_diamond, _facet, _selectors, address(0), ""); + /// @notice Override this to return the diamond address for testing + function getDiamondAddress() internal view virtual returns (address) { + return address(diamond); } - /// @notice Adds a facet and function selectors to the target diamond, optionally executing an initializer. - /// @param _diamond Address of the diamond proxy. - /// @param _facet Address of the facet implementation to add. - /// @param _selectors Function selectors to expose in the diamond. - /// @param _init Address of an initializer (can be facet or another contract). - /// @param _initCallData ABI-encoded calldata for the initializer. - /// @dev Owner is impersonated via vm.startPrank for the duration of the diamondCut. - function addFacet( - address _diamond, - address _facet, - bytes4[] memory _selectors, - address _init, - bytes memory _initCallData - ) public virtual { - _addFacet(_diamond, _facet, _selectors, _init, _initCallData); + /// @notice Override this to return the diamond owner address + function getDiamondOwner() internal view virtual returns (address) { + return USER_DIAMOND_OWNER; } - /// @notice Performs diamondCut with an appended `FacetCut`. - /// @param _diamond Address of the diamond proxy. - /// @param _facet Address of the facet implementation to add. - /// @param _selectors Function selectors to expose in the diamond. - /// @param _init Address of an initializer (address(0) for none). - /// @param _initCallData ABI-encoded calldata for the initializer (empty if none). - /// @dev Example: - /// - Append loupe + ownership cuts first. - /// - Then call `_addFacet(diamond, address(myFacet), selectors, address(0), "")`. - function _addFacet( - address _diamond, - address _facet, - bytes4[] memory _selectors, - address _init, - bytes memory _initCallData - ) internal virtual { - vm.startPrank(OwnershipFacet(_diamond).owner()); - cut.push( - LibDiamond.FacetCut({ - facetAddress: _facet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: _selectors - }) + /// @notice Test that diamond deployment works without errors + function test_DeploysWithoutErrors() public virtual { + assertTrue( + getDiamondAddress() != address(0), + "Diamond should be deployed" ); + } - DiamondCutFacet(_diamond).diamondCut(cut, _init, _initCallData); + /// @notice Test that diamond forwards calls via delegate call + function test_ForwardsCallsViaDelegateCall() public { + address diamondAddr = getDiamondAddress(); + address owner = getDiamondOwner(); + + vm.startPrank(owner); + + DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); + + // Check if DiamondLoupeFacet is already installed + bool loupeAlreadyInstalled = false; + try DiamondLoupeFacet(diamondAddr).facetAddresses() returns ( + address[] memory + ) { + loupeAlreadyInstalled = true; + } catch { + // Loupe not installed, which is expected for basic diamonds + } + + if (!loupeAlreadyInstalled) { + // prepare function selectors + bytes4[] memory functionSelectors = new bytes4[](4); + functionSelectors[0] = diamondLoupe.facets.selector; + functionSelectors[1] = diamondLoupe + .facetFunctionSelectors + .selector; + functionSelectors[2] = diamondLoupe.facetAddresses.selector; + functionSelectors[3] = diamondLoupe.facetAddress.selector; + + // prepare diamondCut + LibDiamond.FacetCut[] memory cuts = new LibDiamond.FacetCut[](1); + cuts[0] = LibDiamond.FacetCut({ + facetAddress: address(diamondLoupe), + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }); + + DiamondCutFacet(diamondAddr).diamondCut(cuts, address(0), ""); + } + + // Now the call should succeed + address[] memory facetAddresses = DiamondLoupeFacet(diamondAddr) + .facetAddresses(); + assertTrue( + facetAddresses.length > 0, + "Should have facets after adding DiamondLoupe" + ); - delete cut; vm.stopPrank(); } + + /// @notice Test that diamond reverts on unknown function selectors + function test_RevertsOnUnknownFunctionSelector() public { + address diamondAddr = getDiamondAddress(); + + // Use a completely random selector that definitely doesn't exist + bytes memory callData = hex"deadbeef"; + + vm.expectRevert(FunctionDoesNotExist.selector); + (bool success, ) = diamondAddr.call(callData); + if (!success) { + vm.expectRevert("Diamond: Function does not exist"); + (bool success2, ) = diamondAddr.call(callData); + if (!success2) { + revert ShouldNotReachThisCode(); + } + } + } + + /// @notice Test that diamond can receive ETH + function test_CanReceiveETH() public { + address diamondAddr = getDiamondAddress(); + uint256 balanceBefore = diamondAddr.balance; + (bool success, ) = diamondAddr.call{ value: 1 ether }(""); + if (!success) revert ExternalCallFailed(); + + assertEq(address(diamond).balance, balanceBefore + 1 ether); + } + + /// @notice Test that diamond owner was correctly registered + function test_DiamondOwnerIsCorrectlyRegistered() public { + address diamondAddr = getDiamondAddress(); + address expectedOwner = getDiamondOwner(); + + // Get the actual owner from the diamond + address actualOwner = OwnershipFacet(diamondAddr).owner(); + + assertEq( + actualOwner, + expectedOwner, + "Diamond owner should match expected owner" + ); + } } diff --git a/test/solidity/utils/CommonDiamondTest.sol b/test/solidity/utils/CommonDiamondTest.sol deleted file mode 100644 index a6ee89257..000000000 --- a/test/solidity/utils/CommonDiamondTest.sol +++ /dev/null @@ -1,206 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { LiFiDiamond } from "lifi/LiFiDiamond.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; -import { EmergencyPauseFacet } from "lifi/Facets/EmergencyPauseFacet.sol"; -import { PeripheryRegistryFacet } from "lifi/Facets/PeripheryRegistryFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -import { BaseDiamondTest } from "./BaseDiamondTest.sol"; - -/// @notice Base contract with common diamond test functions to reduce code duplication -/// @dev Provides standard test patterns that work with any diamond implementation -abstract contract CommonDiamondTest is BaseDiamondTest { - // Main diamond instance - accessible to all inheriting contracts including TestBase - LiFiDiamond internal diamond; - - // Common facet instances - DiamondCutFacet internal diamondCutFacet; - OwnershipFacet internal ownershipFacet; - DiamondLoupeFacet internal diamondLoupeFacet; - PeripheryRegistryFacet internal peripheryFacet; - EmergencyPauseFacet internal emergencyPauseFacet; - - // Events - event DiamondCut( - LibDiamond.FacetCut[] _diamondCut, - address _init, - bytes _calldata - ); - - event OwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - - // Errors - error FunctionDoesNotExist(); - error ShouldNotReachThisCode(); - error InvalidDiamondSetup(); - error ExternalCallFailed(); - - function setUp() public virtual { - // Create the main LiFiDiamond that TestBase and other facet tests expect - diamond = createDiamond(USER_DIAMOND_OWNER, USER_PAUSER); - } - - /// @notice Creates a fully configured diamond with all standard facets - /// @param _diamondOwner Owner address for the diamond - /// @param _pauserWallet Pauser wallet address for emergency pause facet - /// @return The created LiFiDiamond instance - function createDiamond( - address _diamondOwner, - address _pauserWallet - ) internal returns (LiFiDiamond) { - vm.startPrank(_diamondOwner); - - // Recreate facets with the specified pauser - diamondCutFacet = new DiamondCutFacet(); - diamondLoupeFacet = new DiamondLoupeFacet(); - ownershipFacet = new OwnershipFacet(); - peripheryFacet = new PeripheryRegistryFacet(); - emergencyPauseFacet = new EmergencyPauseFacet(_pauserWallet); - - // Create new diamond - diamond = new LiFiDiamond(_diamondOwner, address(diamondCutFacet)); - - // Add Diamond Loupe - _addDiamondLoupeSelectors(address(diamondLoupeFacet)); - - // Add Ownership - _addOwnershipSelectors(address(ownershipFacet)); - - // Add PeripheryRegistry - bytes4[] memory functionSelectors = new bytes4[](2); - functionSelectors[0] = PeripheryRegistryFacet - .registerPeripheryContract - .selector; - functionSelectors[1] = PeripheryRegistryFacet - .getPeripheryContract - .selector; - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(peripheryFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) - ); - - // Add EmergencyPause - functionSelectors = new bytes4[](3); - functionSelectors[0] = emergencyPauseFacet.removeFacet.selector; - functionSelectors[1] = emergencyPauseFacet.pauseDiamond.selector; - functionSelectors[2] = emergencyPauseFacet.unpauseDiamond.selector; - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(emergencyPauseFacet), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }) - ); - - DiamondCutFacet(address(diamond)).diamondCut(cut, address(0), ""); - delete cut; - vm.stopPrank(); - return diamond; - } - - /// @notice Override this to return the diamond address for testing - function getDiamondAddress() internal view virtual returns (address) { - return address(diamond); - } - - /// @notice Override this to return the diamond owner address - function getDiamondOwner() internal view virtual returns (address) { - return USER_DIAMOND_OWNER; - } - - /// @notice Test that diamond deployment works without errors - function test_DeploysWithoutErrors() public virtual { - assertTrue( - getDiamondAddress() != address(0), - "Diamond should be deployed" - ); - } - - /// @notice Test that diamond forwards calls via delegate call - function test_ForwardsCallsViaDelegateCall() public { - address diamondAddr = getDiamondAddress(); - address owner = getDiamondOwner(); - - vm.startPrank(owner); - - DiamondLoupeFacet diamondLoupe = new DiamondLoupeFacet(); - - // Check if DiamondLoupeFacet is already installed - bool loupeAlreadyInstalled = false; - try DiamondLoupeFacet(diamondAddr).facetAddresses() returns ( - address[] memory - ) { - loupeAlreadyInstalled = true; - } catch { - // Loupe not installed, which is expected for basic diamonds - } - - if (!loupeAlreadyInstalled) { - // prepare function selectors - bytes4[] memory functionSelectors = new bytes4[](4); - functionSelectors[0] = diamondLoupe.facets.selector; - functionSelectors[1] = diamondLoupe - .facetFunctionSelectors - .selector; - functionSelectors[2] = diamondLoupe.facetAddresses.selector; - functionSelectors[3] = diamondLoupe.facetAddress.selector; - - // prepare diamondCut - LibDiamond.FacetCut[] memory cuts = new LibDiamond.FacetCut[](1); - cuts[0] = LibDiamond.FacetCut({ - facetAddress: address(diamondLoupe), - action: LibDiamond.FacetCutAction.Add, - functionSelectors: functionSelectors - }); - - DiamondCutFacet(diamondAddr).diamondCut(cuts, address(0), ""); - } - - // Now the call should succeed - address[] memory facetAddresses = DiamondLoupeFacet(diamondAddr) - .facetAddresses(); - assertTrue( - facetAddresses.length > 0, - "Should have facets after adding DiamondLoupe" - ); - - vm.stopPrank(); - } - - /// @notice Test that diamond reverts on unknown function selectors - function test_RevertsOnUnknownFunctionSelector() public { - address diamondAddr = getDiamondAddress(); - - // Use a completely random selector that definitely doesn't exist - bytes memory callData = hex"deadbeef"; - - vm.expectRevert(FunctionDoesNotExist.selector); - (bool success, ) = diamondAddr.call(callData); - if (!success) { - vm.expectRevert("Diamond: Function does not exist"); - (bool success2, ) = diamondAddr.call(callData); - if (!success2) { - revert ShouldNotReachThisCode(); - } - } - } - - /// @notice Test that diamond can receive ETH - function test_CanReceiveETH() public { - address diamondAddr = getDiamondAddress(); - uint256 balanceBefore = diamondAddr.balance; - (bool success, ) = diamondAddr.call{ value: 1 ether }(""); - if (!success) revert ExternalCallFailed(); - - assertEq(address(diamond).balance, balanceBefore + 1 ether); - } -} diff --git a/test/solidity/utils/DiamondTestHelpers.sol b/test/solidity/utils/DiamondTestHelpers.sol new file mode 100644 index 000000000..775f91cf4 --- /dev/null +++ b/test/solidity/utils/DiamondTestHelpers.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.17; + +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { OwnershipFacet } from "lifi/Facets/OwnershipFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; +import { Test } from "forge-std/Test.sol"; +import { TestBaseRandomConstants } from "./TestBaseRandomConstants.sol"; + +/// @notice Minimal helper to compose a test Diamond and add facets/selectors for test scenarios. +/// @dev Provides overloads to add facets with or without init calldata. +/// This contract is used by higher-level LDA test scaffolding to assemble the test Diamond. +abstract contract DiamondTestHelpers is Test, TestBaseRandomConstants { + LibDiamond.FacetCut[] internal cut; + + /// @notice Adds standard Diamond Loupe selectors to the `cut` buffer. + /// @param _diamondLoupe Address of a deployed `DiamondLoupeFacet`. + /// @dev Call this before invoking diamondCut with the buffered `cut`. + function _addDiamondLoupeSelectors(address _diamondLoupe) internal { + bytes4[] memory functionSelectors = new bytes4[](5); + functionSelectors[0] = DiamondLoupeFacet + .facetFunctionSelectors + .selector; + functionSelectors[1] = DiamondLoupeFacet.facets.selector; + functionSelectors[2] = DiamondLoupeFacet.facetAddress.selector; + functionSelectors[3] = DiamondLoupeFacet.facetAddresses.selector; + functionSelectors[4] = DiamondLoupeFacet.supportsInterface.selector; + + cut.push( + LibDiamond.FacetCut({ + facetAddress: _diamondLoupe, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + } + + /// @notice Adds standard Ownership selectors to the `cut` buffer. + /// @param _ownership Address of a deployed `OwnershipFacet`. + /// @dev Call this before invoking diamondCut with the buffered `cut`. + function _addOwnershipSelectors(address _ownership) internal { + bytes4[] memory functionSelectors = new bytes4[](4); + functionSelectors[0] = OwnershipFacet.transferOwnership.selector; + functionSelectors[1] = OwnershipFacet.cancelOwnershipTransfer.selector; + functionSelectors[2] = OwnershipFacet + .confirmOwnershipTransfer + .selector; + functionSelectors[3] = OwnershipFacet.owner.selector; + + cut.push( + LibDiamond.FacetCut({ + facetAddress: _ownership, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: functionSelectors + }) + ); + } + + /// @notice Adds a facet and function selectors to the target diamond. + /// @param _diamond Address of the diamond proxy. + /// @param _facet Address of the facet implementation to add. + /// @param _selectors Function selectors to expose in the diamond. + /// @dev Convenience overload with no initializer; see the 5-arg overload for init flows. + function addFacet( + address _diamond, + address _facet, + bytes4[] memory _selectors + ) public virtual { + _addFacet(_diamond, _facet, _selectors, address(0), ""); + } + + /// @notice Adds a facet and function selectors to the target diamond, optionally executing an initializer. + /// @param _diamond Address of the diamond proxy. + /// @param _facet Address of the facet implementation to add. + /// @param _selectors Function selectors to expose in the diamond. + /// @param _init Address of an initializer (can be facet or another contract). + /// @param _initCallData ABI-encoded calldata for the initializer. + /// @dev Owner is impersonated via vm.startPrank for the duration of the diamondCut. + function addFacet( + address _diamond, + address _facet, + bytes4[] memory _selectors, + address _init, + bytes memory _initCallData + ) public virtual { + _addFacet(_diamond, _facet, _selectors, _init, _initCallData); + } + + /// @notice Performs diamondCut with an appended `FacetCut`. + /// @param _diamond Address of the diamond proxy. + /// @param _facet Address of the facet implementation to add. + /// @param _selectors Function selectors to expose in the diamond. + /// @param _init Address of an initializer (address(0) for none). + /// @param _initCallData ABI-encoded calldata for the initializer (empty if none). + /// @dev Example: + /// - Append loupe + ownership cuts first. + /// - Then call `_addFacet(diamond, address(myFacet), selectors, address(0), "")`. + function _addFacet( + address _diamond, + address _facet, + bytes4[] memory _selectors, + address _init, + bytes memory _initCallData + ) internal virtual { + vm.startPrank(OwnershipFacet(_diamond).owner()); + cut.push( + LibDiamond.FacetCut({ + facetAddress: _facet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: _selectors + }) + ); + + DiamondCutFacet(_diamond).diamondCut(cut, _init, _initCallData); + + delete cut; + vm.stopPrank(); + } +} From c3f6b6e6b605e2e78521c71e54b42460ddfadf3d Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 12:06:30 +0200 Subject: [PATCH 179/220] updates --- script/deploy/deployAllLDAContracts.sh | 2 - script/deploy/deploySingleContract.sh | 2 +- .../facets/LDA/utils/UpdateLDAScriptBase.sol | 4 +- .../deploy/facets/utils/BaseUpdateScript.sol | 360 ----------------- .../deploy/facets/utils/UpdateScriptBase.sol | 361 +++++++++++++++++- .../zksync/LDA/utils/UpdateLDAScriptBase.sol | 4 +- .../deploy/zksync/utils/BaseUpdateScript.sol | 351 ----------------- .../deploy/zksync/utils/UpdateScriptBase.sol | 348 ++++++++++++++++- 8 files changed, 708 insertions(+), 724 deletions(-) delete mode 100644 script/deploy/facets/utils/BaseUpdateScript.sol delete mode 100644 script/deploy/zksync/utils/BaseUpdateScript.sol diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 372dd1a55..289e5a0ce 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -176,8 +176,6 @@ deployAllLDAContracts() { # Deploy all non-core LDA facets and add to diamond for FACET_NAME in $(getContractNamesInFolder "$LDA_FACETS_PATH"); do - echo "[info] Processing LDA facet: $FACET_NAME" - echo "[info] Deploying and adding LDA facet: $FACET_NAME" # get current contract version diff --git a/script/deploy/deploySingleContract.sh b/script/deploy/deploySingleContract.sh index dea2b75d1..80a6c8c1c 100755 --- a/script/deploy/deploySingleContract.sh +++ b/script/deploy/deploySingleContract.sh @@ -2,7 +2,7 @@ # deploys a single contract # should be called like this: -# $(deploySingleContract "Executor" "BSC" "staging" "1.0.0" true false) +# $(deploySingleContract "Executor" "BSC" "staging" "1.0.0" true) deploySingleContract() { # load config & helper functions source script/config.sh diff --git a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol index 01fd8059e..d2680463f 100644 --- a/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/facets/LDA/utils/UpdateLDAScriptBase.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.17; import { stdJson } from "forge-std/StdJson.sol"; -import { BaseUpdateScript } from "../../utils/BaseUpdateScript.sol"; +import { UpdateScriptBase } from "../../utils/UpdateScriptBase.sol"; -contract UpdateLDAScriptBase is BaseUpdateScript { +contract UpdateLDAScriptBase is UpdateScriptBase { using stdJson for string; function _getDiamondAddress() internal override returns (address) { diff --git a/script/deploy/facets/utils/BaseUpdateScript.sol b/script/deploy/facets/utils/BaseUpdateScript.sol deleted file mode 100644 index 8231fad0f..000000000 --- a/script/deploy/facets/utils/BaseUpdateScript.sol +++ /dev/null @@ -1,360 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { ScriptBase } from "./ScriptBase.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; - -abstract contract BaseUpdateScript is ScriptBase { - using stdJson for string; - - error InvalidHexDigit(uint8 d); - - struct FunctionSignature { - string name; - bytes sig; - } - - address internal diamond; - LibDiamond.FacetCut[] internal cut; - bytes4[] internal selectorsToReplace; - bytes4[] internal selectorsToRemove; - bytes4[] internal selectorsToAdd; - DiamondCutFacet internal cutter; - DiamondLoupeFacet internal loupe; - string internal path; - string internal json; - bool internal noBroadcast = false; - bool internal useDefaultDiamond; - - constructor() { - useDefaultDiamond = _shouldUseDefaultDiamond(); - noBroadcast = vm.envOr("NO_BROADCAST", false); - - path = string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - json = vm.readFile(path); - diamond = _getDiamondAddress(); - cutter = DiamondCutFacet(diamond); - loupe = DiamondLoupeFacet(diamond); - } - - /// @notice Updates multiple core facets from global.json configuration - /// @param configKey The key in global.json to read facets from (e.g., ".coreFacets" or ".ldaCoreFacets") - /// @return facets Array of facet addresses after update - /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) - function updateCoreFacets( - string memory configKey - ) - internal - virtual - returns (address[] memory facets, bytes memory cutData) - { - // Read core facets dynamically from global.json config - string memory globalConfigPath = string.concat( - vm.projectRoot(), - "/config/global.json" - ); - string memory globalConfig = vm.readFile(globalConfigPath); - string[] memory coreFacets = globalConfig.readStringArray(configKey); - - emit log_uint(coreFacets.length); - - bytes4[] memory exclude; - - // Check if the loupe was already added to the diamond - bool loupeExists = _checkLoupeExists(); - - // Handle DiamondLoupeFacet separately as it needs special treatment - if (!loupeExists) { - _handleLoupeInstallation(); - } - - // Process all core facets dynamically - for (uint256 i = 0; i < coreFacets.length; i++) { - string memory facetName = coreFacets[i]; - - // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled - if (_shouldSkipCoreFacet(facetName)) { - continue; - } - - emit log(facetName); - - address facetAddress = _getConfigContractAddress( - path, - string.concat(".", facetName) - ); - - bytes4[] memory selectors = getSelectors(facetName, exclude); - - // at this point we know for sure that diamond loupe exists on diamond - buildDiamondCut(selectors, facetAddress); - } - - // Handle noBroadcast mode and broadcasting - return _finalizeCut(); - } - - /// @notice Checks if DiamondLoupeFacet exists on the diamond - /// @return loupeExists True if loupe exists, false otherwise - function _checkLoupeExists() internal virtual returns (bool loupeExists) { - try loupe.facetAddresses() returns (address[] memory) { - // If call was successful, loupe exists on diamond already - emit log("DiamondLoupeFacet exists on diamond already"); - loupeExists = true; - } catch { - // No need to do anything, just making sure that the flow continues in both cases with try/catch - } - } - - /// @notice Handles DiamondLoupeFacet installation if it doesn't exist - function _handleLoupeInstallation() internal virtual { - emit log("DiamondLoupeFacet does not exist on diamond yet"); - address diamondLoupeAddress = _getConfigContractAddress( - path, - ".DiamondLoupeFacet" - ); - bytes4[] memory loupeSelectors = getSelectors( - "DiamondLoupeFacet", - new bytes4[](0) - ); - - buildInitialCut(loupeSelectors, diamondLoupeAddress); - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - // Reset diamond cut variable to remove diamondLoupe information - delete cut; - } - - /// @notice Determines if a facet should be skipped during core facets update - /// @param facetName The name of the facet to check - /// @return True if facet should be skipped, false otherwise - function _shouldSkipCoreFacet( - string memory facetName - ) internal pure virtual returns (bool) { - return (keccak256(bytes(facetName)) == - keccak256(bytes("DiamondLoupeFacet")) || - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondCutFacet"))); - } - - /// @notice Finalizes the diamond cut operation - /// @return facets Array of facet addresses after update - /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) - function _finalizeCut() - internal - virtual - returns (address[] memory facets, bytes memory cutData) - { - // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - address(0), - "" - ); - } - emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); - emit log_bytes(cutData); - emit log("=== END CALLDATA ==="); - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - facets = loupe.facetAddresses(); - } - - function update( - string memory name - ) - internal - virtual - returns (address[] memory facets, bytes memory cutData) - { - address facet = json.readAddress(string.concat(".", name)); - - bytes4[] memory excludes = getExcludes(); - bytes memory callData = getCallData(); - - buildDiamondCut(getSelectors(name, excludes), facet); - - // prepare full diamondCut calldata and log for debugging purposes - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - callData.length > 0 ? facet : address(0), - callData - ); - - emit log("DiamondCutCalldata: "); - emit log_bytes(cutData); - } - - if (noBroadcast) { - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - - if (cut.length > 0) { - cutter.diamondCut( - cut, - callData.length > 0 ? facet : address(0), - callData - ); - } - - facets = loupe.facetAddresses(); - - vm.stopBroadcast(); - } - - function getExcludes() internal virtual returns (bytes4[] memory) {} - - function getCallData() internal virtual returns (bytes memory) {} - - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - - function buildDiamondCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - address oldFacet; - - selectorsToAdd = new bytes4[](0); - selectorsToReplace = new bytes4[](0); - selectorsToRemove = new bytes4[](0); - - // Get selectors to add or replace - for (uint256 i; i < newSelectors.length; i++) { - if (loupe.facetAddress(newSelectors[i]) == address(0)) { - selectorsToAdd.push(newSelectors[i]); - // Don't replace if the new facet address is the same as the old facet address - } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { - selectorsToReplace.push(newSelectors[i]); - oldFacet = loupe.facetAddress(newSelectors[i]); - } - } - - // Get selectors to remove - bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); - for (uint256 i; i < oldSelectors.length; i++) { - bool found = false; - for (uint256 j; j < newSelectors.length; j++) { - if (oldSelectors[i] == newSelectors[j]) { - found = true; - break; - } - } - if (!found) { - selectorsToRemove.push(oldSelectors[i]); - } - } - - // Build diamond cut - if (selectorsToReplace.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Replace, - functionSelectors: selectorsToReplace - }) - ); - } - - if (selectorsToRemove.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(0), - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: selectorsToRemove - }) - ); - } - - if (selectorsToAdd.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectorsToAdd - }) - ); - } - } - - function buildInitialCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: newSelectors - }) - ); - } - - function toHexDigit(uint8 d) internal pure returns (bytes1) { - if (0 <= d && d <= 9) { - return bytes1(uint8(bytes1("0")) + d); - } else if (10 <= uint8(d) && uint8(d) <= 15) { - return bytes1(uint8(bytes1("a")) + d - 10); - } - revert InvalidHexDigit(d); - } - - function fromCode(bytes4 code) public pure returns (string memory) { - bytes memory result = new bytes(10); - result[0] = bytes1("0"); - result[1] = bytes1("x"); - for (uint256 i = 0; i < 4; ++i) { - result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); - result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); - } - return string(result); - } - - // Abstract functions for customization - function _shouldUseDefaultDiamond() internal virtual returns (bool) { - return vm.envOr("USE_DEF_DIAMOND", true); - } - - function _getDiamondAddress() internal virtual returns (address); -} diff --git a/script/deploy/facets/utils/UpdateScriptBase.sol b/script/deploy/facets/utils/UpdateScriptBase.sol index 03b70d837..5b6a32bb2 100644 --- a/script/deploy/facets/utils/UpdateScriptBase.sol +++ b/script/deploy/facets/utils/UpdateScriptBase.sol @@ -1,25 +1,75 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { ScriptBase } from "./ScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; import { AccessManagerFacet } from "lifi/Facets/AccessManagerFacet.sol"; -import { BaseUpdateScript } from "./BaseUpdateScript.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -contract UpdateScriptBase is BaseUpdateScript { +abstract contract UpdateScriptBase is ScriptBase { using stdJson for string; + error InvalidHexDigit(uint8 d); + + struct FunctionSignature { + string name; + bytes sig; + } + struct Approval { address aTokenAddress; address bContractAddress; } - function _getDiamondAddress() internal override returns (address) { + address internal diamond; + LibDiamond.FacetCut[] internal cut; + bytes4[] internal selectorsToReplace; + bytes4[] internal selectorsToRemove; + bytes4[] internal selectorsToAdd; + DiamondCutFacet internal cutter; + DiamondLoupeFacet internal loupe; + string internal path; + string internal json; + bool internal noBroadcast = false; + bool internal useDefaultDiamond; + + constructor() { + useDefaultDiamond = _shouldUseDefaultDiamond(); + noBroadcast = vm.envOr("NO_BROADCAST", false); + + path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); + json = vm.readFile(path); + diamond = _getDiamondAddress(); + cutter = DiamondCutFacet(diamond); + loupe = DiamondLoupeFacet(diamond); + } + + /// @notice Gets the diamond address based on configuration + /// @dev Override this method to customize diamond address selection + function _getDiamondAddress() internal virtual returns (address) { + // Default implementation for regular UpdateScript behavior return useDefaultDiamond ? json.readAddress(".LiFiDiamond") : json.readAddress(".LiFiDiamondImmutable"); } + /// @notice Determines if default diamond should be used + /// @dev Override this method to customize diamond selection logic + function _shouldUseDefaultDiamond() internal virtual returns (bool) { + return vm.envOr("USE_DEF_DIAMOND", true); + } + + /// @notice Approves refund wallet for specific function signatures function approveRefundWallet() internal { // get refund wallet address from global config file string memory globalPath = string.concat(root, "/config/global.json"); @@ -48,6 +98,7 @@ contract UpdateScriptBase is BaseUpdateScript { } } + /// @notice Approves deployer wallet for specific function signatures function approveDeployerWallet() internal { // get deployer wallet address from global config file string memory globalPath = string.concat(root, "/config/global.json"); @@ -75,4 +126,308 @@ contract UpdateScriptBase is BaseUpdateScript { ); } } + + /// @notice Updates multiple core facets from global.json configuration + /// @param configKey The key in global.json to read facets from (e.g., ".coreFacets" or ".ldaCoreFacets") + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function updateCoreFacets( + string memory configKey + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // Read core facets dynamically from global.json config + string memory globalConfigPath = string.concat( + vm.projectRoot(), + "/config/global.json" + ); + string memory globalConfig = vm.readFile(globalConfigPath); + string[] memory coreFacets = globalConfig.readStringArray(configKey); + + emit log_uint(coreFacets.length); + + bytes4[] memory exclude; + + // Check if the loupe was already added to the diamond + bool loupeExists = _checkLoupeExists(); + + // Handle DiamondLoupeFacet separately as it needs special treatment + if (!loupeExists) { + _handleLoupeInstallation(); + } + + // Process all core facets dynamically + for (uint256 i = 0; i < coreFacets.length; i++) { + string memory facetName = coreFacets[i]; + + // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled + if (_shouldSkipCoreFacet(facetName)) { + continue; + } + + emit log(facetName); + + address facetAddress = _getConfigContractAddress( + path, + string.concat(".", facetName) + ); + + bytes4[] memory selectors = getSelectors(facetName, exclude); + + // at this point we know for sure that diamond loupe exists on diamond + buildDiamondCut(selectors, facetAddress); + } + + // Handle noBroadcast mode and broadcasting + return _finalizeCut(); + } + + /// @notice Checks if DiamondLoupeFacet exists on the diamond + /// @return loupeExists True if loupe exists, false otherwise + function _checkLoupeExists() internal virtual returns (bool loupeExists) { + try loupe.facetAddresses() returns (address[] memory) { + // If call was successful, loupe exists on diamond already + emit log("DiamondLoupeFacet exists on diamond already"); + loupeExists = true; + } catch { + // No need to do anything, just making sure that the flow continues in both cases with try/catch + } + } + + /// @notice Handles DiamondLoupeFacet installation if it doesn't exist + function _handleLoupeInstallation() internal virtual { + emit log("DiamondLoupeFacet does not exist on diamond yet"); + address diamondLoupeAddress = _getConfigContractAddress( + path, + ".DiamondLoupeFacet" + ); + bytes4[] memory loupeSelectors = getSelectors( + "DiamondLoupeFacet", + new bytes4[](0) + ); + + buildInitialCut(loupeSelectors, diamondLoupeAddress); + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + // Reset diamond cut variable to remove diamondLoupe information + delete cut; + } + + /// @notice Determines if a facet should be skipped during core facets update + /// @param facetName The name of the facet to check + /// @return True if facet should be skipped, false otherwise + function _shouldSkipCoreFacet( + string memory facetName + ) internal pure virtual returns (bool) { + return (keccak256(bytes(facetName)) == + keccak256(bytes("DiamondLoupeFacet")) || + keccak256(bytes(facetName)) == + keccak256(bytes("DiamondCutFacet"))); + } + + /// @notice Finalizes the diamond cut operation + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function _finalizeCut() + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + address(0), + "" + ); + } + emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); + emit log_bytes(cutData); + emit log("=== END CALLDATA ==="); + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + facets = loupe.facetAddresses(); + } + + function update( + string memory name + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + address facet = json.readAddress(string.concat(".", name)); + + bytes4[] memory excludes = getExcludes(); + bytes memory callData = getCallData(); + + buildDiamondCut(getSelectors(name, excludes), facet); + + // prepare full diamondCut calldata and log for debugging purposes + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + callData.length > 0 ? facet : address(0), + callData + ); + + emit log("DiamondCutCalldata: "); + emit log_bytes(cutData); + } + + if (noBroadcast) { + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + + if (cut.length > 0) { + cutter.diamondCut( + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } + + function getExcludes() internal virtual returns (bytes4[] memory) {} + + function getCallData() internal virtual returns (bytes memory) {} + + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/facets/utils/contract-selectors.sh"; + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + + function buildDiamondCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + address oldFacet; + + selectorsToAdd = new bytes4[](0); + selectorsToReplace = new bytes4[](0); + selectorsToRemove = new bytes4[](0); + + // Get selectors to add or replace + for (uint256 i; i < newSelectors.length; i++) { + if (loupe.facetAddress(newSelectors[i]) == address(0)) { + selectorsToAdd.push(newSelectors[i]); + // Don't replace if the new facet address is the same as the old facet address + } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { + selectorsToReplace.push(newSelectors[i]); + oldFacet = loupe.facetAddress(newSelectors[i]); + } + } + + // Get selectors to remove + bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); + for (uint256 i; i < oldSelectors.length; i++) { + bool found = false; + for (uint256 j; j < newSelectors.length; j++) { + if (oldSelectors[i] == newSelectors[j]) { + found = true; + break; + } + } + if (!found) { + selectorsToRemove.push(oldSelectors[i]); + } + } + + // Build diamond cut + if (selectorsToReplace.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Replace, + functionSelectors: selectorsToReplace + }) + ); + } + + if (selectorsToRemove.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(0), + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: selectorsToRemove + }) + ); + } + + if (selectorsToAdd.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectorsToAdd + }) + ); + } + } + + function buildInitialCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: newSelectors + }) + ); + } + + function toHexDigit(uint8 d) internal pure returns (bytes1) { + if (0 <= d && d <= 9) { + return bytes1(uint8(bytes1("0")) + d); + } else if (10 <= uint8(d) && uint8(d) <= 15) { + return bytes1(uint8(bytes1("a")) + d - 10); + } + revert InvalidHexDigit(d); + } + + function fromCode(bytes4 code) public pure returns (string memory) { + bytes memory result = new bytes(10); + result[0] = bytes1("0"); + result[1] = bytes1("x"); + for (uint256 i = 0; i < 4; ++i) { + result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); + result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); + } + return string(result); + } } diff --git a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol index bbf48c4e4..762fc4285 100644 --- a/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol +++ b/script/deploy/zksync/LDA/utils/UpdateLDAScriptBase.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.17; import { stdJson } from "forge-std/StdJson.sol"; -import { BaseUpdateScript } from "../../utils/BaseUpdateScript.sol"; +import { UpdateScriptBase } from "../../utils/UpdateScriptBase.sol"; -contract UpdateLDAScriptBase is BaseUpdateScript { +contract UpdateLDAScriptBase is UpdateScriptBase { using stdJson for string; function _getDiamondAddress() internal override returns (address) { diff --git a/script/deploy/zksync/utils/BaseUpdateScript.sol b/script/deploy/zksync/utils/BaseUpdateScript.sol deleted file mode 100644 index db6934722..000000000 --- a/script/deploy/zksync/utils/BaseUpdateScript.sol +++ /dev/null @@ -1,351 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity ^0.8.17; - -import { ScriptBase } from "./ScriptBase.sol"; -import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; -import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; -import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; - -abstract contract BaseUpdateScript is ScriptBase { - using stdJson for string; - - struct FunctionSignature { - string name; - bytes sig; - } - - address internal diamond; - LibDiamond.FacetCut[] internal cut; - bytes4[] internal selectorsToReplace; - bytes4[] internal selectorsToRemove; - bytes4[] internal selectorsToAdd; - DiamondCutFacet internal cutter; - DiamondLoupeFacet internal loupe; - string internal path; - string internal json; - bool internal noBroadcast = false; - bool internal useDefaultDiamond; - - error FailedToConvert(); - - constructor() { - useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); - noBroadcast = vm.envOr("NO_BROADCAST", false); - - path = string.concat( - root, - "/deployments/", - network, - ".", - fileSuffix, - "json" - ); - json = vm.readFile(path); - diamond = _getDiamondAddress(); - cutter = DiamondCutFacet(diamond); - loupe = DiamondLoupeFacet(diamond); - } - - function update( - string memory name - ) - internal - virtual - returns (address[] memory facets, bytes memory cutData) - { - address facet = json.readAddress(string.concat(".", name)); - - bytes4[] memory excludes = getExcludes(); - bytes memory callData = getCallData(); - - buildDiamondCut(getSelectors(name, excludes), facet); - - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - callData.length > 0 ? facet : address(0), - callData - ); - } - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - - if (cut.length > 0) { - cutter.diamondCut( - cut, - callData.length > 0 ? facet : address(0), - callData - ); - } - - facets = loupe.facetAddresses(); - - vm.stopBroadcast(); - } - - /// @notice Updates multiple core facets from global.json configuration - /// @param configKey The key in global.json to read facets from (e.g., ".coreFacets" or ".ldaCoreFacets") - /// @return facets Array of facet addresses after update - /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) - function updateCoreFacets( - string memory configKey - ) - internal - virtual - returns (address[] memory facets, bytes memory cutData) - { - // Read core facets dynamically from global.json config - string memory globalConfigPath = string.concat( - vm.projectRoot(), - "/config/global.json" - ); - string memory globalConfig = vm.readFile(globalConfigPath); - string[] memory coreFacets = globalConfig.readStringArray(configKey); - - emit log_uint(coreFacets.length); - - bytes4[] memory exclude; - - // Check if the loupe was already added to the diamond - bool loupeExists = _checkLoupeExists(); - - // Handle DiamondLoupeFacet separately as it needs special treatment - if (!loupeExists) { - _handleLoupeInstallation(); - } - - // Process all core facets dynamically - for (uint256 i = 0; i < coreFacets.length; i++) { - string memory facetName = coreFacets[i]; - - // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled - if (_shouldSkipCoreFacet(facetName)) { - continue; - } - - emit log(facetName); - - address facetAddress = _getConfigContractAddress( - path, - string.concat(".", facetName) - ); - - bytes4[] memory selectors = getSelectors(facetName, exclude); - - // at this point we know for sure that diamond loupe exists on diamond - buildDiamondCut(selectors, facetAddress); - } - - // Handle noBroadcast mode and broadcasting - return _finalizeCut(); - } - - /// @notice Checks if DiamondLoupeFacet exists on the diamond - /// @return loupeExists True if loupe exists, false otherwise - function _checkLoupeExists() internal virtual returns (bool loupeExists) { - try loupe.facetAddresses() returns (address[] memory) { - // If call was successful, loupe exists on diamond already - emit log("DiamondLoupeFacet exists on diamond already"); - loupeExists = true; - } catch { - // No need to do anything, just making sure that the flow continues in both cases with try/catch - } - } - - /// @notice Handles DiamondLoupeFacet installation if it doesn't exist - function _handleLoupeInstallation() internal virtual { - emit log("DiamondLoupeFacet does not exist on diamond yet"); - address diamondLoupeAddress = _getConfigContractAddress( - path, - ".DiamondLoupeFacet" - ); - bytes4[] memory loupeSelectors = getSelectors( - "DiamondLoupeFacet", - new bytes4[](0) - ); - - buildInitialCut(loupeSelectors, diamondLoupeAddress); - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - // Reset diamond cut variable to remove diamondLoupe information - delete cut; - } - - /// @notice Determines if a facet should be skipped during core facets update - /// @param facetName The name of the facet to check - /// @return True if facet should be skipped, false otherwise - function _shouldSkipCoreFacet( - string memory facetName - ) internal pure virtual returns (bool) { - return (keccak256(bytes(facetName)) == - keccak256(bytes("DiamondLoupeFacet")) || - keccak256(bytes(facetName)) == - keccak256(bytes("DiamondCutFacet"))); - } - - /// @notice Finalizes the diamond cut operation - /// @return facets Array of facet addresses after update - /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) - function _finalizeCut() - internal - virtual - returns (address[] memory facets, bytes memory cutData) - { - // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - address(0), - "" - ); - } - emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); - emit log_bytes(cutData); - emit log("=== END CALLDATA ==="); - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut(cut, address(0), ""); - } - vm.stopBroadcast(); - - facets = loupe.facetAddresses(); - } - - function getExcludes() internal virtual returns (bytes4[] memory) {} - - function getCallData() internal virtual returns (bytes memory) {} - - function getSelectors( - string memory _facetName, - bytes4[] memory _exclude - ) internal returns (bytes4[] memory selectors) { - string[] memory cmd = new string[](3); - cmd[0] = "script/deploy/zksync/utils/contract-selectors.sh"; - cmd[1] = _facetName; - string memory exclude; - for (uint256 i; i < _exclude.length; i++) { - exclude = string.concat(exclude, fromCode(_exclude[i]), " "); - } - cmd[2] = exclude; - bytes memory res = vm.ffi(cmd); - selectors = abi.decode(res, (bytes4[])); - } - - function buildDiamondCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - address oldFacet; - - selectorsToAdd = new bytes4[](0); - selectorsToReplace = new bytes4[](0); - selectorsToRemove = new bytes4[](0); - - // Get selectors to add or replace - for (uint256 i; i < newSelectors.length; i++) { - if (loupe.facetAddress(newSelectors[i]) == address(0)) { - selectorsToAdd.push(newSelectors[i]); - // Don't replace if the new facet address is the same as the old facet address - } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { - selectorsToReplace.push(newSelectors[i]); - oldFacet = loupe.facetAddress(newSelectors[i]); - } - } - - // Get selectors to remove - bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); - for (uint256 i; i < oldSelectors.length; i++) { - bool found = false; - for (uint256 j; j < newSelectors.length; j++) { - if (oldSelectors[i] == newSelectors[j]) { - found = true; - break; - } - } - if (!found) { - selectorsToRemove.push(oldSelectors[i]); - } - } - - // Build diamond cut - if (selectorsToReplace.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Replace, - functionSelectors: selectorsToReplace - }) - ); - } - - if (selectorsToRemove.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: address(0), - action: LibDiamond.FacetCutAction.Remove, - functionSelectors: selectorsToRemove - }) - ); - } - - if (selectorsToAdd.length > 0) { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: selectorsToAdd - }) - ); - } - } - - function buildInitialCut( - bytes4[] memory newSelectors, - address newFacet - ) internal { - cut.push( - LibDiamond.FacetCut({ - facetAddress: newFacet, - action: LibDiamond.FacetCutAction.Add, - functionSelectors: newSelectors - }) - ); - } - - function toHexDigit(uint8 d) internal pure returns (bytes1) { - if (0 <= d && d <= 9) { - return bytes1(uint8(bytes1("0")) + d); - } else if (10 <= uint8(d) && uint8(d) <= 15) { - return bytes1(uint8(bytes1("a")) + d - 10); - } - revert FailedToConvert(); - } - - function fromCode(bytes4 code) public pure returns (string memory) { - bytes memory result = new bytes(10); - result[0] = bytes1("0"); - result[1] = bytes1("x"); - for (uint256 i = 0; i < 4; ++i) { - result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); - result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); - } - return string(result); - } - - // Abstract functions for customization - function _getDiamondAddress() internal virtual returns (address); -} diff --git a/script/deploy/zksync/utils/UpdateScriptBase.sol b/script/deploy/zksync/utils/UpdateScriptBase.sol index ab5e869c5..efe6bca4b 100644 --- a/script/deploy/zksync/utils/UpdateScriptBase.sol +++ b/script/deploy/zksync/utils/UpdateScriptBase.sol @@ -1,16 +1,358 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.17; +import { ScriptBase } from "./ScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { BaseUpdateScript } from "./BaseUpdateScript.sol"; +import { DiamondCutFacet } from "lifi/Facets/DiamondCutFacet.sol"; +import { DiamondLoupeFacet } from "lifi/Facets/DiamondLoupeFacet.sol"; +import { LibDiamond } from "lifi/Libraries/LibDiamond.sol"; -contract UpdateScriptBase is BaseUpdateScript { +abstract contract UpdateScriptBase is ScriptBase { using stdJson for string; - function _getDiamondAddress() internal override returns (address) { + struct FunctionSignature { + string name; + bytes sig; + } + + address internal diamond; + LibDiamond.FacetCut[] internal cut; + bytes4[] internal selectorsToReplace; + bytes4[] internal selectorsToRemove; + bytes4[] internal selectorsToAdd; + DiamondCutFacet internal cutter; + DiamondLoupeFacet internal loupe; + string internal path; + string internal json; + bool internal noBroadcast = false; + bool internal useDefaultDiamond; + + error FailedToConvert(); + + constructor() { + useDefaultDiamond = vm.envBool("USE_DEF_DIAMOND"); + noBroadcast = vm.envOr("NO_BROADCAST", false); + + path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); + json = vm.readFile(path); + diamond = _getDiamondAddress(); + cutter = DiamondCutFacet(diamond); + loupe = DiamondLoupeFacet(diamond); + } + + /// @notice Gets the diamond address based on configuration + /// @dev Override this method to customize diamond address selection + function _getDiamondAddress() internal virtual returns (address) { + // Default implementation for regular UpdateScript behavior return useDefaultDiamond ? json.readAddress(".LiFiDiamond") : json.readAddress(".LiFiDiamondImmutable"); } + + function update( + string memory name + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + address facet = json.readAddress(string.concat(".", name)); + + bytes4[] memory excludes = getExcludes(); + bytes memory callData = getCallData(); + + buildDiamondCut(getSelectors(name, excludes), facet); + + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + + if (cut.length > 0) { + cutter.diamondCut( + cut, + callData.length > 0 ? facet : address(0), + callData + ); + } + + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } + + /// @notice Updates multiple core facets from global.json configuration + /// @param configKey The key in global.json to read facets from (e.g., ".coreFacets" or ".ldaCoreFacets") + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function updateCoreFacets( + string memory configKey + ) + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // Read core facets dynamically from global.json config + string memory globalConfigPath = string.concat( + vm.projectRoot(), + "/config/global.json" + ); + string memory globalConfig = vm.readFile(globalConfigPath); + string[] memory coreFacets = globalConfig.readStringArray(configKey); + + emit log_uint(coreFacets.length); + + bytes4[] memory exclude; + + // Check if the loupe was already added to the diamond + bool loupeExists = _checkLoupeExists(); + + // Handle DiamondLoupeFacet separately as it needs special treatment + if (!loupeExists) { + _handleLoupeInstallation(); + } + + // Process all core facets dynamically + for (uint256 i = 0; i < coreFacets.length; i++) { + string memory facetName = coreFacets[i]; + + // Skip DiamondCutFacet and DiamondLoupeFacet as they were already handled + if (_shouldSkipCoreFacet(facetName)) { + continue; + } + + emit log(facetName); + + address facetAddress = _getConfigContractAddress( + path, + string.concat(".", facetName) + ); + + bytes4[] memory selectors = getSelectors(facetName, exclude); + + // at this point we know for sure that diamond loupe exists on diamond + buildDiamondCut(selectors, facetAddress); + } + + // Handle noBroadcast mode and broadcasting + return _finalizeCut(); + } + + /// @notice Checks if DiamondLoupeFacet exists on the diamond + /// @return loupeExists True if loupe exists, false otherwise + function _checkLoupeExists() internal virtual returns (bool loupeExists) { + try loupe.facetAddresses() returns (address[] memory) { + // If call was successful, loupe exists on diamond already + emit log("DiamondLoupeFacet exists on diamond already"); + loupeExists = true; + } catch { + // No need to do anything, just making sure that the flow continues in both cases with try/catch + } + } + + /// @notice Handles DiamondLoupeFacet installation if it doesn't exist + function _handleLoupeInstallation() internal virtual { + emit log("DiamondLoupeFacet does not exist on diamond yet"); + address diamondLoupeAddress = _getConfigContractAddress( + path, + ".DiamondLoupeFacet" + ); + bytes4[] memory loupeSelectors = getSelectors( + "DiamondLoupeFacet", + new bytes4[](0) + ); + + buildInitialCut(loupeSelectors, diamondLoupeAddress); + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + // Reset diamond cut variable to remove diamondLoupe information + delete cut; + } + + /// @notice Determines if a facet should be skipped during core facets update + /// @param facetName The name of the facet to check + /// @return True if facet should be skipped, false otherwise + function _shouldSkipCoreFacet( + string memory facetName + ) internal pure virtual returns (bool) { + return (keccak256(bytes(facetName)) == + keccak256(bytes("DiamondLoupeFacet")) || + keccak256(bytes(facetName)) == + keccak256(bytes("DiamondCutFacet"))); + } + + /// @notice Finalizes the diamond cut operation + /// @return facets Array of facet addresses after update + /// @return cutData Encoded diamond cut calldata (if noBroadcast is true) + function _finalizeCut() + internal + virtual + returns (address[] memory facets, bytes memory cutData) + { + // If noBroadcast is activated, we only prepare calldata for sending it to multisig SAFE + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + address(0), + "" + ); + } + emit log("=== DIAMOND CUT CALLDATA FOR MANUAL EXECUTION ==="); + emit log_bytes(cutData); + emit log("=== END CALLDATA ==="); + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut(cut, address(0), ""); + } + vm.stopBroadcast(); + + facets = loupe.facetAddresses(); + } + + function getExcludes() internal virtual returns (bytes4[] memory) {} + + function getCallData() internal virtual returns (bytes memory) {} + + function getSelectors( + string memory _facetName, + bytes4[] memory _exclude + ) internal returns (bytes4[] memory selectors) { + string[] memory cmd = new string[](3); + cmd[0] = "script/deploy/zksync/utils/contract-selectors.sh"; + cmd[1] = _facetName; + string memory exclude; + for (uint256 i; i < _exclude.length; i++) { + exclude = string.concat(exclude, fromCode(_exclude[i]), " "); + } + cmd[2] = exclude; + bytes memory res = vm.ffi(cmd); + selectors = abi.decode(res, (bytes4[])); + } + + function buildDiamondCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + address oldFacet; + + selectorsToAdd = new bytes4[](0); + selectorsToReplace = new bytes4[](0); + selectorsToRemove = new bytes4[](0); + + // Get selectors to add or replace + for (uint256 i; i < newSelectors.length; i++) { + if (loupe.facetAddress(newSelectors[i]) == address(0)) { + selectorsToAdd.push(newSelectors[i]); + // Don't replace if the new facet address is the same as the old facet address + } else if (loupe.facetAddress(newSelectors[i]) != newFacet) { + selectorsToReplace.push(newSelectors[i]); + oldFacet = loupe.facetAddress(newSelectors[i]); + } + } + + // Get selectors to remove + bytes4[] memory oldSelectors = loupe.facetFunctionSelectors(oldFacet); + for (uint256 i; i < oldSelectors.length; i++) { + bool found = false; + for (uint256 j; j < newSelectors.length; j++) { + if (oldSelectors[i] == newSelectors[j]) { + found = true; + break; + } + } + if (!found) { + selectorsToRemove.push(oldSelectors[i]); + } + } + + // Build diamond cut + if (selectorsToReplace.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Replace, + functionSelectors: selectorsToReplace + }) + ); + } + + if (selectorsToRemove.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: address(0), + action: LibDiamond.FacetCutAction.Remove, + functionSelectors: selectorsToRemove + }) + ); + } + + if (selectorsToAdd.length > 0) { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: selectorsToAdd + }) + ); + } + } + + function buildInitialCut( + bytes4[] memory newSelectors, + address newFacet + ) internal { + cut.push( + LibDiamond.FacetCut({ + facetAddress: newFacet, + action: LibDiamond.FacetCutAction.Add, + functionSelectors: newSelectors + }) + ); + } + + function toHexDigit(uint8 d) internal pure returns (bytes1) { + if (0 <= d && d <= 9) { + return bytes1(uint8(bytes1("0")) + d); + } else if (10 <= uint8(d) && uint8(d) <= 15) { + return bytes1(uint8(bytes1("a")) + d - 10); + } + revert FailedToConvert(); + } + + function fromCode(bytes4 code) public pure returns (string memory) { + bytes memory result = new bytes(10); + result[0] = bytes1("0"); + result[1] = bytes1("x"); + for (uint256 i = 0; i < 4; ++i) { + result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16); + result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16); + } + return string(result); + } } From 355b03c3ab1f5025394065287b1a77457cb0a9a9 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 12:15:41 +0200 Subject: [PATCH 180/220] Refactor UpdateScriptBase to use _getConfigContractAddress for facet retrieval --- script/deploy/facets/utils/UpdateScriptBase.sol | 5 ++++- script/deploy/zksync/utils/UpdateScriptBase.sol | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/script/deploy/facets/utils/UpdateScriptBase.sol b/script/deploy/facets/utils/UpdateScriptBase.sol index 5b6a32bb2..de5edd444 100644 --- a/script/deploy/facets/utils/UpdateScriptBase.sol +++ b/script/deploy/facets/utils/UpdateScriptBase.sol @@ -271,7 +271,10 @@ abstract contract UpdateScriptBase is ScriptBase { virtual returns (address[] memory facets, bytes memory cutData) { - address facet = json.readAddress(string.concat(".", name)); + address facet = _getConfigContractAddress( + path, + string.concat(".", name) + ); bytes4[] memory excludes = getExcludes(); bytes memory callData = getCallData(); diff --git a/script/deploy/zksync/utils/UpdateScriptBase.sol b/script/deploy/zksync/utils/UpdateScriptBase.sol index efe6bca4b..0c649fe8d 100644 --- a/script/deploy/zksync/utils/UpdateScriptBase.sol +++ b/script/deploy/zksync/utils/UpdateScriptBase.sol @@ -64,7 +64,10 @@ abstract contract UpdateScriptBase is ScriptBase { virtual returns (address[] memory facets, bytes memory cutData) { - address facet = json.readAddress(string.concat(".", name)); + address facet = _getConfigContractAddress( + path, + string.concat(".", name) + ); bytes4[] memory excludes = getExcludes(); bytes memory callData = getCallData(); From 6b57ceeab2971280a8e3ccb4dc5d742a174e66f3 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 20:01:46 +0200 Subject: [PATCH 181/220] Refactor getRpcUrl function to improve RPC URL retrieval from environment variables and fallback to networks.json; update ldaHealthCheck script to utilize the new getRpcUrl implementation. --- script/demoScripts/utils/demoScriptHelpers.ts | 26 ++++++++++++++++--- script/deploy/ldaHealthCheck.ts | 13 +++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/script/demoScripts/utils/demoScriptHelpers.ts b/script/demoScripts/utils/demoScriptHelpers.ts index ead84342c..918eba123 100644 --- a/script/demoScripts/utils/demoScriptHelpers.ts +++ b/script/demoScripts/utils/demoScriptHelpers.ts @@ -595,12 +595,30 @@ const normalizePrivateKey = (pk: string): `0x${string}` => { } /** - * Return the correct RPC environment variable - * (e.g. `ETH_NODE_URI_ARBITRUM` or `ETH_NODE_URI_MAINNET`) + * Get RPC URL for a chain, with fallback from environment variable to networks.json + * @param chain - The supported chain name + * @returns RPC URL string */ -const getRpcUrl = (chain: SupportedChain) => { +export const getRpcUrl = (chain: SupportedChain): string => { + // First, try to get from environment variable const envKey = `ETH_NODE_URI_${chain.toUpperCase()}` - return getEnvVar(envKey) + const envRpcUrl = process.env[envKey] + + if (envRpcUrl) { + return envRpcUrl + } + + // Fallback to networks.json + const networksConfig = networks as Record + const networkRpcUrl = networksConfig[chain.toLowerCase()]?.rpcUrl + + if (networkRpcUrl) { + return networkRpcUrl + } + + throw new Error( + `No RPC URL found for chain '${chain}' in environment variable '${envKey}' or networks.json` + ) } /** diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index 3c88eaf69..184f4604e 100755 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -11,18 +11,11 @@ import { execSync } from 'child_process' import { defineCommand, runMain } from 'citty' import { consola } from 'consola' -import type { SupportedChain } from '../common/types.js' +import type { SupportedChain } from '../common/types' +import { getRpcUrl } from '../demoScripts/utils/demoScriptHelpers' const errors: string[] = [] -// Helper function to get RPC URL from networks.json -const getRpcUrl = ( - network: string, - networksConfig: Record -): string => { - return networksConfig[network.toLowerCase()]?.rpcUrl || '' -} - // Helper function to check if contract is deployed using cast const checkIsDeployedWithCast = async ( contractName: string, @@ -195,7 +188,7 @@ const main = defineCommand({ const ldaCoreFacets = globalConfig.ldaCoreFacets || [] // Get RPC URL - const rpcUrl = getRpcUrl(network, networksConfig) + const rpcUrl = getRpcUrl(network as SupportedChain) if (!rpcUrl) { consola.error(`No RPC URL found for network: ${network}`) process.exit(1) From f1897b50b31bc090ee15377ab58c7b80e17302b4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 21:07:16 +0200 Subject: [PATCH 182/220] updated, dont mix files between environments --- script/deploy/ldaHealthCheck.ts | 85 +++++++++++++-------------------- 1 file changed, 33 insertions(+), 52 deletions(-) diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index 184f4604e..517c1915b 100755 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -82,10 +82,9 @@ const main = defineCommand({ process.exit(1) } - // Load main deployment file (contains LiFiDEXAggregatorDiamond address and shared infrastructure) - // For staging, try environment-specific file first, then fallback to main - let mainDeployedContracts: Record = {} - const mainDeploymentFile = `../../deployments/${network.toLowerCase()}.json` + // Load deployment file (contains LiFiDEXAggregatorDiamond address and shared infrastructure) + let deployedContracts: Record = {} + const productionDeploymentFile = `../../deployments/${network.toLowerCase()}.json` if (environment === 'staging') { const stagingFile = `../../deployments/${network.toLowerCase()}.staging.json` @@ -94,63 +93,45 @@ const main = defineCommand({ const { default: contracts } = await import(stagingFile, { with: { type: 'json' }, }) - mainDeployedContracts = contracts + deployedContracts = contracts consola.info( `Successfully loaded ${ Object.keys(contracts).length } contracts from staging file` ) - consola.info( - `LiFiDEXAggregatorDiamond address: ${ - contracts['LiFiDEXAggregatorDiamond'] || 'NOT FOUND' - }` - ) } catch (error) { - consola.warn(`Failed to load staging file: ${error}`) - // Fallback to main deployment file for staging - try { - consola.info( - `Falling back to main deployment file: ${mainDeploymentFile}` - ) - const { default: contracts } = await import(mainDeploymentFile, { - with: { type: 'json' }, - }) - mainDeployedContracts = contracts - consola.info( - `Successfully loaded ${ - Object.keys(contracts).length - } contracts from main file` - ) - consola.info( - `LiFiDEXAggregatorDiamond address: ${ - contracts['LiFiDEXAggregatorDiamond'] || 'NOT FOUND' - }` - ) - } catch (fallbackError) { - consola.error( - `Failed to load deployment files: ${stagingFile} and ${mainDeploymentFile}` - ) - consola.error( - 'Cannot verify LDA diamond and core facets availability.' - ) - process.exit(1) - } + consola.error(`Failed to load staging deployment file: ${stagingFile}`) + consola.error(`Error: ${error}`) + consola.error( + 'Cannot proceed with staging environment without staging deployment file.' + ) + process.exit(1) } } - // Production - use main deployment file - else + // Production - use production deployment file + else { + consola.info( + `Loading production deployment file: ${productionDeploymentFile}` + ) try { - const { default: contracts } = await import(mainDeploymentFile, { + const { default: contracts } = await import(productionDeploymentFile, { with: { type: 'json' }, }) - mainDeployedContracts = contracts + deployedContracts = contracts + consola.info( + `Successfully loaded ${ + Object.keys(contracts).length + } contracts from production file` + ) } catch (error) { consola.error( - `Failed to load main deployment file: ${mainDeploymentFile}` + `Failed to load production deployment file: ${productionDeploymentFile}` ) + consola.error(`Error: ${error}`) consola.error('Cannot verify LDA diamond and core facets availability.') process.exit(1) } + } // Load LDA-specific facet information const ldaDeploymentFile = @@ -203,13 +184,13 @@ const main = defineCommand({ // ╰─────────────────────────────────────────────────────────╯ consola.box('Checking LDA diamond contract full deployment...') - const diamondAddress = mainDeployedContracts['LiFiDEXAggregatorDiamond'] + const diamondAddress = deployedContracts['LiFiDEXAggregatorDiamond'] consola.info( `Looking for LiFiDEXAggregatorDiamond at address: ${diamondAddress}` ) consola.info( - `Available contracts in mainDeployedContracts: ${Object.keys( - mainDeployedContracts + `Available contracts in deployedContracts: ${Object.keys( + deployedContracts ) .filter((k) => k.includes('LiFi')) .join(', ')}` @@ -217,7 +198,7 @@ const main = defineCommand({ const diamondDeployed = await checkIsDeployedWithCast( 'LiFiDEXAggregatorDiamond', - mainDeployedContracts, + deployedContracts, rpcUrl ) @@ -238,7 +219,7 @@ const main = defineCommand({ // Check if core facet is deployed in regular deployment file (shared with LiFi Diamond) const isDeployed = await checkIsDeployedWithCast( facet, - mainDeployedContracts, + deployedContracts, rpcUrl ) @@ -273,13 +254,13 @@ const main = defineCommand({ if (Array.isArray(onChainFacets)) { // Create mapping from addresses to facet names - // For core facets, use addresses from main deployment file + // For core facets, use addresses from production deployment file // For non-core facets, use addresses from LDA deployment file const configFacetsByAddress: Record = {} - // Add core facets from main deployment file + // Add core facets from production deployment file for (const facet of ldaCoreFacets) { - const address = mainDeployedContracts[facet] + const address = deployedContracts[facet] if (address) configFacetsByAddress[address.toLowerCase()] = facet } From c19904e6ee7fe420c39c3e00762e30eadd97e37c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 21:11:43 +0200 Subject: [PATCH 183/220] check ownership only for production env --- script/deploy/ldaHealthCheck.ts | 64 +++++++++++++++------------------ 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/script/deploy/ldaHealthCheck.ts b/script/deploy/ldaHealthCheck.ts index 517c1915b..c92c8ddc2 100755 --- a/script/deploy/ldaHealthCheck.ts +++ b/script/deploy/ldaHealthCheck.ts @@ -307,57 +307,51 @@ const main = defineCommand({ // ╭─────────────────────────────────────────────────────────╮ // │ Check LDA Diamond ownership │ // ╰─────────────────────────────────────────────────────────╯ - consola.box('Checking LDA Diamond ownership...') - try { - const owner = execSync( - `cast call "${diamondAddress}" "owner() returns (address)" --rpc-url "${rpcUrl}"`, - { encoding: 'utf8', stdio: 'pipe' } - ).trim() + if (environment === 'production') { + consola.box('Checking LDA Diamond ownership...') - consola.info(`LiFiDEXAggregatorDiamond current owner: ${owner}`) + try { + const owner = execSync( + `cast call "${diamondAddress}" "owner() returns (address)" --rpc-url "${rpcUrl}"`, + { encoding: 'utf8', stdio: 'pipe' } + ).trim() - // Get expected multisig address from networks.json - const expectedMultisigAddress = - networksConfig[network.toLowerCase() as SupportedChain]?.safeAddress + consola.info(`LiFiDEXAggregatorDiamond current owner: ${owner}`) - if (!expectedMultisigAddress) { - if (environment === 'production') { + // Get expected multisig address from networks.json + const expectedMultisigAddress = + networksConfig[network.toLowerCase() as SupportedChain]?.safeAddress + + if (!expectedMultisigAddress) { logError( `No multisig address (safeAddress) found in networks.json for network ${network}` ) } else { - consola.warn( - `No multisig address (safeAddress) found in networks.json for network ${network}` + consola.info( + `Expected multisig address from networks.json: ${expectedMultisigAddress}` ) - consola.info('For staging environment, this is acceptable') - } - } else { - consola.info( - `Expected multisig address from networks.json: ${expectedMultisigAddress}` - ) - if (owner.toLowerCase() === expectedMultisigAddress.toLowerCase()) { - consola.success( - `✅ LiFiDEXAggregatorDiamond is correctly owned by multisig: ${expectedMultisigAddress}` - ) - } else { - if (environment === 'production') { - logError( - `❌ LiFiDEXAggregatorDiamond ownership mismatch! Current owner: ${owner}, Expected multisig: ${expectedMultisigAddress}` + if (owner.toLowerCase() === expectedMultisigAddress.toLowerCase()) { + consola.success( + `✅ LiFiDEXAggregatorDiamond is correctly owned by multisig: ${expectedMultisigAddress}` ) } else { - consola.info( - 'For staging environment, ownership transfer to multisig is not done' + logError( + `❌ LiFiDEXAggregatorDiamond ownership mismatch! Current owner: ${owner}, Expected multisig: ${expectedMultisigAddress}` ) } } + } catch (error) { + logError( + `Failed to check LiFiDEXAggregatorDiamond ownership: ${ + (error as Error).message + }` + ) } - } catch (error) { - logError( - `Failed to check LiFiDEXAggregatorDiamond ownership: ${ - (error as Error).message - }` + } else { + consola.info( + '⏭️ Skipping LDA Diamond ownership check for staging environment' ) } From 4eb8b19bd9126ce08b204da110caf42a40a784e0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 22:42:00 +0200 Subject: [PATCH 184/220] updates --- deployments/_deployments_log_file.json | 25 ++- deployments/arbitrum.diamond.staging.json | 15 +- deployments/arbitrum.staging.json | 2 +- deployments/bsc.lda.diamond.staging.json | 2 +- deployments/bsc.staging.json | 5 +- script/deploy/deployAllContracts.sh | 2 +- script/deploy/deployAllLDAContracts.sh | 4 +- script/deploy/deployFacetAndAddToDiamond.sh | 24 +-- script/helperFunctions.sh | 84 +++------- script/scriptMaster.sh | 21 +-- script/tasks/diamondUpdateFacet.sh | 36 +++-- script/tasks/ldaDiamondUpdateFacet.sh | 161 -------------------- 12 files changed, 84 insertions(+), 297 deletions(-) delete mode 100644 script/tasks/ldaDiamondUpdateFacet.sh diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index a14c0b517..e088459af 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -38134,11 +38134,11 @@ ], "1.1.0": [ { - "ADDRESS": "0x36e1375B0755162d720276dFF6893DF02bd49225", + "ADDRESS": "0x33EcEb68994E0499a61FAda3b49Ab243e63555F1", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-08-01 12:50:40", + "TIMESTAMP": "2025-09-08 22:27:21", "CONSTRUCTOR_ARGS": "0x000000000000000000000000023fa838682c115c2cfba96ef3791cb5bd931fc7", - "SALT": "", + "SALT": "22345119", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -38224,6 +38224,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.1.0": [ + { + "ADDRESS": "0x33EcEb68994E0499a61FAda3b49Ab243e63555F1", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-08 22:22:45", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000023fa838682c115c2cfba96ef3791cb5bd931fc7", + "SALT": "22345119", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] } }, "linea": { @@ -40190,11 +40203,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x2a528a7dDa49D45f9B766e131460a36e060861B2", + "ADDRESS": "0x4582E5095D17980D213c5Fb3959466C745593244", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 19:24:46", + "TIMESTAMP": "2025-09-08 22:16:25", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345121", + "SALT": "22345119", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index 92266b404..63cde2282 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -165,15 +165,15 @@ "Name": "AllBridgeFacet", "Version": "2.1.0" }, - "0xc2A0D799744536C621Af9B2933CdB4Ad959980bF": { - "Name": "AcrossFacetV4", - "Version": "1.0.0" + "0xb1b7786dfbb59dB5d1a65d4Be6c92C3B112fFb9b": { + "Name": "", + "Version": "" }, - "0x8962d191Ba0f2Bc29b949ACA222f8251B241190b": { - "Name": "AcrossFacetPackedV4", - "Version": "1.0.0" + "0xf536ed5A4310455FF39dBf90336e17d11550E7b4": { + "Name": "", + "Version": "" }, - "0x36e1375B0755162d720276dFF6893DF02bd49225": { + "0x33EcEb68994E0499a61FAda3b49Ab243e63555F1": { "Name": "GlacisFacet", "Version": "1.1.0" }, @@ -188,7 +188,6 @@ "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", "GasZipPeriphery": "", "LidoWrapper": "", - "LiFiDEXAggregator": "", "Patcher": "0x3971A968c03cd9640239C937F8d30D024840E691", "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", "ReceiverAcrossV3": "0xe4F3DEF14D61e47c696374453CD64d438FD277F8", diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 77992f683..002abeeb4 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -51,7 +51,7 @@ "ReceiverAcrossV3": "0xe4F3DEF14D61e47c696374453CD64d438FD277F8", "AcrossFacetPackedV3": "0x21767081Ff52CE5563A29f27149D01C7127775A2", "RelayFacet": "0x681a3409c35F12224c436D50Ce14F25f954B6Ea2", - "GlacisFacet": "0x36e1375B0755162d720276dFF6893DF02bd49225", + "GlacisFacet": "0x33EcEb68994E0499a61FAda3b49Ab243e63555F1", "PioneerFacet": "0x371E61d9DC497C506837DFA47B8dccEF1da30459", "GasZipFacet": "0x7C27b0FD92dbC5a1cA268255A649320E8C649e70", "ChainflipFacet": "0xa884c21873A671bD010567cf97c937b153F842Cc", diff --git a/deployments/bsc.lda.diamond.staging.json b/deployments/bsc.lda.diamond.staging.json index f12e791f5..3b16a040f 100644 --- a/deployments/bsc.lda.diamond.staging.json +++ b/deployments/bsc.lda.diamond.staging.json @@ -29,7 +29,7 @@ "Name": "IzumiV3Facet", "Version": "1.0.0" }, - "0x2a528a7dDa49D45f9B766e131460a36e060861B2": { + "0x4582E5095D17980D213c5Fb3959466C745593244": { "Name": "KatanaV3Facet", "Version": "1.0.0" }, diff --git a/deployments/bsc.staging.json b/deployments/bsc.staging.json index c640bafe0..6535de43a 100644 --- a/deployments/bsc.staging.json +++ b/deployments/bsc.staging.json @@ -39,10 +39,11 @@ "CoreRouteFacet": "0x07497D2C0B32537B0162dc35a4794F4B089a53Aa", "CurveFacet": "0x7B383eD28261835e63D2aed3b4A415B29438354B", "IzumiV3Facet": "0x0e5898bAa8CAf208292a9f7b0C5DA7BbDFc95F4b", - "KatanaV3Facet": "0x2a528a7dDa49D45f9B766e131460a36e060861B2", + "KatanaV3Facet": "0x4582E5095D17980D213c5Fb3959466C745593244", "NativeWrapperFacet": "0xd13d6DF1ef271dC887Df5499e4059e1AeC42Ea11", "SyncSwapV2Facet": "0xdFc21dcEc5220bfaB06D865312991F6058f5dfC6", "UniV2StyleFacet": "0x8ee5946F6d0818557cc49A862470D89A34b986d0", "UniV3StyleFacet": "0x2AA70A1115F9bA6008bD04511AAF7d81E5251d21", - "VelodromeV2Facet": "0x8CFB30c9A05357DAd207600A577f5251Fa7bCeab" + "VelodromeV2Facet": "0x8CFB30c9A05357DAd207600A577f5251Fa7bCeab", + "GlacisFacet": "0x33EcEb68994E0499a61FAda3b49Ab243e63555F1" } \ No newline at end of file diff --git a/script/deploy/deployAllContracts.sh b/script/deploy/deployAllContracts.sh index 8329c0e35..61b42472b 100755 --- a/script/deploy/deployAllContracts.sh +++ b/script/deploy/deployAllContracts.sh @@ -184,7 +184,7 @@ deployAllContracts() { echo "" echo "" echo "[info] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> now updating core facets in diamond contract" - diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "UpdateCoreFacets" false + diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "UpdateCoreFacets" # check if last command was executed successfully, otherwise exit script with error message checkFailure $? "update core facets in $DIAMOND_CONTRACT_NAME on network $NETWORK" diff --git a/script/deploy/deployAllLDAContracts.sh b/script/deploy/deployAllLDAContracts.sh index 289e5a0ce..9290095c0 100644 --- a/script/deploy/deployAllLDAContracts.sh +++ b/script/deploy/deployAllLDAContracts.sh @@ -18,7 +18,7 @@ deployAllLDAContracts() { source script/config.sh source script/helperFunctions.sh source script/deploy/deployFacetAndAddToDiamond.sh - source script/tasks/ldaDiamondUpdateFacet.sh + source script/tasks/diamondUpdateFacet.sh # read function arguments into variables local NETWORK="$1" @@ -155,7 +155,7 @@ deployAllLDAContracts() { echo "" echo "[info] STEP 3: Adding core facets to LDA Diamond..." - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" + diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$LDA_DIAMOND_CONTRACT_NAME" "UpdateLDACoreFacets" if [ $? -ne 0 ]; then error "❌ Failed to add core facets to LDA Diamond" diff --git a/script/deploy/deployFacetAndAddToDiamond.sh b/script/deploy/deployFacetAndAddToDiamond.sh index 884f27f3b..67ace1c00 100755 --- a/script/deploy/deployFacetAndAddToDiamond.sh +++ b/script/deploy/deployFacetAndAddToDiamond.sh @@ -10,8 +10,6 @@ function deployFacetAndAddToDiamond() { source script/helperFunctions.sh source script/deploy/deploySingleContract.sh source script/tasks/diamondUpdatePeriphery.sh - source script/tasks/ldaDiamondUpdateFacet.sh - # read function arguments into variables local NETWORK="$1" @@ -114,23 +112,11 @@ function deployFacetAndAddToDiamond() { # prepare update script name local UPDATE_SCRIPT="Update$FACET_CONTRACT_NAME" - # update diamond (use appropriate function based on diamond type) - if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then - # Use LDA-specific update function - ldaDiamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" - - if [ $? -ne 0 ]; then - warning "this call was not successful: ldaDiamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" - return 1 - fi - else - # Use regular diamond update function - diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" true - - if [ $? -ne 0 ]; then - warning "this call was not successful: diamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT true" - return 1 - fi + diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_CONTRACT_NAME" "$UPDATE_SCRIPT" + + if [ $? -ne 0 ]; then + warning "this call was not successful: diamondUpdateFacet $NETWORK $ENVIRONMENT $DIAMOND_CONTRACT_NAME $UPDATE_SCRIPT" + return 1 fi echo "[info] <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $FACET_CONTRACT_NAME successfully deployed and added to $DIAMOND_CONTRACT_NAME" diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 41ed55768..a31c73bc6 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -915,6 +915,7 @@ function saveDiamondFacets() { ENVIRONMENT=$2 USE_MUTABLE_DIAMOND=$3 FACETS=$4 + DIAMOND_CONTRACT_NAME=$5 # Optional: "LiFiDEXAggregatorDiamond" for LDA diamonds # logging for debug purposes echo "" @@ -923,6 +924,7 @@ function saveDiamondFacets() { echoDebug "ENVIRONMENT=$ENVIRONMENT" echoDebug "USE_MUTABLE_DIAMOND=$USE_MUTABLE_DIAMOND" echoDebug "FACETS=$FACETS" + echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" # get file suffix based on value in variable ENVIRONMENT local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") @@ -931,11 +933,17 @@ function saveDiamondFacets() { FACETS=$(echo "$4" | tr -d '[' | tr -d ']' | tr -d ',') FACETS=$(printf '"%s",' "$FACETS" | sed 's/,*$//') - # define path for json file based on which diamond was used - if [[ "$USE_MUTABLE_DIAMOND" == "true" ]]; then + # define path for json file based on diamond contract name + if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then + # LDA diamond + DIAMOND_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" + DIAMOND_NAME="LiFiDEXAggregatorDiamond" + elif [[ "$USE_MUTABLE_DIAMOND" == "true" ]]; then + # Regular mutable diamond DIAMOND_FILE="./deployments/${NETWORK}.diamond.${FILE_SUFFIX}json" DIAMOND_NAME="LiFiDiamond" else + # Regular immutable diamond DIAMOND_FILE="./deployments/${NETWORK}.diamond.immutable.${FILE_SUFFIX}json" DIAMOND_NAME="LiFiDiamondImmutable" fi @@ -973,9 +981,12 @@ function saveDiamondFacets() { printf %s "$result" >"$DIAMOND_FILE" done - # add information about registered periphery contracts - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" + # add information about registered periphery contracts (only for regular diamonds, not LDA) + if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond" ]]; then + saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" + fi } + function saveDiamondPeriphery_MULTICALL_NOT_IN_USE() { # read function arguments into variables NETWORK=$1 @@ -1131,65 +1142,6 @@ function saveDiamondPeriphery() { done } -# LDA-specific diamond save functions -function saveLDADiamondFacets() { - # read function arguments into variables - NETWORK=$1 - ENVIRONMENT=$2 - FACETS=$3 - - # logging for debug purposes - echo "" - echoDebug "in function saveLDADiamondFacets" - echoDebug "NETWORK=$NETWORK" - echoDebug "ENVIRONMENT=$ENVIRONMENT" - echoDebug "FACETS=$FACETS" - - # get file suffix based on value in variable ENVIRONMENT - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - - # store function arguments in variables - FACETS=$(echo "$3" | tr -d '[' | tr -d ']' | tr -d ',') - FACETS=$(printf '"%s",' "$FACETS" | sed 's/,*$//') - - # define path for LDA diamond json file - DIAMOND_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" - DIAMOND_NAME="LiFiDEXAggregatorDiamond" - - # create an empty json that replaces the existing file - echo "{}" >"$DIAMOND_FILE" - - # create an iterable FACETS array - # Remove brackets from FACETS string - FACETS_ADJ="${3#\[}" - FACETS_ADJ="${FACETS_ADJ%\]}" - # Split string into array - IFS=', ' read -ra FACET_ADDRESSES <<<"$FACETS_ADJ" - - # loop through all facets - for FACET_ADDRESS in "${FACET_ADDRESSES[@]}"; do - # get a JSON entry from log file - JSON_ENTRY=$(findContractInMasterLogByAddress "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") - - # check if contract was found in log file - if [[ $? -ne 0 ]]; then - warning "could not find any information about this facet address ($FACET_ADDRESS) in master log file while creating $DIAMOND_FILE (ENVIRONMENT=$ENVIRONMENT), " - - # try to find name of contract from network-specific deployments file - # load JSON FILE that contains deployment addresses - NAME=$(getContractNameFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") - - # create JSON entry manually with limited information (address only) - JSON_ENTRY="{\"$FACET_ADDRESS\": {\"Name\": \"$NAME\", \"Version\": \"\"}}" - fi - - # add new entry to JSON file - result=$(cat "$DIAMOND_FILE" | jq -r --argjson json_entry "$JSON_ENTRY" '.[$diamond_name] |= . + {Facets: (.Facets + $json_entry)}' --arg diamond_name "$DIAMOND_NAME" || cat "$DIAMOND_FILE") - - printf %s "$result" >"$DIAMOND_FILE" - done -} - function saveContract() { # read function arguments into variables local NETWORK=$1 @@ -2544,7 +2496,7 @@ function updateAllContractsToTargetState() { # update diamond with core facets echo "" - diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_NAME" "UpdateCoreFacets" false 2>/dev/null + diamondUpdateFacet "$NETWORK" "$ENVIRONMENT" "$DIAMOND_NAME" "UpdateCoreFacets" 2>/dev/null # check if last command was executed successfully, otherwise exit script with error message checkFailure $? "update core facets in $DIAMOND_NAME on network $NETWORK" @@ -4432,7 +4384,7 @@ function updateDiamondLogForNetwork() { local DIAMOND_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" echo "{\"$DIAMOND_TYPE\": {\"Facets\": {}}}" >"$DIAMOND_FILE" else - saveLDADiamondFacets "$NETWORK" "$ENVIRONMENT" "$KNOWN_FACET_ADDRESSES" + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" "LiFiDEXAggregatorDiamond" fi else # For regular diamonds, use existing save functions @@ -4457,7 +4409,7 @@ function updateDiamondLogs() { # read function arguments into variable local ENVIRONMENT=$1 local NETWORK=$2 - local DIAMOND_TYPE=${3:-"LiFiDiamond"} # Optional parameter, defaults to regular diamond + local DIAMOND_TYPE=${3:-"LiFiDiamond"} # Default to LiFiDiamond, can be "LiFiDEXAggregatorDiamond" for LDA # if no network was passed to this function, update all networks if [[ -z $NETWORK ]]; then diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index a59349055..f49a1c474 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -147,27 +147,10 @@ scriptMaster() { checkRequiredVariablesInDotEnv "$NETWORK" # Handle ZkSync - # We need to make sure that the zksync fork of foundry is available before - # we can deploy contracts to zksync. if isZkEvmNetwork "$NETWORK"; then - # Check if the foundry-zksync binaries exist, if not fetch them - install_foundry_zksync - - # Combine regular ZkSync contracts and LDA ZkSync contracts in the selection - REGULAR_ZKSYNC_SCRIPTS=$(ls -1 "script/deploy/zksync/" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy') - LDA_ZKSYNC_SCRIPTS=$(ls -1 "script/deploy/zksync/LDA/" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy') - - # Combine both lists and let user select - ALL_ZKSYNC_SCRIPTS=$(echo -e "$REGULAR_ZKSYNC_SCRIPTS\n$LDA_ZKSYNC_SCRIPTS") - SCRIPT=$(echo "$ALL_ZKSYNC_SCRIPTS" | gum filter --placeholder "Deploy Script") + SCRIPT=$(ls -1 "script/deploy/zksync/" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") else - # Combine regular LiFi contracts and LDA contracts in the selection - REGULAR_SCRIPTS=$(ls -1 "script/deploy/facets/" | sed -e 's/\.s.sol$//' | grep 'Deploy') - LDA_SCRIPTS=$(ls -1 "script/deploy/facets/LDA/" | sed -e 's/\.s.sol$//' | grep 'Deploy') - - # Combine both lists and let user select - ALL_SCRIPTS=$(echo -e "$REGULAR_SCRIPTS\n$LDA_SCRIPTS") - SCRIPT=$(echo "$ALL_SCRIPTS" | gum filter --placeholder "Deploy Script") + SCRIPT=$(ls -1 "script/deploy/facets/" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") fi # get user-selected deploy script and contract from list diff --git a/script/tasks/diamondUpdateFacet.sh b/script/tasks/diamondUpdateFacet.sh index 74fa7fa66..2680f65f3 100755 --- a/script/tasks/diamondUpdateFacet.sh +++ b/script/tasks/diamondUpdateFacet.sh @@ -11,7 +11,6 @@ diamondUpdateFacet() { local ENVIRONMENT="$2" local DIAMOND_CONTRACT_NAME="$3" local SCRIPT="$4" - local REPLACE_EXISTING_FACET="$5" # if no ENVIRONMENT was passed to this function, determine it if [[ -z "$ENVIRONMENT" ]]; then @@ -82,22 +81,37 @@ diamondUpdateFacet() { # if no SCRIPT was passed to this function, ask user to select it if [[ -z "$SCRIPT" ]]; then echo "Please select which facet you would like to update" - SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.s.sol$//' | grep 'Update' | gum filter --placeholder "Update Script") + if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then + SCRIPT=$(ls -1 "script/deploy/facets/LDA/" | sed -e 's/\.s.sol$//' | grep 'Update' | gum filter --placeholder "Update LDA Script") + else + SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.s.sol$//' | grep 'Update' | gum filter --placeholder "Update Script") + fi fi - # Handle script paths and extensions based on network type + # Handle script paths and extensions based on network type and diamond type if isZkEvmNetwork "$NETWORK"; then - SCRIPT_PATH="script/deploy/zksync/$SCRIPT.zksync.s.sol" + if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then + SCRIPT_PATH="script/deploy/zksync/LDA/$SCRIPT.zksync.s.sol" + else + SCRIPT_PATH="script/deploy/zksync/$SCRIPT.zksync.s.sol" + fi # Check if the foundry-zksync binaries exist, if not fetch them install_foundry_zksync else - SCRIPT_PATH=$DEPLOY_SCRIPT_DIRECTORY"$SCRIPT.s.sol" + if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then + SCRIPT_PATH="script/deploy/facets/LDA/$SCRIPT.s.sol" + else + SCRIPT_PATH=$DEPLOY_SCRIPT_DIRECTORY"$SCRIPT.s.sol" + fi fi CONTRACT_NAME=$(basename "$SCRIPT_PATH" | sed 's/\.zksync\.s\.sol$//' | sed 's/\.s\.sol$//') # set flag for mutable/immutable diamond - USE_MUTABLE_DIAMOND=$([[ "$DIAMOND_CONTRACT_NAME" == "LiFiDiamond" ]] && echo true || echo false) + USE_MUTABLE_DIAMOND=$([[ "$DIAMOND_CONTRACT_NAME" == "LiFiDiamond" || "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]] && echo true || echo false) + + # set flag for LDA diamond + USE_LDA_DIAMOND=$([[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]] && echo false || echo true) # logging for debug purposes echoDebug "updating $DIAMOND_CONTRACT_NAME on $NETWORK with address $DIAMOND_ADDRESS in $ENVIRONMENT environment with script $SCRIPT (FILE_SUFFIX=$FILE_SUFFIX, USE_MUTABLE_DIAMOND=$USE_MUTABLE_DIAMOND)" @@ -121,10 +135,10 @@ diamondUpdateFacet() { if isZkEvmNetwork "$NETWORK"; then echo "zkEVM network detected" - RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync NO_BROADCAST=true NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_MUTABLE_DIAMOND PRIVATE_KEY=$PRIVATE_KEY ./foundry-zksync/forge script "$SCRIPT_PATH" -f "$NETWORK" -vvvv --json --skip-simulation --slow --zksync) + RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync NO_BROADCAST=true NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$PRIVATE_KEY ./foundry-zksync/forge script "$SCRIPT_PATH" -f "$NETWORK" -vvvv --json --skip-simulation --slow --zksync) else # PROD (normal mode): suggest diamondCut transaction to SAFE - RAW_RETURN_DATA=$(NO_BROADCAST=true NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_MUTABLE_DIAMOND PRIVATE_KEY=$PRIVATE_KEY forge script "$SCRIPT_PATH" -f "$NETWORK" -vvvv --json --skip-simulation --legacy) + RAW_RETURN_DATA=$(NO_BROADCAST=true NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$PRIVATE_KEY forge script "$SCRIPT_PATH" -f "$NETWORK" -vvvv --json --skip-simulation --legacy) fi # Extract JSON starting with {"logs": from mixed output @@ -162,9 +176,9 @@ diamondUpdateFacet() { echo "Sending diamondCut transaction directly to diamond (staging or new network deployment)..." if isZkEvmNetwork "$NETWORK"; then - RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync ./foundry-zksync/forge script "$SCRIPT_PATH" -f "$NETWORK" --json --broadcast --skip-simulation --slow --zksync --private-key $(getPrivateKey "$NETWORK" "$ENVIRONMENT")) + RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync USE_DEF_DIAMOND=$USE_LDA_DIAMOND ./foundry-zksync/forge script "$SCRIPT_PATH" -f "$NETWORK" --json --broadcast --skip-simulation --slow --zksync --private-key $(getPrivateKey "$NETWORK" "$ENVIRONMENT")) else - RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_MUTABLE_DIAMOND NO_BROADCAST=false PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$SCRIPT_PATH" -f "$NETWORK" -vvvv --json --broadcast --legacy) + RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND NO_BROADCAST=false PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$SCRIPT_PATH" -f "$NETWORK" -vvvv --json --broadcast --legacy) fi fi RETURN_CODE=$? @@ -201,7 +215,7 @@ diamondUpdateFacet() { # save facet addresses (only if deploying to staging, otherwise we update the logs after the diamondCut tx gets signed in the SAFE) if [[ "$ENVIRONMENT" != "production" ]]; then - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "$DIAMOND_CONTRACT_NAME" fi echo "[info] $SCRIPT successfully executed on network $NETWORK in $ENVIRONMENT environment" diff --git a/script/tasks/ldaDiamondUpdateFacet.sh b/script/tasks/ldaDiamondUpdateFacet.sh deleted file mode 100644 index 7f72180bd..000000000 --- a/script/tasks/ldaDiamondUpdateFacet.sh +++ /dev/null @@ -1,161 +0,0 @@ -#!/bin/bash - -# executes a diamond update script to update an LDA facet on LiFiDEXAggregatorDiamond -function ldaDiamondUpdateFacet() { - - # load required variables and helper functions - source script/config.sh - source script/helperFunctions.sh - - # read function arguments into variables - local NETWORK="$1" - local ENVIRONMENT="$2" - local DIAMOND_CONTRACT_NAME="$3" - local UPDATE_SCRIPT="$4" - - # if no NETWORK was passed to this function, ask user to select it - if [[ -z "$NETWORK" ]]; then - checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" - NETWORK=$(jq -r 'keys[]' "$NETWORKS_JSON_FILE_PATH" | gum filter --placeholder "Network") - checkRequiredVariablesInDotEnv $NETWORK - fi - - # if no ENVIRONMENT was passed to this function, determine it - if [[ -z "$ENVIRONMENT" ]]; then - if [[ "$PRODUCTION" == "true" ]]; then - # make sure that PRODUCTION was selected intentionally by user - echo " " - echo " " - printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!! ATTENTION !!!!!!!!!!!!!!!!!!!!!!!!"; - printf '\033[33m%s\033[0m\n' "The config environment variable PRODUCTION is set to true"; - printf '\033[33m%s\033[0m\n' "This means you will be updating LDA contracts in production"; - printf '\033[31m%s\031\n' "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; - echo " " - printf '\033[33m%s\033[0m\n' "Last chance: Do you want to skip?"; - PROD_SELECTION=$(gum choose \ - "yes" \ - "no" \ - ) - - if [[ $PROD_SELECTION != "no" ]]; then - echo "...exiting script" - exit 0 - fi - - ENVIRONMENT="production" - else - ENVIRONMENT="staging" - fi - fi - - # get file suffix based on value in variable ENVIRONMENT - FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - - # if no DIAMOND_CONTRACT_NAME was passed to this function, default to LiFiDEXAggregatorDiamond - if [[ -z "$DIAMOND_CONTRACT_NAME" ]]; then - DIAMOND_CONTRACT_NAME="LiFiDEXAggregatorDiamond" - fi - - # if no UPDATE_SCRIPT was passed to this function, ask user to select it - if [[ -z "$UPDATE_SCRIPT" ]]; then - echo "" - echo "Please select which LDA update script you would like to execute" - local SCRIPT=$(ls -1 script/deploy/facets/LDA/ | sed -e 's/\.s.sol$//' | grep 'Update' | gum filter --placeholder "Update LDA Script") - UPDATE_SCRIPT="$SCRIPT" - fi - - # set LDA-specific script directory (use ZkSync path for ZkSync networks) - if isZkEvmNetwork "$NETWORK"; then - LDA_UPDATE_SCRIPT_PATH="script/deploy/zksync/LDA/${UPDATE_SCRIPT}.zksync.s.sol" - else - LDA_UPDATE_SCRIPT_PATH="script/deploy/facets/LDA/${UPDATE_SCRIPT}.s.sol" - fi - - # check if LDA update script exists - if ! checkIfFileExists "$LDA_UPDATE_SCRIPT_PATH" >/dev/null; then - error "could not find LDA update script for $UPDATE_SCRIPT in this path: $LDA_UPDATE_SCRIPT_PATH." - return 1 - fi - - local LDA_DEPLOYMENT_FILE="./deployments/${NETWORK}.${FILE_SUFFIX}json" - local DIAMOND_ADDRESS=$(jq -r '.'"$DIAMOND_CONTRACT_NAME" "$LDA_DEPLOYMENT_FILE") - - # if no diamond address was found, throw an error and exit this script - if [[ "$DIAMOND_ADDRESS" == "null" ]]; then - error "could not find address for $DIAMOND_CONTRACT_NAME on network $NETWORK in file '$LDA_DEPLOYMENT_FILE' - exiting script now" - return 1 - fi - - # set flag for LDA diamond (always false since LiFiDEXAggregatorDiamond is not the default diamond) - USE_LDA_DIAMOND=false - - if [[ -z "$GAS_ESTIMATE_MULTIPLIER" ]]; then - GAS_ESTIMATE_MULTIPLIER=130 # this is foundry's default value - fi - - # execute LDA diamond update script - local attempts=1 - - while [ $attempts -le "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; do - echo "[info] trying to execute LDA diamond update script $UPDATE_SCRIPT now - attempt ${attempts} (max attempts: $MAX_ATTEMPTS_PER_SCRIPT_EXECUTION) " - - # ensure that gas price is below maximum threshold (for mainnet only) - doNotContinueUnlessGasIsBelowThreshold "$NETWORK" - - # try to execute call (use ZkSync forge for ZkSync networks) - if isZkEvmNetwork "$NETWORK"; then - # For ZkSync networks, use ZkSync-specific forge and compile first - echo "[info] Compiling contracts with ZkSync compiler for LDA diamond update..." - FOUNDRY_PROFILE=zksync ./foundry-zksync/forge build --zksync - RAW_RETURN_DATA=$(FOUNDRY_PROFILE=zksync NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") ./foundry-zksync/forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --skip-simulation --slow --zksync --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") - else - # For regular networks, use regular forge - RAW_RETURN_DATA=$(NETWORK=$NETWORK FILE_SUFFIX=$FILE_SUFFIX USE_DEF_DIAMOND=$USE_LDA_DIAMOND PRIVATE_KEY=$(getPrivateKey "$NETWORK" "$ENVIRONMENT") forge script "$LDA_UPDATE_SCRIPT_PATH" -f "$NETWORK" -vvvvv --json --broadcast --slow --gas-estimate-multiplier "$GAS_ESTIMATE_MULTIPLIER") - fi - - local RETURN_CODE=$? - - # print return data only if debug mode is activated - echoDebug "RAW_RETURN_DATA: $RAW_RETURN_DATA" - - # check return data for error message (regardless of return code as this is not 100% reliable) - if [[ $RAW_RETURN_DATA == *"\"logs\":[]"* && $RAW_RETURN_DATA == *"\"returns\":{}"* ]]; then - warning "The transaction was executed but the return value suggests that no logs were emitted" - warning "This happens if contracts are already up-to-date." - warning "This may also be a sign that the transaction was not executed properly." - warning "Please check manually if the transaction was executed and if the LDA diamond was updated" - echo "" - return 0 - fi - - # check the return code the last call - if [ "$RETURN_CODE" -eq 0 ]; then - # extract the "returns" property directly from the JSON output - RETURN_DATA=$(echo "$RAW_RETURN_DATA" | jq -r '.returns // empty' 2>/dev/null) - - # get the facet addresses that are known to the diamond from the return data - FACETS=$(echo "$RETURN_DATA" | jq -r '.facets.value // "{}"') - if [[ $FACETS != "{}" ]]; then - echo "[info] LDA diamond update was successful" - return 0 # exit the loop if the operation was successful - fi - fi - - echo "[error] Call failed with error code $RETURN_CODE" - echo "[error] Error message: $RAW_RETURN_DATA" - - attempts=$((attempts + 1)) - - # exit the loop if this was the last attempt - if [ $attempts -gt "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; then - error "max attempts reached, execution of LDA diamond update for $UPDATE_SCRIPT failed" - return 1 - fi - - # wait a bit before retrying - echo "retrying in $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR seconds..." - sleep $TIME_TO_WAIT_BEFORE_RETRY_ON_ERROR - done - - return 1 -} \ No newline at end of file From 40d3700ca30b6a145a41f4cf9c881a1c1e560aa9 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 8 Sep 2025 23:15:15 +0200 Subject: [PATCH 185/220] changing "Deploy LDA facet" to "Deploy LDA contract" --- script/scriptMaster.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index f49a1c474..85cd205c2 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -123,7 +123,7 @@ scriptMaster() { "11) Update diamond log(s)" \ "12) Propose upgrade TX to Gnosis SAFE" \ "13) Remove facets or periphery from diamond" \ - "14) Deploy LDA facet to one selected network" \ + "14) Deploy LDA contract to one selected network" \ ) #--------------------------------------------------------------------------------------------------------------------- @@ -592,10 +592,10 @@ scriptMaster() { bunx tsx script/tasks/cleanUpProdDiamond.ts #--------------------------------------------------------------------------------------------------------------------- - # use case 14: Deploy LDA facet to one selected network + # use case 14: Deploy LDA contract to one selected network elif [[ "$SELECTION" == "14)"* ]]; then echo "" - echo "[info] selected use case: Deploy LDA facet to one selected network" + echo "[info] selected use case: Deploy LDA contract to one selected network" checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" # get user-selected network from list From 3fea66322d0f9a1d1e2e667bc562b133e00b0396 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 00:29:02 +0200 Subject: [PATCH 186/220] revert --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 5bd8d6213..8e50d834f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -6,7 +6,7 @@ echo "Running 'forge build' and 'typechain generation'..." # Run forge build first (required for typechain) echo "Building contracts with forge..." -forge build src +forge build if [ $? -ne 0 ]; then printf '\n%s\n\n' "Forge build failed. Aborting commit." From 03fa2efb6218e550d944de9ba9f15b4294cdf2d6 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 00:58:41 +0200 Subject: [PATCH 187/220] moved lda contract logic to 14 selector --- deployments/_deployments_log_file.json | 17 ++++- deployments/arbitrum.diamond.staging.json | 4 +- deployments/arbitrum.lda.diamond.staging.json | 4 +- deployments/arbitrum.staging.json | 4 +- script/scriptMaster.sh | 65 +++++-------------- 5 files changed, 35 insertions(+), 59 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index e088459af..dee6f57c8 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -38047,6 +38047,17 @@ "SALT": "", "VERIFIED": "true" } + ], + "1.0.1": [ + { + "ADDRESS": "0xaA1E88f4D0cb0a798f1FeBAfc8fAb4778629D4e7", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-09 00:51:30", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000079001a5e762f3befc8e5871b42f6734e00498920", + "SALT": "22345117", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } ] }, "production": { @@ -40173,11 +40184,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "ADDRESS": "0xb11208638f71585C02b23c2E9cfE4BB791a0d952", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:08:38", + "TIMESTAMP": "2025-09-09 00:57:01", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345117", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index 63cde2282..e53d1cfee 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -149,9 +149,9 @@ "Name": "GlacisFacet", "Version": "1.0.0" }, - "0xa884c21873A671bD010567cf97c937b153F842Cc": { + "0xaA1E88f4D0cb0a798f1FeBAfc8fAb4778629D4e7": { "Name": "ChainflipFacet", - "Version": "1.0.0" + "Version": "1.0.1" }, "0x371E61d9DC497C506837DFA47B8dccEF1da30459": { "Name": "PioneerFacet", diff --git a/deployments/arbitrum.lda.diamond.staging.json b/deployments/arbitrum.lda.diamond.staging.json index 07905ba44..581150106 100644 --- a/deployments/arbitrum.lda.diamond.staging.json +++ b/deployments/arbitrum.lda.diamond.staging.json @@ -17,7 +17,7 @@ "Name": "AlgebraFacet", "Version": "1.0.0" }, - "0x5465843475BE7DA9E5640a1BcD5310B68646Dbef": { + "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240": { "Name": "CoreRouteFacet", "Version": "1.0.0" }, @@ -29,7 +29,7 @@ "Name": "IzumiV3Facet", "Version": "1.0.0" }, - "0xf33C1c24ccc5A137231d89272a2383c28B1dd046": { + "0xb11208638f71585C02b23c2E9cfE4BB791a0d952": { "Name": "KatanaV3Facet", "Version": "1.0.0" }, diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 002abeeb4..59dd4b61e 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -54,7 +54,7 @@ "GlacisFacet": "0x33EcEb68994E0499a61FAda3b49Ab243e63555F1", "PioneerFacet": "0x371E61d9DC497C506837DFA47B8dccEF1da30459", "GasZipFacet": "0x7C27b0FD92dbC5a1cA268255A649320E8C649e70", - "ChainflipFacet": "0xa884c21873A671bD010567cf97c937b153F842Cc", + "ChainflipFacet": "0xaA1E88f4D0cb0a798f1FeBAfc8fAb4778629D4e7", "LiFiDEXAggregator": "0x14aB08312a1EA45F76fd83AaE89A3118537FC06D", "Patcher": "0x18069208cA7c2D55aa0073E047dD45587B26F6D4", "WhitelistManagerFacet": "0x603f0c31B37E5ca3eA75D5730CCfaBCFF6D17aa3", @@ -63,7 +63,7 @@ "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", "CoreRouteFacet": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "KatanaV3Facet": "0xf33C1c24ccc5A137231d89272a2383c28B1dd046", + "KatanaV3Facet": "0xb11208638f71585C02b23c2E9cfE4BB791a0d952", "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", diff --git a/script/scriptMaster.sh b/script/scriptMaster.sh index 85cd205c2..d0805f22d 100755 --- a/script/scriptMaster.sh +++ b/script/scriptMaster.sh @@ -148,64 +148,31 @@ scriptMaster() { # Handle ZkSync if isZkEvmNetwork "$NETWORK"; then - SCRIPT=$(ls -1 "script/deploy/zksync/" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") + DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/" + # Check if the foundry-zksync binaries exist, if not fetch them + install_foundry_zksync + # get user-selected deploy script and contract from list + SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.zksync.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") else - SCRIPT=$(ls -1 "script/deploy/facets/" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") + DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" + SCRIPT=$(ls -1 "$DEPLOY_SCRIPT_DIRECTORY" | sed -e 's/\.s.sol$//' | grep 'Deploy' | gum filter --placeholder "Deploy Script") fi # get user-selected deploy script and contract from list CONTRACT=$(echo "$SCRIPT" | sed -e 's/Deploy//') - - # Set appropriate directory based on network type and script location - if isZkEvmNetwork "$NETWORK"; then - # For ZkSync networks, check if it's an LDA script - if echo "$LDA_ZKSYNC_SCRIPTS" | grep -q "^$SCRIPT$"; then - DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/LDA/" - else - DEPLOY_SCRIPT_DIRECTORY="script/deploy/zksync/" - fi - else - # For regular networks, check if it's an LDA script - if echo "$LDA_SCRIPTS" | grep -q "^$SCRIPT$"; then - DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/LDA/" - else - DEPLOY_SCRIPT_DIRECTORY="script/deploy/facets/" - fi - fi # check if new contract should be added to diamond after deployment # Skip diamond addition question for LiFiDEXAggregatorDiamond (has custom flow) and LiFiDiamond contracts - if [[ ! "$CONTRACT" == "LiFiDiamond"* && ! "$CONTRACT" == "LiFiDEXAggregatorDiamond" ]]; then + if [[ ! "$CONTRACT" == "LiFiDiamond"* ]]; then echo "" echo "Do you want to add this contract to a diamond after deployment?" # Check if this is an LDA contract based on network type and script - IS_LDA_CONTRACT=false - if isZkEvmNetwork "$NETWORK"; then - if echo "$LDA_ZKSYNC_SCRIPTS" | grep -q "^Deploy${CONTRACT}$"; then - IS_LDA_CONTRACT=true - fi - else - if echo "$LDA_SCRIPTS" | grep -q "^Deploy${CONTRACT}$"; then - IS_LDA_CONTRACT=true - fi - fi - - if [[ "$IS_LDA_CONTRACT" == "true" ]]; then - # For LDA contracts, offer LDA diamond option - ADD_TO_DIAMOND=$( - gum choose \ - "yes - to LiFiDEXAggregatorDiamond" \ - " no - do not update any diamond" - ) - else - # For regular contracts, offer regular diamond options - ADD_TO_DIAMOND=$( - gum choose \ - "yes - to LiFiDiamond" \ - "yes - to LiFiDiamondImmutable" \ - " no - do not update any diamond" - ) - fi + ADD_TO_DIAMOND=$( + gum choose \ + "yes - to LiFiDiamond" \ + "yes - to LiFiDiamondImmutable" \ + " no - do not update any diamond" + ) fi # get current contract version @@ -216,9 +183,7 @@ scriptMaster() { echo "[info] selected option: $ADD_TO_DIAMOND" # determine the diamond type and call unified function - if [[ "$ADD_TO_DIAMOND" == *"LiFiDEXAggregatorDiamond"* ]]; then - deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDEXAggregatorDiamond" "$VERSION" - elif [[ "$ADD_TO_DIAMOND" == *"LiFiDiamondImmutable"* ]]; then + if [[ "$ADD_TO_DIAMOND" == *"LiFiDiamondImmutable"* ]]; then deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamondImmutable" "$VERSION" else deployFacetAndAddToDiamond "$NETWORK" "$ENVIRONMENT" "$CONTRACT" "LiFiDiamond" "$VERSION" From 5fa279e0cbb9deb9142eea50433e3bf8b243e7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 9 Sep 2025 10:31:35 +0700 Subject: [PATCH 188/220] parallelize internal logic in diamondLogUpdate helper function --- script/helperFunctions.sh | 224 +++++++++++++++++++++++++++++--------- 1 file changed, 175 insertions(+), 49 deletions(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 7ae645048..42312a193 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -915,6 +915,9 @@ function saveDiamondFacets() { ENVIRONMENT=$2 USE_MUTABLE_DIAMOND=$3 FACETS=$4 + # optional: output control for parallel orchestration + local OUTPUT_MODE="$5" # "facets-only" to write only facets JSON to OUTPUT_PATH + local OUTPUT_PATH="$6" # path to write facets JSON when in facets-only mode # logging for debug purposes echo "" @@ -927,10 +930,6 @@ function saveDiamondFacets() { # get file suffix based on value in variable ENVIRONMENT local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - # store function arguments in variables - FACETS=$(echo "$4" | tr -d '[' | tr -d ']' | tr -d ',') - FACETS=$(printf '"%s",' "$FACETS" | sed 's/,*$//') - # define path for json file based on which diamond was used if [[ "$USE_MUTABLE_DIAMOND" == "true" ]]; then DIAMOND_FILE="./deployments/${NETWORK}.diamond.${FILE_SUFFIX}json" @@ -940,41 +939,87 @@ function saveDiamondFacets() { DIAMOND_NAME="LiFiDiamondImmutable" fi - # create an empty json that replaces the existing file - echo "{}" >"$DIAMOND_FILE" - # create an iterable FACETS array - # Remove brackets from FACETS string + # Remove brackets from FACETS string and split into array FACETS_ADJ="${4#\[}" FACETS_ADJ="${FACETS_ADJ%\]}" - # Split string into array - IFS=', ' read -ra FACET_ADDRESSES <<<"$FACETS_ADJ" + IFS=',' read -ra FACET_ADDRESSES <<<"$FACETS_ADJ" + + # Set up a temp directory to collect facet entries (avoid concurrent writes) + local TEMP_DIR + TEMP_DIR=$(mktemp -d) + trap 'rm -rf "$TEMP_DIR" 2>/dev/null' EXIT + local FACETS_DIR="$TEMP_DIR/facets" + mkdir -p "$FACETS_DIR" + + # determine concurrency (fallback to 10 if not set) + local CONCURRENCY=${MAX_CONCURRENT_JOBS:-10} + if [[ -z "$CONCURRENCY" || "$CONCURRENCY" -le 0 ]]; then + CONCURRENCY=10 + fi + + # resolve each facet in parallel and write individual JSON files + for RAW_ADDR in "${FACET_ADDRESSES[@]}"; do + # sanitize address (strip quotes, spaces) + FACET_ADDRESS=$(echo "$RAW_ADDR" | tr -d '"' | tr -d ' ') + if [[ -z "$FACET_ADDRESS" ]]; then + continue + fi - # loop through all facets - for FACET_ADDRESS in "${FACET_ADDRESSES[@]}"; do - # get a JSON entry from log file - JSON_ENTRY=$(findContractInMasterLogByAddress "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") + # throttle background jobs + while [[ $(jobs | wc -l | tr -d ' ') -ge $CONCURRENCY ]]; do + sleep 0.1 + done - # check if contract was found in log file - if [[ $? -ne 0 ]]; then - warning "could not find any information about this facet address ($FACET_ADDRESS) in master log file while creating $DIAMOND_FILE (ENVIRONMENT=$ENVIRONMENT), " + ( + JSON_ENTRY=$(findContractInMasterLogByAddress "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") + if [[ $? -ne 0 || -z "$JSON_ENTRY" ]]; then + warning "could not find any information about this facet address ($FACET_ADDRESS) in master log file while creating $DIAMOND_FILE (ENVIRONMENT=$ENVIRONMENT), " + NAME=$(getContractNameFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") + JSON_ENTRY="{\"$FACET_ADDRESS\": {\"Name\": \"$NAME\", \"Version\": \"\"}}" + fi + echo "$JSON_ENTRY" > "$FACETS_DIR/${FACET_ADDRESS}.json" + ) & + done - # try to find name of contract from network-specific deployments file - # load JSON FILE that contains deployment addresses - NAME=$(getContractNameFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") + # wait for all background jobs to complete + wait - # create JSON entry manually with limited information (address only) - JSON_ENTRY="{\"$FACET_ADDRESS\": {\"Name\": \"$NAME\", \"Version\": \"\"}}" + # merge all facet JSON entries into a single object preserving original order + local MERGE_FILES=() + for RAW_ADDR in "${FACET_ADDRESSES[@]}"; do + ADDR=$(echo "$RAW_ADDR" | tr -d '"' | tr -d ' ') + [[ -z "$ADDR" ]] && continue + FILEPATH="$FACETS_DIR/${ADDR}.json" + if [[ -s "$FILEPATH" ]]; then + MERGE_FILES+=("$FILEPATH") fi + done - # add new entry to JSON file - result=$(cat "$DIAMOND_FILE" | jq -r --argjson json_entry "$JSON_ENTRY" '.[$diamond_name] |= . + {Facets: (.Facets + $json_entry)}' --arg diamond_name "$DIAMOND_NAME" || cat "$DIAMOND_FILE") + local FACETS_JSON='{}' + if [[ ${#MERGE_FILES[@]} -gt 0 ]]; then + FACETS_JSON=$(jq -s 'add' "${MERGE_FILES[@]}") + fi + # if called in facets-only mode, output to path and skip touching DIAMOND_FILE or periphery + if [[ "$OUTPUT_MODE" == "facets-only" && -n "$OUTPUT_PATH" ]]; then + printf %s "$FACETS_JSON" > "$OUTPUT_PATH" + else + # ensure diamond file exists + if [[ ! -e $DIAMOND_FILE ]]; then + echo "{}" >"$DIAMOND_FILE" + fi + + # write merged facets to diamond file in a single atomic update + result=$(jq -r --arg diamond_name "$DIAMOND_NAME" --argjson facets_obj "$FACETS_JSON" ' + .[$diamond_name] = (.[$diamond_name] // {}) | + .[$diamond_name].Facets = ((.[$diamond_name].Facets // {}) + $facets_obj) + ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") printf %s "$result" >"$DIAMOND_FILE" - done - # add information about registered periphery contracts - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" + # add information about registered periphery contracts + saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" + fi } function saveDiamondPeriphery_MULTICALL_NOT_IN_USE() { # read function arguments into variables @@ -1073,6 +1118,9 @@ function saveDiamondPeriphery() { NETWORK=$1 ENVIRONMENT=$2 USE_MUTABLE_DIAMOND=$3 + # optional: output control for parallel orchestration + local OUTPUT_MODE="$4" # "periphery-only" to write only periphery JSON to OUTPUT_PATH + local OUTPUT_PATH="$5" # path to write periphery JSON when in periphery-only mode # get file suffix based on value in variable ENVIRONMENT local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") @@ -1107,28 +1155,72 @@ function saveDiamondPeriphery() { echoDebug "DIAMOND_ADDRESS=$DIAMOND_ADDRESS" echoDebug "DIAMOND_FILE=$DIAMOND_FILE" - # create an empty json if it does not exist - if [[ ! -e $DIAMOND_FILE ]]; then - echo "{}" >"$DIAMOND_FILE" - fi - # get a list of all periphery contracts PERIPHERY_CONTRACTS=$(getContractNamesInFolder "src/Periphery/") - # loop through periphery contracts + # prepare temp dir to collect per-contract JSON snippets + local TEMP_DIR + TEMP_DIR=$(mktemp -d) + local PERIPHERY_DIR="$TEMP_DIR/periphery" + mkdir -p "$PERIPHERY_DIR" + + # determine concurrency (fallback to 10 if not set) + local CONCURRENCY=${MAX_CONCURRENT_JOBS:-10} + if [[ -z "$CONCURRENCY" || "$CONCURRENCY" -le 0 ]]; then + CONCURRENCY=10 + fi + + # resolve each periphery address in parallel and write to temp files for CONTRACT in $PERIPHERY_CONTRACTS; do - # get the address of this contract from diamond (will return ZERO_ADDRESS, if not registered) - ADDRESS=$(cast call "$DIAMOND_ADDRESS" "getPeripheryContract(string) returns (address)" "$CONTRACT" --rpc-url "$RPC_URL") + # throttle background jobs + while [[ $(jobs | wc -l | tr -d ' ') -ge $CONCURRENCY ]]; do + sleep 0.1 + done + + ( + ADDRESS=$(cast call "$DIAMOND_ADDRESS" "getPeripheryContract(string) returns (address)" "$CONTRACT" --rpc-url "$RPC_URL" 2>/dev/null) + if [[ "$ADDRESS" == $ZERO_ADDRESS || -z "$ADDRESS" ]]; then + ADDRESS="" + fi + echo "{\"$CONTRACT\": \"$ADDRESS\"}" > "$PERIPHERY_DIR/${CONTRACT}.json" + ) & + done - # check if address is ZERO_ADDRESS - if [[ "$ADDRESS" == $ZERO_ADDRESS ]]; then - ADDRESS="" + # wait for all background jobs + wait + + # merge all periphery JSON entries in the same order as contract list + local MERGE_FILES=() + for CONTRACT in $PERIPHERY_CONTRACTS; do + FILEPATH="$PERIPHERY_DIR/${CONTRACT}.json" + if [[ -s "$FILEPATH" ]]; then + MERGE_FILES+=("$FILEPATH") fi + done + + local PERIPHERY_JSON='{}' + if [[ ${#MERGE_FILES[@]} -gt 0 ]]; then + PERIPHERY_JSON=$(jq -s 'add' "${MERGE_FILES[@]}") + fi - # add new entry to JSON file - result=$(cat "$DIAMOND_FILE" | jq -r ".$DIAMOND_NAME.Periphery += {\"$CONTRACT\": \"$ADDRESS\"}" || cat "$DIAMOND_FILE") + if [[ "$OUTPUT_MODE" == "periphery-only" && -n "$OUTPUT_PATH" ]]; then + # write only the periphery object to the given path + printf %s "$PERIPHERY_JSON" > "$OUTPUT_PATH" + else + # ensure diamond file exists + if [[ ! -e $DIAMOND_FILE ]]; then + echo "{}" >"$DIAMOND_FILE" + fi + # update diamond file in a single atomic write + result=$(jq -r --arg diamond_name "$DIAMOND_NAME" --argjson periphery_obj "$PERIPHERY_JSON" ' + .[$diamond_name] = (.[$diamond_name] // {}) | + .[$diamond_name].Periphery = $periphery_obj + ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") printf %s "$result" >"$DIAMOND_FILE" - done + fi + + # cleanup + rm -rf "$TEMP_DIR" } function saveContract() { # read function arguments into variables @@ -4361,20 +4453,54 @@ function updateDiamondLogForNetwork() { warning "[$NETWORK] Failed to get facets from diamond $DIAMOND_ADDRESS after $MAX_ATTEMPTS_PER_SCRIPT_EXECUTION attempts" fi + # prepare for parallel facet/periphery processing and final merge + local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + local DIAMOND_FILE="./deployments/${NETWORK}.diamond.${FILE_SUFFIX}json" + local DIAMOND_NAME="LiFiDiamond" + local TEMP_DIR + TEMP_DIR=$(mktemp -d) + local FACETS_TMP="$TEMP_DIR/facets.json" + local PERIPHERY_TMP="$TEMP_DIR/periphery.json" + + # start periphery resolution in background + saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "true" "periphery-only" "$PERIPHERY_TMP" & + local PID_PERIPHERY=$! + + # start facets resolution (if available) in background if [[ -z $KNOWN_FACET_ADDRESSES ]]; then warning "[$NETWORK] no facets found in diamond $DIAMOND_ADDRESS" - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "true" + echo '{}' > "$FACETS_TMP" else - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" - # saveDiamondPeriphery is executed as part of saveDiamondFacets + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" & fi + local PID_FACETS=$! - # check result - if [[ $? -ne 0 ]]; then - error "[$NETWORK] failed to update diamond log" - else - success "[$NETWORK] updated diamond log" + # wait for both background jobs to complete + if [[ -n "$PID_PERIPHERY" ]]; then wait "$PID_PERIPHERY"; fi + if [[ -n "$PID_FACETS" ]]; then wait "$PID_FACETS"; fi + + # validate temp outputs exist + if [[ ! -s "$FACETS_TMP" ]]; then echo '{}' > "$FACETS_TMP"; fi + if [[ ! -s "$PERIPHERY_TMP" ]]; then echo '{}' > "$PERIPHERY_TMP"; fi + + # ensure diamond file exists + if [[ ! -e $DIAMOND_FILE ]]; then + echo "{}" > "$DIAMOND_FILE" fi + + # merge facets and periphery into diamond file atomically + local MERGED + MERGED=$(jq -r --arg diamond_name "$DIAMOND_NAME" --slurpfile facets "$FACETS_TMP" --slurpfile periphery "$PERIPHERY_TMP" ' + .[$diamond_name] = (.[$diamond_name] // {}) | + .[$diamond_name].Facets = $facets[0] | + .[$diamond_name].Periphery = $periphery[0] + ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") + printf %s "$MERGED" > "$DIAMOND_FILE" + + # clean up + rm -rf "$TEMP_DIR" + + success "[$NETWORK] updated diamond log" } function updateDiamondLogs() { From f419d82cd860acc586d83c0e8bfc0f2efbe03de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 9 Sep 2025 10:49:39 +0700 Subject: [PATCH 189/220] update saveDiamondFacets call --- script/tasks/diamondUpdateFacet.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/tasks/diamondUpdateFacet.sh b/script/tasks/diamondUpdateFacet.sh index 74fa7fa66..f5576a9f6 100755 --- a/script/tasks/diamondUpdateFacet.sh +++ b/script/tasks/diamondUpdateFacet.sh @@ -201,7 +201,8 @@ diamondUpdateFacet() { # save facet addresses (only if deploying to staging, otherwise we update the logs after the diamondCut tx gets signed in the SAFE) if [[ "$ENVIRONMENT" != "production" ]]; then - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" + # Using default behavior: update diamond file (not facets-only mode) + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" fi echo "[info] $SCRIPT successfully executed on network $NETWORK in $ENVIRONMENT environment" From 9a0abc431ee23dd0d91692effd296bec85591d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 9 Sep 2025 10:51:08 +0700 Subject: [PATCH 190/220] quote parameters + minor fixes --- script/helperFunctions.sh | 358 ++++++++++++++++++-------------------- 1 file changed, 168 insertions(+), 190 deletions(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 42312a193..84d154c33 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -7,8 +7,6 @@ source .env # load script source script/config.sh - - ZERO_ADDRESS=0x0000000000000000000000000000000000000000 RED='\033[0;31m' # Red color GREEN='\033[0;32m' # Green color @@ -115,9 +113,9 @@ function logContractDeploymentInfo { # Create lock file path local LOCK_FILE="${LOG_FILE_PATH}.lock" - local LOCK_TIMEOUT=30 # 30 seconds timeout + local LOCK_TIMEOUT=30 # 30 seconds timeout local LOCK_ATTEMPTS=0 - local MAX_LOCK_ATTEMPTS=60 # 60 attempts = 30 seconds total + local MAX_LOCK_ATTEMPTS=60 # 60 attempts = 30 seconds total # Wait for lock to be available while [[ -f "$LOCK_FILE" && $LOCK_ATTEMPTS -lt $MAX_LOCK_ATTEMPTS ]]; do @@ -132,7 +130,7 @@ function logContractDeploymentInfo { fi # Create lock file - echo "$$" > "$LOCK_FILE" + echo "$$" >"$LOCK_FILE" # Ensure lock is released on exit trap 'rm -f "$LOCK_FILE" 2>/dev/null' EXIT @@ -578,7 +576,6 @@ function isMongoLoggingEnabled() { fi } - function queryMongoDeployment() { local CONTRACT="$1" local NETWORK="$2" @@ -619,14 +616,6 @@ function getLatestMongoDeployment() { return $? } - - - - - - - - function getUnverifiedContractsFromMongo() { local ENVIRONMENT="$1" @@ -916,8 +905,8 @@ function saveDiamondFacets() { USE_MUTABLE_DIAMOND=$3 FACETS=$4 # optional: output control for parallel orchestration - local OUTPUT_MODE="$5" # "facets-only" to write only facets JSON to OUTPUT_PATH - local OUTPUT_PATH="$6" # path to write facets JSON when in facets-only mode + local OUTPUT_MODE="$5" # "facets-only" to write only facets JSON to OUTPUT_PATH + local OUTPUT_PATH="$6" # path to write facets JSON when in facets-only mode # logging for debug purposes echo "" @@ -978,7 +967,7 @@ function saveDiamondFacets() { NAME=$(getContractNameFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") JSON_ENTRY="{\"$FACET_ADDRESS\": {\"Name\": \"$NAME\", \"Version\": \"\"}}" fi - echo "$JSON_ENTRY" > "$FACETS_DIR/${FACET_ADDRESS}.json" + echo "$JSON_ENTRY" >"$FACETS_DIR/${FACET_ADDRESS}.json" ) & done @@ -1003,7 +992,7 @@ function saveDiamondFacets() { # if called in facets-only mode, output to path and skip touching DIAMOND_FILE or periphery if [[ "$OUTPUT_MODE" == "facets-only" && -n "$OUTPUT_PATH" ]]; then - printf %s "$FACETS_JSON" > "$OUTPUT_PATH" + printf %s "$FACETS_JSON" >"$OUTPUT_PATH" else # ensure diamond file exists if [[ ! -e $DIAMOND_FILE ]]; then @@ -1119,8 +1108,8 @@ function saveDiamondPeriphery() { ENVIRONMENT=$2 USE_MUTABLE_DIAMOND=$3 # optional: output control for parallel orchestration - local OUTPUT_MODE="$4" # "periphery-only" to write only periphery JSON to OUTPUT_PATH - local OUTPUT_PATH="$5" # path to write periphery JSON when in periphery-only mode + local OUTPUT_MODE="$4" # "periphery-only" to write only periphery JSON to OUTPUT_PATH + local OUTPUT_PATH="$5" # path to write periphery JSON when in periphery-only mode # get file suffix based on value in variable ENVIRONMENT local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") @@ -1171,7 +1160,7 @@ function saveDiamondPeriphery() { fi # resolve each periphery address in parallel and write to temp files - for CONTRACT in $PERIPHERY_CONTRACTS; do + for CONTRACT in ${PERIPHERY_CONTRACTS}; do # throttle background jobs while [[ $(jobs | wc -l | tr -d ' ') -ge $CONCURRENCY ]]; do sleep 0.1 @@ -1179,10 +1168,10 @@ function saveDiamondPeriphery() { ( ADDRESS=$(cast call "$DIAMOND_ADDRESS" "getPeripheryContract(string) returns (address)" "$CONTRACT" --rpc-url "$RPC_URL" 2>/dev/null) - if [[ "$ADDRESS" == $ZERO_ADDRESS || -z "$ADDRESS" ]]; then + if [[ "$ADDRESS" == "$ZERO_ADDRESS" || -z "$ADDRESS" ]]; then ADDRESS="" fi - echo "{\"$CONTRACT\": \"$ADDRESS\"}" > "$PERIPHERY_DIR/${CONTRACT}.json" + echo "{\"$CONTRACT\": \"$ADDRESS\"}" >"$PERIPHERY_DIR/${CONTRACT}.json" ) & done @@ -1191,7 +1180,7 @@ function saveDiamondPeriphery() { # merge all periphery JSON entries in the same order as contract list local MERGE_FILES=() - for CONTRACT in $PERIPHERY_CONTRACTS; do + for CONTRACT in ${PERIPHERY_CONTRACTS}; do FILEPATH="$PERIPHERY_DIR/${CONTRACT}.json" if [[ -s "$FILEPATH" ]]; then MERGE_FILES+=("$FILEPATH") @@ -1205,7 +1194,7 @@ function saveDiamondPeriphery() { if [[ "$OUTPUT_MODE" == "periphery-only" && -n "$OUTPUT_PATH" ]]; then # write only the periphery object to the given path - printf %s "$PERIPHERY_JSON" > "$OUTPUT_PATH" + printf %s "$PERIPHERY_JSON" >"$OUTPUT_PATH" else # ensure diamond file exists if [[ ! -e $DIAMOND_FILE ]]; then @@ -1248,9 +1237,9 @@ function saveContract() { # Create lock file path local LOCK_FILE="${ADDRESSES_FILE}.lock" - local LOCK_TIMEOUT=30 # 30 seconds timeout + local LOCK_TIMEOUT=30 # 30 seconds timeout local LOCK_ATTEMPTS=0 - local MAX_LOCK_ATTEMPTS=60 # 60 attempts = 30 seconds total + local MAX_LOCK_ATTEMPTS=60 # 60 attempts = 30 seconds total # Wait for lock to be available while [[ -f "$LOCK_FILE" && $LOCK_ATTEMPTS -lt $MAX_LOCK_ATTEMPTS ]]; do @@ -1265,7 +1254,7 @@ function saveContract() { fi # Create lock file - echo "$$" > "$LOCK_FILE" + echo "$$" >"$LOCK_FILE" # Ensure lock is released on exit trap 'rm -f "$LOCK_FILE" 2>/dev/null' EXIT @@ -1728,9 +1717,9 @@ function parseTargetStateGoogleSpreadsheet() { # Wait for all background jobs and check for failures wait if [ $? -ne 0 ]; then - error "One or more network processing jobs failed" - rm -rf "$TEMP_DIR" - return 1 + error "One or more network processing jobs failed" + rm -rf "$TEMP_DIR" + return 1 fi echo "" @@ -1749,8 +1738,6 @@ function parseTargetStateGoogleSpreadsheet() { return 0 } - - function processNetworkLine() { # Function: processNetworkLine # Description: Processes a single network line from the CSV in parallel @@ -1770,13 +1757,13 @@ function processNetworkLine() { local CONTRACTS_ARRAY_STR="$6" # Convert contracts array string back to array - IFS=$'\n' read -d '' -r -a CONTRACTS_ARRAY <<< "$CONTRACTS_ARRAY_STR" + IFS=$'\n' read -d '' -r -a CONTRACTS_ARRAY <<<"$CONTRACTS_ARRAY_STR" echo "[$NETWORK] Starting processing..." # Create temporary JSON file for this network local NETWORK_JSON_FILE="$TEMP_DIR/${NETWORK}.json" - echo "{}" > "$NETWORK_JSON_FILE" + echo "{}" >"$NETWORK_JSON_FILE" # Split the line by comma into an array IFS=',' read -ra LINE_ARRAY <<<"$LINE" @@ -1826,7 +1813,6 @@ function processNetworkLine() { # check if cell value is "latest" >> find version if [[ "$CELL_VALUE" == "latest" ]]; then - # echo warning that sheet needs to be updated echo "[$NETWORK] Warning: the latest version for contract $CONTRACT is $CURRENT_VERSION. Please update this for network $NETWORK in the Google sheet" >&2 @@ -1874,17 +1860,17 @@ function addContractVersionToNetworkJSON() { # Use jq to add the contract version to the network JSON file jq --arg NETWORK "$NETWORK" \ - --arg ENVIRONMENT "$ENVIRONMENT" \ - --arg CONTRACT "$CONTRACT" \ - --arg DIAMOND_TYPE "$DIAMOND_TYPE" \ - --arg VERSION "$VERSION" \ - ' + --arg ENVIRONMENT "$ENVIRONMENT" \ + --arg CONTRACT "$CONTRACT" \ + --arg DIAMOND_TYPE "$DIAMOND_TYPE" \ + --arg VERSION "$VERSION" \ + ' .[$NETWORK] //= {} | .[$NETWORK][$ENVIRONMENT] //= {} | .[$NETWORK][$ENVIRONMENT][$DIAMOND_TYPE] //= {} | .[$NETWORK][$ENVIRONMENT][$DIAMOND_TYPE][$CONTRACT] = $VERSION ' \ - "$JSON_FILE" > "${JSON_FILE}.tmp" && mv "${JSON_FILE}.tmp" "$JSON_FILE" + "$JSON_FILE" >"${JSON_FILE}.tmp" && mv "${JSON_FILE}.tmp" "$JSON_FILE" } function mergeNetworkResults() { @@ -1906,15 +1892,13 @@ function mergeNetworkResults() { for NETWORK_JSON in "$TEMP_DIR"/*.json; do if [[ -f "$NETWORK_JSON" ]]; then # Merge this network's data into the main target state file - jq -s '.[0] * .[1]' "$MERGED_JSON" "$NETWORK_JSON" > "${MERGED_JSON}.tmp" && mv "${MERGED_JSON}.tmp" "$MERGED_JSON" + jq -s '.[0] * .[1]' "$MERGED_JSON" "$NETWORK_JSON" >"${MERGED_JSON}.tmp" && mv "${MERGED_JSON}.tmp" "$MERGED_JSON" fi done echo "All network results merged into $TARGET_STATE_PATH" } - - function getBytecodeFromArtifact() { # read function arguments into variables local contract="$1" @@ -1959,7 +1943,6 @@ function addPeripheryToDexsJson() { # get number of periphery contracts to be added local ADD_COUNTER=${#CONTRACTS[@]} - # get number of existing DEX addresses in the file for the given network local EXISTING_DEXS=$(jq --arg network "$NETWORK" '.[$network] | length' "$FILEPATH_DEXS") @@ -1987,10 +1970,9 @@ function addPeripheryToDexsJson() { else # add the address to dexs.json local TMP_FILE="tmp.$$.json" - jq --arg address "$CONTRACT_ADDRESS" --arg network "$NETWORK" '(.[$network] //= []) | .[$network] += [$address]' $FILEPATH_DEXS > "$TMP_FILE" && mv "$TMP_FILE" $FILEPATH_DEXS + jq --arg address "$CONTRACT_ADDRESS" --arg network "$NETWORK" '(.[$network] //= []) | .[$network] += [$address]' $FILEPATH_DEXS >"$TMP_FILE" && mv "$TMP_FILE" $FILEPATH_DEXS rm -f "$TMP_FILE" - success "$CONTRACT address $CONTRACT_ADDRESS added to dexs.json[$NETWORK]" fi done @@ -2109,9 +2091,9 @@ function verifyContract() { VERIFY_CMD+=("--verifier-url" "$VERIFIER_URL") fi - echoDebug "VERIFY_CMD: ${VERIFY_CMD[*]}" + echoDebug "VERIFY_CMD: ${VERIFY_CMD[*]}" - # Attempt verification with retries (for cases where block explorer isn't synced) + # Attempt verification with retries (for cases where block explorer isn't synced) while [ $RETRY_COUNT -lt "$MAX_RETRIES" ]; do echo "[info] Attempt $((RETRY_COUNT + 1))/$MAX_RETRIES: Submitting verification for [$FULL_PATH] $ADDRESS..." echo "[info] ...using the following command: " @@ -2801,7 +2783,6 @@ function checkNetworksJsonFilePath() { fi } - function getIncludedNetworksArray() { # prepare required variables checkNetworksJsonFilePath || checkFailure $? "retrieve NETWORKS_JSON_FILE_PATH" @@ -3284,7 +3265,6 @@ function doesDiamondHaveCoreFacetsRegistered() { FACETS_NAMES=($(getCoreFacetsArray)) checkFailure $? "retrieve core facets array from global.json" - # get a list of all facets that the diamond knows KNOWN_FACET_ADDRESSES=$(cast call "$DIAMOND_ADDRESS" "facets() returns ((address,bytes4[])[])" --rpc-url "$RPC_URL") 2>/dev/null local CAST_EXIT_CODE=$? @@ -3720,13 +3700,13 @@ function isZkEvmNetwork() { local NETWORK="$1" # Check if the network exists in networks.json - if ! jq -e --arg network "$NETWORK" '.[$network] != null' "$NETWORKS_JSON_FILE_PATH" > /dev/null; then + if ! jq -e --arg network "$NETWORK" '.[$network] != null' "$NETWORKS_JSON_FILE_PATH" >/dev/null; then error "Network '$NETWORK' not found in networks.json" return 1 fi # Check if isZkEVM property exists for this network - if ! jq -e --arg network "$NETWORK" '.[$network].isZkEVM != null' "$NETWORKS_JSON_FILE_PATH" > /dev/null; then + if ! jq -e --arg network "$NETWORK" '.[$network].isZkEVM != null' "$NETWORKS_JSON_FILE_PATH" >/dev/null; then error "isZkEVM property not defined for network '$NETWORK' in networks.json" return 1 fi @@ -3735,9 +3715,9 @@ function isZkEvmNetwork() { local IS_ZK_EVM=$(jq -r --arg network "$NETWORK" '.[$network].isZkEVM' "$NETWORKS_JSON_FILE_PATH") if [[ "$IS_ZK_EVM" == "true" ]]; then - return 0 # Success (true) + return 0 # Success (true) else - return 1 # Failure (false) + return 1 # Failure (false) fi } @@ -3746,9 +3726,9 @@ function isActiveMainnet() { local NETWORK="$1" # Check if the network exists in the JSON - if ! jq -e --arg network "$NETWORK" '.[$network] != null' "$NETWORKS_JSON_FILE_PATH" > /dev/null; then + if ! jq -e --arg network "$NETWORK" '.[$network] != null' "$NETWORKS_JSON_FILE_PATH" >/dev/null; then error "Network '$NETWORK' not found in networks.json" - return 1 # false + return 1 # false fi local TYPE=$(jq -r --arg network "$NETWORK" '.[$network].type // empty' "$NETWORKS_JSON_FILE_PATH") @@ -3756,9 +3736,9 @@ function isActiveMainnet() { # Check if both values are present and match required conditions if [[ "$TYPE" == "mainnet" && "$STATUS" == "active" ]]; then - return 0 # true + return 0 # true else - return 1 # false + return 1 # false fi } @@ -3847,105 +3827,103 @@ function extractDeployedAddressFromRawReturnData() { fi } - # transfers ownership of the given contract from old wallet to new wallet (e.g. new tester wallet) # will fail if old wallet is not owner # will transfer native funds from new owner to old owner, if old wallet has insufficient funds # will send all remaining native funds from old owner to new owner after ownership transfer transferContractOwnership() { - local PRIV_KEY_OLD_OWNER="$1" - local PRIV_KEY_NEW_OWNER="$2" - local CONTRACT_ADDRESS="$3" - local NETWORK="$4" - - # Define minimum native balance - local MIN_NATIVE_BALANCE=$(convertToBcInt "100000000000000") # 100,000 Gwei - local NATIVE_TRANSFER_GAS_STIPEND=$(convertToBcInt "21000000000000") # 21,000 Gwei - local MIN_NATIVE_BALANCE_DOUBLE=$(convertToBcInt "$MIN_NATIVE_BALANCE * 2") - - local RPC_URL=$(getRPCUrl "$NETWORK") || checkFailure $? "get rpc url" - - # Get address of old and new owner - local ADDRESS_OLD_OWNER=$(cast wallet address --private-key "$PRIV_KEY_OLD_OWNER") - local ADDRESS_NEW_OWNER=$(cast wallet address --private-key "$PRIV_KEY_NEW_OWNER") - echo "Transferring ownership of contract $CONTRACT_ADDRESS on $NETWORK from $ADDRESS_OLD_OWNER to $ADDRESS_NEW_OWNER now" - - # make sure OLD_OWNER is actually contract owner - local CURRENT_OWNER=$(cast call "$CONTRACT_ADDRESS" "owner() returns (address)" --rpc-url "$RPC_URL") - if [[ "$CURRENT_OWNER" -ne "$ADDRESS_OLD_OWNER" ]]; then - error "Current contract owner ($CURRENT_OWNER) does not match with private key of old owner provided ($ADDRESS_OLD_OWNER)" - return 1 - fi + local PRIV_KEY_OLD_OWNER="$1" + local PRIV_KEY_NEW_OWNER="$2" + local CONTRACT_ADDRESS="$3" + local NETWORK="$4" - # Check native funds of old owner wallet - local NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") - local NATIVE_BALANCE_NEW=$(convertToBcInt "$(cast balance "$ADDRESS_NEW_OWNER" --rpc-url "$RPC_URL")") + # Define minimum native balance + local MIN_NATIVE_BALANCE=$(convertToBcInt "100000000000000") # 100,000 Gwei + local NATIVE_TRANSFER_GAS_STIPEND=$(convertToBcInt "21000000000000") # 21,000 Gwei + local MIN_NATIVE_BALANCE_DOUBLE=$(convertToBcInt "$MIN_NATIVE_BALANCE * 2") - echo "native balance old owner: $NATIVE_BALANCE_OLD" - echo "native balance new owner: $NATIVE_BALANCE_NEW" + local RPC_URL=$(getRPCUrl "$NETWORK") || checkFailure $? "get rpc url" - # make sure that sufficient native balances are available on both wallets - if (( $(echo "$NATIVE_BALANCE_OLD < $MIN_NATIVE_BALANCE" | bc -l) )); then - echo "old balance is low" - if (( $(echo "$NATIVE_BALANCE_NEW < $MIN_NATIVE_BALANCE_DOUBLE" | bc -l) )); then - echo "balance of new owner wallet is too low. Cannot continue" - return 1 - else - echo "sending ""$MIN_NATIVE_BALANCE"" native tokens from new (""$ADDRESS_NEW_OWNER"") to old wallet (""$ADDRESS_OLD_OWNER"") now" - # Send some funds from new to old wallet - cast send "$ADDRESS_OLD_OWNER" --value "$MIN_NATIVE_BALANCE" --private-key "$PRIV_KEY_NEW_OWNER" --rpc-url "$RPC_URL" + # Get address of old and new owner + local ADDRESS_OLD_OWNER=$(cast wallet address --private-key "$PRIV_KEY_OLD_OWNER") + local ADDRESS_NEW_OWNER=$(cast wallet address --private-key "$PRIV_KEY_NEW_OWNER") + echo "Transferring ownership of contract $CONTRACT_ADDRESS on $NETWORK from $ADDRESS_OLD_OWNER to $ADDRESS_NEW_OWNER now" - NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") - NATIVE_BALANCE_NEW=$(convertToBcInt "$(cast balance "$ADDRESS_NEW_OWNER" --rpc-url "$RPC_URL")") - echo "" - echo "native balance old owner: $NATIVE_BALANCE_OLD" - echo "native balance new owner: $NATIVE_BALANCE_NEW" - fi - fi + # make sure OLD_OWNER is actually contract owner + local CURRENT_OWNER=$(cast call "$CONTRACT_ADDRESS" "owner() returns (address)" --rpc-url "$RPC_URL") + if [[ "$CURRENT_OWNER" -ne "$ADDRESS_OLD_OWNER" ]]; then + error "Current contract owner ($CURRENT_OWNER) does not match with private key of old owner provided ($ADDRESS_OLD_OWNER)" + return 1 + fi - # # transfer ownership to new owner - echo "" - echo "[info] calling transferOwnership() function from old owner wallet now" - cast send "$CONTRACT_ADDRESS" "transferOwnership(address)" "$ADDRESS_NEW_OWNER" --private-key "$PRIV_KEY_OLD_OWNER" --rpc-url "$RPC_URL" - echo "" + # Check native funds of old owner wallet + local NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") + local NATIVE_BALANCE_NEW=$(convertToBcInt "$(cast balance "$ADDRESS_NEW_OWNER" --rpc-url "$RPC_URL")") - # # accept ownership transfer - echo "" - echo "[info] calling confirmOwnershipTransfer() function from new owner wallet now" - cast send "$CONTRACT_ADDRESS" "confirmOwnershipTransfer()" --private-key "$PRIV_KEY_NEW_OWNER" --rpc-url "$RPC_URL" - echo "" - echo "" + echo "native balance old owner: $NATIVE_BALANCE_OLD" + echo "native balance new owner: $NATIVE_BALANCE_NEW" - # send remaining native tokens from old owner wallet to new owner wallet - NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") - SENDABLE_BALANCE=$(convertToBcInt "$NATIVE_BALANCE_OLD - $NATIVE_TRANSFER_GAS_STIPEND") - if [[ $SENDABLE_BALANCE -gt 0 ]]; then - echo "" - echo "sending ""$SENDABLE_BALANCE"" native tokens from old (""$ADDRESS_OLD_OWNER"") to new wallet (""$ADDRESS_NEW_OWNER"") now" - cast send "$ADDRESS_NEW_OWNER" --value "$SENDABLE_BALANCE" --private-key "$PRIV_KEY_OLD_OWNER" --rpc-url "$RPC_URL" + # make sure that sufficient native balances are available on both wallets + if (($(echo "$NATIVE_BALANCE_OLD < $MIN_NATIVE_BALANCE" | bc -l))); then + echo "old balance is low" + if (($(echo "$NATIVE_BALANCE_NEW < $MIN_NATIVE_BALANCE_DOUBLE" | bc -l))); then + echo "balance of new owner wallet is too low. Cannot continue" + return 1 else - echo "remaining native balance in old wallet is too low to send back to new wallet" + echo "sending ""$MIN_NATIVE_BALANCE"" native tokens from new (""$ADDRESS_NEW_OWNER"") to old wallet (""$ADDRESS_OLD_OWNER"") now" + # Send some funds from new to old wallet + cast send "$ADDRESS_OLD_OWNER" --value "$MIN_NATIVE_BALANCE" --private-key "$PRIV_KEY_NEW_OWNER" --rpc-url "$RPC_URL" + + NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") + NATIVE_BALANCE_NEW=$(convertToBcInt "$(cast balance "$ADDRESS_NEW_OWNER" --rpc-url "$RPC_URL")") + echo "" + echo "native balance old owner: $NATIVE_BALANCE_OLD" + echo "native balance new owner: $NATIVE_BALANCE_NEW" fi + fi - # check balances - NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") - NATIVE_BALANCE_NEW=$(convertToBcInt "$(cast balance "$ADDRESS_NEW_OWNER" --rpc-url "$RPC_URL")") - echo "" - echo "native balance old owner: $NATIVE_BALANCE_OLD" - echo "native balance new owner: $NATIVE_BALANCE_NEW" + # # transfer ownership to new owner + echo "" + echo "[info] calling transferOwnership() function from old owner wallet now" + cast send "$CONTRACT_ADDRESS" "transferOwnership(address)" "$ADDRESS_NEW_OWNER" --private-key "$PRIV_KEY_OLD_OWNER" --rpc-url "$RPC_URL" + echo "" - # make sure NEW OWNER is actually contract owner - CURRENT_OWNER=$(cast call "$CONTRACT_ADDRESS" "owner() returns (address)" --rpc-url "$RPC_URL") + # # accept ownership transfer + echo "" + echo "[info] calling confirmOwnershipTransfer() function from new owner wallet now" + cast send "$CONTRACT_ADDRESS" "confirmOwnershipTransfer()" --private-key "$PRIV_KEY_NEW_OWNER" --rpc-url "$RPC_URL" + echo "" + echo "" + + # send remaining native tokens from old owner wallet to new owner wallet + NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") + SENDABLE_BALANCE=$(convertToBcInt "$NATIVE_BALANCE_OLD - $NATIVE_TRANSFER_GAS_STIPEND") + if [[ $SENDABLE_BALANCE -gt 0 ]]; then echo "" - if [[ "$CURRENT_OWNER" -ne "$ADDRESS_NEW_OWNER" ]]; then - error "Current contract owner ($CURRENT_OWNER) does not match with new owner address ($ADDRESS_NEW_OWNER). Ownership transfer failed" - return 1 - else - echo "Ownership transfer executed successfully" - return 0 - fi -} + echo "sending ""$SENDABLE_BALANCE"" native tokens from old (""$ADDRESS_OLD_OWNER"") to new wallet (""$ADDRESS_NEW_OWNER"") now" + cast send "$ADDRESS_NEW_OWNER" --value "$SENDABLE_BALANCE" --private-key "$PRIV_KEY_OLD_OWNER" --rpc-url "$RPC_URL" + else + echo "remaining native balance in old wallet is too low to send back to new wallet" + fi + + # check balances + NATIVE_BALANCE_OLD=$(convertToBcInt "$(cast balance "$ADDRESS_OLD_OWNER" --rpc-url "$RPC_URL")") + NATIVE_BALANCE_NEW=$(convertToBcInt "$(cast balance "$ADDRESS_NEW_OWNER" --rpc-url "$RPC_URL")") + echo "" + echo "native balance old owner: $NATIVE_BALANCE_OLD" + echo "native balance new owner: $NATIVE_BALANCE_NEW" + # make sure NEW OWNER is actually contract owner + CURRENT_OWNER=$(cast call "$CONTRACT_ADDRESS" "owner() returns (address)" --rpc-url "$RPC_URL") + echo "" + if [[ "$CURRENT_OWNER" -ne "$ADDRESS_NEW_OWNER" ]]; then + error "Current contract owner ($CURRENT_OWNER) does not match with new owner address ($ADDRESS_NEW_OWNER). Ownership transfer failed" + return 1 + else + echo "Ownership transfer executed successfully" + return 0 + fi +} function printDeploymentsStatus() { # read function arguments into variables @@ -4377,9 +4355,9 @@ function sendMessageToSlackSmartContractsChannel() { # Send the message curl -H "Content-Type: application/json" \ - -X POST \ - -d "{\"text\": \"$MESSAGE\"}" \ - "$SLACK_WEBHOOK_SC_GENERAL" + -X POST \ + -d "{\"text\": \"$MESSAGE\"}" \ + "$SLACK_WEBHOOK_SC_GENERAL" echoDebug "Log message sent to Slack" @@ -4393,7 +4371,7 @@ function getUserInfo() { # log Github email address EMAIL=$(git config --global user.email) if [ -z "$EMAIL" ]; then - EMAIL=$(git config --local user.email) + EMAIL=$(git config --local user.email) fi # return collected info @@ -4436,7 +4414,7 @@ function updateDiamondLogForNetwork() { attempts=0 # initialize attempts to 0 while [ $attempts -lt "$MAX_ATTEMPTS_PER_SCRIPT_EXECUTION" ]; do - echo "[$NETWORK] Trying to get facets for diamond $DIAMOND_ADDRESS now - attempt $((attempts+1))" + echo "[$NETWORK] Trying to get facets for diamond $DIAMOND_ADDRESS now - attempt $((attempts + 1))" # try to execute call local KNOWN_FACET_ADDRESSES=$(cast call "$DIAMOND_ADDRESS" "facetAddresses() returns (address[])" --rpc-url "$RPC_URL") 2>/dev/null @@ -4449,7 +4427,7 @@ function updateDiamondLogForNetwork() { sleep 1 # wait for 1 second before trying the operation again done - if [ $attempts -eq $((MAX_ATTEMPTS_PER_SCRIPT_EXECUTION+1)) ]; then + if [ $attempts -eq $((MAX_ATTEMPTS_PER_SCRIPT_EXECUTION + 1)) ]; then warning "[$NETWORK] Failed to get facets from diamond $DIAMOND_ADDRESS after $MAX_ATTEMPTS_PER_SCRIPT_EXECUTION attempts" fi @@ -4469,7 +4447,7 @@ function updateDiamondLogForNetwork() { # start facets resolution (if available) in background if [[ -z $KNOWN_FACET_ADDRESSES ]]; then warning "[$NETWORK] no facets found in diamond $DIAMOND_ADDRESS" - echo '{}' > "$FACETS_TMP" + echo '{}' >"$FACETS_TMP" else saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" & fi @@ -4480,12 +4458,12 @@ function updateDiamondLogForNetwork() { if [[ -n "$PID_FACETS" ]]; then wait "$PID_FACETS"; fi # validate temp outputs exist - if [[ ! -s "$FACETS_TMP" ]]; then echo '{}' > "$FACETS_TMP"; fi - if [[ ! -s "$PERIPHERY_TMP" ]]; then echo '{}' > "$PERIPHERY_TMP"; fi + if [[ ! -s "$FACETS_TMP" ]]; then echo '{}' >"$FACETS_TMP"; fi + if [[ ! -s "$PERIPHERY_TMP" ]]; then echo '{}' >"$PERIPHERY_TMP"; fi # ensure diamond file exists if [[ ! -e $DIAMOND_FILE ]]; then - echo "{}" > "$DIAMOND_FILE" + echo "{}" >"$DIAMOND_FILE" fi # merge facets and periphery into diamond file atomically @@ -4495,7 +4473,7 @@ function updateDiamondLogForNetwork() { .[$diamond_name].Facets = $facets[0] | .[$diamond_name].Periphery = $periphery[0] ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") - printf %s "$MERGED" > "$DIAMOND_FILE" + printf %s "$MERGED" >"$DIAMOND_FILE" # clean up rm -rf "$TEMP_DIR" @@ -4600,8 +4578,8 @@ install_foundry_zksync() { # Verify that FOUNDRY_ZKSYNC_VERSION is set if [ -z "${FOUNDRY_ZKSYNC_VERSION}" ]; then - echo "Error: FOUNDRY_ZKSYNC_VERSION is not set" - return 1 + echo "Error: FOUNDRY_ZKSYNC_VERSION is not set" + return 1 fi echo "Using Foundry zkSync version: ${FOUNDRY_ZKSYNC_VERSION}" @@ -4609,37 +4587,37 @@ install_foundry_zksync() { # Check if binaries already exist and are executable # -x tests if a file exists and has execute permissions if [ -x "${install_dir}/forge" ] && [ -x "${install_dir}/cast" ]; then - echo "forge and cast binaries already exist in ${install_dir} and are executable" - echo "Skipping download and installation" - return 0 + echo "forge and cast binaries already exist in ${install_dir} and are executable" + echo "Skipping download and installation" + return 0 fi # Detect operating system # $OSTYPE is a bash variable that contains the operating system type local os if [[ "$OSTYPE" == "darwin"* ]]; then - os="darwin" + os="darwin" elif [[ "$OSTYPE" == "linux-gnu"* ]]; then - os="linux" + os="linux" else - echo "Unsupported operating system" - return 1 + echo "Unsupported operating system" + return 1 fi # Detect CPU architecture # uname -m returns the machine hardware name local arch case $(uname -m) in - x86_64) # Intel/AMD 64-bit - arch="amd64" - ;; - arm64|aarch64) # ARM 64-bit (e.g., Apple Silicon, AWS Graviton) - arch="arm64" - ;; - *) - echo "Unsupported architecture: $(uname -m)" - return 1 - ;; + x86_64) # Intel/AMD 64-bit + arch="amd64" + ;; + arm64 | aarch64) # ARM 64-bit (e.g., Apple Silicon, AWS Graviton) + arch="arm64" + ;; + *) + echo "Unsupported architecture: $(uname -m)" + return 1 + ;; esac # Construct download URL using the specified version @@ -4660,22 +4638,22 @@ install_foundry_zksync() { # Download the file using curl or wget, whichever is available # command -v checks if a command exists # &> /dev/null redirects both stdout and stderr to null - if command -v curl &> /dev/null; then - # -L flag follows redirects, -o specifies output file - curl -L -o "${install_dir}/${filename}" "$download_url" - elif command -v wget &> /dev/null; then - # -O specifies output file - wget -O "${install_dir}/${filename}" "$download_url" + if command -v curl &>/dev/null; then + # -L flag follows redirects, -o specifies output file + curl -L -o "${install_dir}/${filename}" "$download_url" + elif command -v wget &>/dev/null; then + # -O specifies output file + wget -O "${install_dir}/${filename}" "$download_url" else - echo "Neither curl nor wget is installed" - return 1 + echo "Neither curl nor wget is installed" + return 1 fi # Check if download was successful # $? contains the return status of the last command if [ $? -ne 0 ]; then - echo "Download failed" - return 1 + echo "Download failed" + return 1 fi echo "Download completed successfully" @@ -4686,8 +4664,8 @@ install_foundry_zksync() { tar -xzf "${install_dir}/${filename}" -C "$install_dir" if [ $? -ne 0 ]; then - echo "Extraction failed" - return 1 + echo "Extraction failed" + return 1 fi # Make binaries executable @@ -4696,8 +4674,8 @@ install_foundry_zksync() { chmod +x "${install_dir}/forge" "${install_dir}/cast" if [ $? -ne 0 ]; then - echo "Failed to set executable permissions" - return 1 + echo "Failed to set executable permissions" + return 1 fi # Clean up by removing the downloaded archive @@ -4705,15 +4683,15 @@ install_foundry_zksync() { rm "${install_dir}/${filename}" if [ $? -ne 0 ]; then - echo "Cleanup failed" - return 1 + echo "Cleanup failed" + return 1 fi # Verify that binaries are executable # This is a final check to ensure everything worked if [ ! -x "${install_dir}/forge" ] || [ ! -x "${install_dir}/cast" ]; then - echo "Installation completed but binaries are not executable. Please check permissions." - return 1 + echo "Installation completed but binaries are not executable. Please check permissions." + return 1 fi echo "Installation completed successfully" @@ -4776,7 +4754,7 @@ getContractDeploymentStatusSummary() { printf "%-20s %-10s %-10s %-42s\n" "NETWORK" "DEPLOYED" "VERIFIED" "ADDRESS" printf "%-20s %-10s %-10s %-42s\n" "--------------------" "----------" "----------" "------------------------------------------" - # Check each network + # Check each network for network in "${NETWORKS[@]}"; do # Check if contract is deployed - use a more robust approach local LOG_ENTRY="" From e45b558bb88c3cb3e37072d64afb847731c21104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Bl=C3=A4cker?= Date: Tue, 9 Sep 2025 10:55:34 +0700 Subject: [PATCH 191/220] fixed PID issue --- script/helperFunctions.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 84d154c33..ba87e3d2a 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4432,7 +4432,8 @@ function updateDiamondLogForNetwork() { fi # prepare for parallel facet/periphery processing and final merge - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") + local FILE_SUFFIX + FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") local DIAMOND_FILE="./deployments/${NETWORK}.diamond.${FILE_SUFFIX}json" local DIAMOND_NAME="LiFiDiamond" local TEMP_DIR @@ -4445,13 +4446,14 @@ function updateDiamondLogForNetwork() { local PID_PERIPHERY=$! # start facets resolution (if available) in background + local PID_FACETS if [[ -z $KNOWN_FACET_ADDRESSES ]]; then warning "[$NETWORK] no facets found in diamond $DIAMOND_ADDRESS" echo '{}' >"$FACETS_TMP" else saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" & + PID_FACETS=$! fi - local PID_FACETS=$! # wait for both background jobs to complete if [[ -n "$PID_PERIPHERY" ]]; then wait "$PID_PERIPHERY"; fi From aff1f53db22e8b22d611c1b60d8659445e84dfab Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 13:56:27 +0200 Subject: [PATCH 192/220] changes --- .../arbitrum.diamond.immutable.staging.json | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 deployments/arbitrum.diamond.immutable.staging.json diff --git a/deployments/arbitrum.diamond.immutable.staging.json b/deployments/arbitrum.diamond.immutable.staging.json deleted file mode 100644 index 19aef1d78..000000000 --- a/deployments/arbitrum.diamond.immutable.staging.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "LiFiDiamondImmutable": { - "Facets": {}, - "Periphery": { - "ERC20Proxy": "0xF6d5cf7a12d89BC0fD34E27d2237875b564A6ADf", - "Executor": "0x23f882bA2fa54A358d8599465EB471f58Cc26751", - "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", - "GasZipPeriphery": "", - "LidoWrapper": "", - "Patcher": "0x3971A968c03cd9640239C937F8d30D024840E691", - "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", - "ReceiverAcrossV3": "0xe4F3DEF14D61e47c696374453CD64d438FD277F8", - "ReceiverChainflip": "", - "ReceiverStargateV2": "", - "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" - } - } -} \ No newline at end of file From 547dd3149a9778f524470bc28097e5874b39124c Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 14:22:56 +0200 Subject: [PATCH 193/220] changes --- ...ging.json => bsc.diamond.lda.staging.json} | 3 +- deployments/bsc.diamond.staging.json | 101 ++---------------- ...json => optimism.diamond.lda.staging.json} | 13 +++ deployments/optimism.diamond.staging.json | 19 ++-- script/helperFunctions.sh | 28 +++-- 5 files changed, 52 insertions(+), 112 deletions(-) rename deployments/{bsc.lda.diamond.staging.json => bsc.diamond.lda.staging.json} (98%) rename deployments/{optimism.lda.diamond.staging.json => optimism.diamond.lda.staging.json} (74%) diff --git a/deployments/bsc.lda.diamond.staging.json b/deployments/bsc.diamond.lda.staging.json similarity index 98% rename from deployments/bsc.lda.diamond.staging.json rename to deployments/bsc.diamond.lda.staging.json index 3b16a040f..3f3f05ef5 100644 --- a/deployments/bsc.lda.diamond.staging.json +++ b/deployments/bsc.diamond.lda.staging.json @@ -53,6 +53,7 @@ "Name": "VelodromeV2Facet", "Version": "1.0.0" } - } + }, + "Periphery": {} } } \ No newline at end of file diff --git a/deployments/bsc.diamond.staging.json b/deployments/bsc.diamond.staging.json index f89fccfee..ed1a2093f 100644 --- a/deployments/bsc.diamond.staging.json +++ b/deployments/bsc.diamond.staging.json @@ -1,103 +1,18 @@ { "LiFiDiamond": { - "Facets": { - "0xf03AFcA857918BE01EBD6C6800Fc2974b8a9eBA2": { - "Name": "EmergencyPauseFacet", - "Version": "1.0.0" - }, - "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6": { - "Name": "GenericSwapFacetV3", - "Version": "1.0.1" - }, - "0x06045F5FA6EA7c6AcEb104b55BcD6C3dE3a08831": { - "Name": "DiamondCutFacet", - "Version": "1.0.0" - }, - "0x8938CEa23C3c5eAABb895765f5B0b2b07D680402": { - "Name": "DiamondLoupeFacet", - "Version": "1.0.0" - }, - "0x53d4Bcd5BEa4e863376b7eA43D7465351a4d71B0": { - "Name": "OwnershipFacet", - "Version": "1.0.0" - }, - "0x83be9a6642c41f7ef78A0B60a355B5D7f3C9A62f": { - "Name": "WithdrawFacet", - "Version": "1.0.0" - }, - "0x5c9cbc3FB4FF588199BFbE68b72Fc127dd0D8630": { - "Name": "DexManagerFacet", - "Version": "1.0.1" - }, - "0x1A841931913806FB7570B43bcD64A487A8E7A50c": { - "Name": "AccessManagerFacet", - "Version": "1.0.0" - }, - "0x27A0B9Dd7ee2762e15CCF36DF2F54A4A7B7a9304": { - "Name": "PeripheryRegistryFacet", - "Version": "1.0.0" - }, - "0x9E39c6906F8FBC16D7CC996aB81bcBeD0F6E021d": { - "Name": "AllBridgeFacet", - "Version": "2.0.0" - }, - "0xd6aba485e836d1C717734fFD00A25d16Cf738b49": { - "Name": "", - "Version": "" - }, - "0x9a077b9dD746237d8854848BDB01521B47edC8DF": { - "Name": "LIFuelFacet", - "Version": "1.0.0" - }, - "0x04BD4b6430483cFdD0450D0aFb08633d33C93275": { - "Name": "", - "Version": "" - }, - "0x297aF81049744284874A7e5E90A907bAF6ACbbb5": { - "Name": "", - "Version": "" - }, - "0xAfcC5c55d5Ec3082675D51331E7Ed9AdE195db48": { - "Name": "StargateFacet", - "Version": "2.2.0" - }, - "0xa6aAe470E7B8E8916e692882A5db25bB40C398A7": { - "Name": "ThorSwapFacet", - "Version": "0.0.3" - }, - "0x8BFC9f022ACb65dfa0Eb6CCbA45c04C2a6cb9A34": { - "Name": "SquidFacet", - "Version": "0.0.8" - }, - "0x7ac3EB2D191EBAb9E925CAbFD4F8155be066b3aa": { - "Name": "AmarokFacetPacked", - "Version": "1.0.0" - }, - "0xA08EcCb4aDd1556CC42ABD5d8dFbEe8a56012359": { - "Name": "GenericSwapFacet", - "Version": "1.0.0" - }, - "0x089153117bffd37CBbE0c604dAE8e493D4743fA8": { - "Name": "StargateFacetV2", - "Version": "1.0.1" - }, - "0x0DAff7e73fDb2bbaDa232A16a5BEA72463893E35": { - "Name": "GasZipFacet", - "Version": "2.0.0" - } - }, + "Facets": {}, "Periphery": { "ERC20Proxy": "0xf90a432dD1D0541470BC9C440d9dEc3659755238", "Executor": "0x4f3B1b1075cC19daA15b7cc681b28e2fB82145eD", "FeeCollector": "0x7f98D45c7902f079fDb65811B633522e2d227BB6", "GasZipPeriphery": "0x46d8Aa20D5aD98927Cf885De9eBf9436E8E551c2", - "LiFiDEXAggregator": "0xD6f02718B9df9FAd2665c7304BC5b26D5bbD8642", - "LiFuelFeeCollector": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "Receiver": "0x76EE0F8fb09047284B6ea89881595Fc6F5B09E12", - "ReceiverAcrossV3": "0x76EE0F8fb09047284B6ea89881595Fc6F5B09E12", + "LidoWrapper": "", + "Patcher": "", + "Permit2Proxy": "", + "ReceiverAcrossV3": "", + "ReceiverChainflip": "", "ReceiverStargateV2": "", - "RelayerCelerIM": "", - "TokenWrapper": "" + "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d" } } -} +} \ No newline at end of file diff --git a/deployments/optimism.lda.diamond.staging.json b/deployments/optimism.diamond.lda.staging.json similarity index 74% rename from deployments/optimism.lda.diamond.staging.json rename to deployments/optimism.diamond.lda.staging.json index fb16d2ca1..b6c064c2d 100644 --- a/deployments/optimism.lda.diamond.staging.json +++ b/deployments/optimism.diamond.lda.staging.json @@ -53,6 +53,19 @@ "Name": "VelodromeV2Facet", "Version": "1.0.0" } + }, + "Periphery": { + "ERC20Proxy": "0xF6d5cf7a12d89BC0fD34E27d2237875b564A6ADf", + "Executor": "0x23f882bA2fa54A358d8599465EB471f58Cc26751", + "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", + "GasZipPeriphery": "", + "LidoWrapper": "", + "Patcher": "", + "Permit2Proxy": "0x808eb38763f3F51F9C47bc93Ef8d5aB7E6241F46", + "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", + "ReceiverChainflip": "", + "ReceiverStargateV2": "", + "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" } } } \ No newline at end of file diff --git a/deployments/optimism.diamond.staging.json b/deployments/optimism.diamond.staging.json index d4805ed99..0b839094e 100644 --- a/deployments/optimism.diamond.staging.json +++ b/deployments/optimism.diamond.staging.json @@ -85,6 +85,10 @@ "Name": "CelerCircleBridgeFacet", "Version": "1.0.1" }, + "0x2Af20933E5886aFe275c0EEE4A2e65daA4E8b169": { + "Name": "CelerIMFacetMutable", + "Version": "" + }, "0x380157643592725677F165b67642448CDCAeE026": { "Name": "HopFacet", "Version": "2.0.0" @@ -145,6 +149,10 @@ "Name": "", "Version": "" }, + "0xf536ed5A4310455FF39dBf90336e17d11550E7b4": { + "Name": "", + "Version": "" + }, "0x36e1375B0755162d720276dFF6893DF02bd49225": { "Name": "GlacisFacet", "Version": "1.1.0" @@ -157,13 +165,9 @@ "Name": "MayanFacet", "Version": "1.2.2" }, - "0xc2A0D799744536C621Af9B2933CdB4Ad959980bF": { - "Name": "AcrossFacetV4", - "Version": "1.0.0" - }, - "0x8962d191Ba0f2Bc29b949ACA222f8251B241190b": { - "Name": "AcrossFacetPackedV4", - "Version": "1.0.0" + "0xc4884225aeFe7218f9f489A5Eb8beB504ab272AA": { + "Name": "", + "Version": "" } }, "Periphery": { @@ -172,7 +176,6 @@ "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", "GasZipPeriphery": "", "LidoWrapper": "", - "LiFiDEXAggregator": "", "Patcher": "", "Permit2Proxy": "0x808eb38763f3F51F9C47bc93Ef8d5aB7E6241F46", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index d2e3af9d6..a8bdccf0b 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4440,24 +4440,32 @@ function updateDiamondLogForNetwork() { local FACETS_TMP="$TEMP_DIR/facets_${DIAMOND_CONTRACT_NAME}.json" local PERIPHERY_TMP="$TEMP_DIR/periphery_${DIAMOND_CONTRACT_NAME}.json" + # export DIAMOND_CONTRACT_NAME for the functions to use + export DIAMOND_CONTRACT_NAME="$DIAMOND_CONTRACT_NAME" - # start periphery resolution in background - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "true" "periphery-only" "$PERIPHERY_TMP" & - local PID_PERIPHERY=$! + # start periphery and facets resolution in parallel + local pids=() + + # only process periphery for LiFiDiamond and LiFiDiamondImmutable, skip for LiFiDEXAggregatorDiamond + if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond" ]]; then + saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "periphery-only" "$PERIPHERY_TMP" & + pids+=($!) + else + # For LiFiDEXAggregatorDiamond, create empty periphery file + echo '{}' >"$PERIPHERY_TMP" + fi - # start facets resolution (if available) in background - local PID_FACETS + # Start facets resolution (for all diamond types) if [[ -z $KNOWN_FACET_ADDRESSES ]]; then - warning "[$NETWORK] no facets found in diamond $DIAMOND_ADDRESS" + warning "[$NETWORK] no facets found in $DIAMOND_CONTRACT_NAME $DIAMOND_ADDRESS" echo '{}' >"$FACETS_TMP" else - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" & - PID_FACETS=$! + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" & + pids+=($!) fi # wait for both background jobs to complete - if [[ -n "$PID_PERIPHERY" ]]; then wait "$PID_PERIPHERY"; fi - if [[ -n "$PID_FACETS" ]]; then wait "$PID_FACETS"; fi + if [[ -n "$pids" ]]; then wait "$pids"; fi # validate temp outputs exist if [[ ! -s "$FACETS_TMP" ]]; then echo '{}' >"$FACETS_TMP"; fi From fe50631e5996610291773083239144b7bd7117ab Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 19:19:43 +0200 Subject: [PATCH 194/220] changes --- deployments/arbitrum.diamond.lda.staging.json | 14 +-- deployments/bsc.diamond.staging.json | 87 ++++++++++++++++++- deployments/optimism.diamond.lda.staging.json | 14 +-- script/helperFunctions.sh | 83 ++++++++---------- script/tasks/diamondUpdateFacet.sh | 2 +- 5 files changed, 127 insertions(+), 73 deletions(-) diff --git a/deployments/arbitrum.diamond.lda.staging.json b/deployments/arbitrum.diamond.lda.staging.json index c4ea9c350..a67ea3d81 100644 --- a/deployments/arbitrum.diamond.lda.staging.json +++ b/deployments/arbitrum.diamond.lda.staging.json @@ -54,18 +54,6 @@ "Version": "1.0.0" } }, - "Periphery": { - "ERC20Proxy": "0xF6d5cf7a12d89BC0fD34E27d2237875b564A6ADf", - "Executor": "0x23f882bA2fa54A358d8599465EB471f58Cc26751", - "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", - "GasZipPeriphery": "", - "LidoWrapper": "", - "Patcher": "0x3971A968c03cd9640239C937F8d30D024840E691", - "Permit2Proxy": "0xb33Fe241BEd9bf5F694101D7498F63a0d060F999", - "ReceiverAcrossV3": "0xe4F3DEF14D61e47c696374453CD64d438FD277F8", - "ReceiverChainflip": "", - "ReceiverStargateV2": "", - "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" - } + "Periphery": {} } } \ No newline at end of file diff --git a/deployments/bsc.diamond.staging.json b/deployments/bsc.diamond.staging.json index ed1a2093f..b7528d86d 100644 --- a/deployments/bsc.diamond.staging.json +++ b/deployments/bsc.diamond.staging.json @@ -1,6 +1,91 @@ { "LiFiDiamond": { - "Facets": {}, + "Facets": { + "0xf03AFcA857918BE01EBD6C6800Fc2974b8a9eBA2": { + "Name": "EmergencyPauseFacet", + "Version": "1.0.0" + }, + "0xE871874D8AC30E8aCD0eC67529b4a5dDD73Bf0d6": { + "Name": "GenericSwapFacetV3", + "Version": "1.0.1" + }, + "0x06045F5FA6EA7c6AcEb104b55BcD6C3dE3a08831": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0x8938CEa23C3c5eAABb895765f5B0b2b07D680402": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x53d4Bcd5BEa4e863376b7eA43D7465351a4d71B0": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0x83be9a6642c41f7ef78A0B60a355B5D7f3C9A62f": { + "Name": "WithdrawFacet", + "Version": "1.0.0" + }, + "0x5c9cbc3FB4FF588199BFbE68b72Fc127dd0D8630": { + "Name": "DexManagerFacet", + "Version": "1.0.1" + }, + "0x1A841931913806FB7570B43bcD64A487A8E7A50c": { + "Name": "AccessManagerFacet", + "Version": "1.0.0" + }, + "0x27A0B9Dd7ee2762e15CCF36DF2F54A4A7B7a9304": { + "Name": "PeripheryRegistryFacet", + "Version": "1.0.0" + }, + "0x9E39c6906F8FBC16D7CC996aB81bcBeD0F6E021d": { + "Name": "AllBridgeFacet", + "Version": "2.0.0" + }, + "0xd6aba485e836d1C717734fFD00A25d16Cf738b49": { + "Name": "", + "Version": "" + }, + "0x9a077b9dD746237d8854848BDB01521B47edC8DF": { + "Name": "LIFuelFacet", + "Version": "1.0.0" + }, + "0x04BD4b6430483cFdD0450D0aFb08633d33C93275": { + "Name": "", + "Version": "" + }, + "0x297aF81049744284874A7e5E90A907bAF6ACbbb5": { + "Name": "", + "Version": "" + }, + "0xAfcC5c55d5Ec3082675D51331E7Ed9AdE195db48": { + "Name": "StargateFacet", + "Version": "2.2.0" + }, + "0xa6aAe470E7B8E8916e692882A5db25bB40C398A7": { + "Name": "ThorSwapFacet", + "Version": "0.0.3" + }, + "0x8BFC9f022ACb65dfa0Eb6CCbA45c04C2a6cb9A34": { + "Name": "SquidFacet", + "Version": "0.0.8" + }, + "0x7ac3EB2D191EBAb9E925CAbFD4F8155be066b3aa": { + "Name": "AmarokFacetPacked", + "Version": "1.0.0" + }, + "0xA08EcCb4aDd1556CC42ABD5d8dFbEe8a56012359": { + "Name": "GenericSwapFacet", + "Version": "1.0.0" + }, + "0x089153117bffd37CBbE0c604dAE8e493D4743fA8": { + "Name": "StargateFacetV2", + "Version": "1.0.1" + }, + "0x0DAff7e73fDb2bbaDa232A16a5BEA72463893E35": { + "Name": "GasZipFacet", + "Version": "2.0.0" + } + }, "Periphery": { "ERC20Proxy": "0xf90a432dD1D0541470BC9C440d9dEc3659755238", "Executor": "0x4f3B1b1075cC19daA15b7cc681b28e2fB82145eD", diff --git a/deployments/optimism.diamond.lda.staging.json b/deployments/optimism.diamond.lda.staging.json index b6c064c2d..42671d06e 100644 --- a/deployments/optimism.diamond.lda.staging.json +++ b/deployments/optimism.diamond.lda.staging.json @@ -54,18 +54,6 @@ "Version": "1.0.0" } }, - "Periphery": { - "ERC20Proxy": "0xF6d5cf7a12d89BC0fD34E27d2237875b564A6ADf", - "Executor": "0x23f882bA2fa54A358d8599465EB471f58Cc26751", - "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", - "GasZipPeriphery": "", - "LidoWrapper": "", - "Patcher": "", - "Permit2Proxy": "0x808eb38763f3F51F9C47bc93Ef8d5aB7E6241F46", - "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", - "ReceiverChainflip": "", - "ReceiverStargateV2": "", - "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" - } + "Periphery": {} } } \ No newline at end of file diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index a8bdccf0b..7485718ed 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -873,8 +873,9 @@ function saveDiamondFacets() { local USE_MUTABLE_DIAMOND=$3 local FACETS=$4 # optional: output control for parallel orchestration - local OUTPUT_MODE="$5" # "facets-only" to write only facets JSON to OUTPUT_PATH - local OUTPUT_PATH="$6" # path to write facets JSON when in facets-only mode + local OUTPUT_MODE="$5" # "facets-only" to write only facets JSON to OUTPUT_PATH + local OUTPUT_PATH="$6" # path to write facets JSON when in facets-only mode + local FACETS_DIR="$7" # path for intermediate files, provided by the caller # logging for debug purposes echo "" @@ -884,36 +885,21 @@ function saveDiamondFacets() { echoDebug "USE_MUTABLE_DIAMOND=$USE_MUTABLE_DIAMOND" echoDebug "FACETS=$FACETS" echoDebug "DIAMOND_CONTRACT_NAME=$DIAMOND_CONTRACT_NAME" + echoDebug "FACETS_DIR=$FACETS_DIR" - # get file suffix based on value in variable ENVIRONMENT - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - - # define path for json file based on which diamond was used - if [[ "$DIAMOND_CONTRACT_NAME" == "LiFiDEXAggregatorDiamond" ]]; then - DIAMOND_FILE="./deployments/${NETWORK}.diamond.lda.${FILE_SUFFIX}json" - DIAMOND_NAME="LiFiDEXAggregatorDiamond" - else - if [[ "$USE_MUTABLE_DIAMOND" == "true" ]]; then - DIAMOND_FILE="./deployments/${NETWORK}.diamond.${FILE_SUFFIX}json" - DIAMOND_NAME="LiFiDiamond" - else - # Regular immutable diamond - DIAMOND_FILE="./deployments/${NETWORK}.diamond.immutable.${FILE_SUFFIX}json" - DIAMOND_NAME="LiFiDiamondImmutable" - fi - fi # create an iterable FACETS array - # Remove brackets from FACETS string and split into array FACETS_ADJ="${4#\[}" FACETS_ADJ="${FACETS_ADJ%\]}" IFS=',' read -ra FACET_ADDRESSES <<<"$FACETS_ADJ" - # Set up a temp directory to collect facet entries (avoid concurrent writes) - local TEMP_DIR - TEMP_DIR=$(mktemp -d) - trap 'rm -rf "$TEMP_DIR" 2>/dev/null' EXIT - local FACETS_DIR="$TEMP_DIR/facets" - mkdir -p "$FACETS_DIR" + # ensure the directory for intermediate files exists + if [[ -n "$FACETS_DIR" ]]; then + mkdir -p "$FACETS_DIR" + else + # fallback for safety, though the caller should always provide it now + FACETS_DIR=$(mktemp -d) + trap 'rm -rf "$FACETS_DIR" 2>/dev/null' EXIT + fi # determine concurrency (fallback to 10 if not set) local CONCURRENCY=${MAX_CONCURRENT_JOBS:-10} @@ -941,6 +927,7 @@ function saveDiamondFacets() { NAME=$(getContractNameFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") JSON_ENTRY="{\"$FACET_ADDRESS\": {\"Name\": \"$NAME\", \"Version\": \"\"}}" fi + # This now writes to the directory passed in by the parent function echo "$JSON_ENTRY" >"$FACETS_DIR/${FACET_ADDRESS}.json" ) & done @@ -1085,6 +1072,7 @@ function saveDiamondPeriphery() { # optional: output control for parallel orchestration local OUTPUT_MODE="$4" # "periphery-only" to write only periphery JSON to OUTPUT_PATH local OUTPUT_PATH="$5" # path to write periphery JSON when in periphery-only mode + local PERIPHERY_DIR="$6" # path for intermediate files, provided by the caller # get file suffix based on value in variable ENVIRONMENT local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") @@ -1113,20 +1101,19 @@ function saveDiamondPeriphery() { echoDebug "in function saveDiamondPeriphery" echoDebug "NETWORK=$NETWORK" echoDebug "ENVIRONMENT=$ENVIRONMENT" - echoDebug "USE_MUTABLE_DIAMOND=$USE_MUTABLE_DIAMOND" - echoDebug "FILE_SUFFIX=$FILE_SUFFIX" - echoDebug "RPC_URL=$RPC_URL" echoDebug "DIAMOND_ADDRESS=$DIAMOND_ADDRESS" - echoDebug "DIAMOND_FILE=$DIAMOND_FILE" + echoDebug "PERIPHERY_DIR=$PERIPHERY_DIR" # +++ ADD THIS for debugging # get a list of all periphery contracts PERIPHERY_CONTRACTS=$(getContractNamesInFolder "src/Periphery/") - # prepare temp dir to collect per-contract JSON snippets - local TEMP_DIR - TEMP_DIR=$(mktemp -d) - local PERIPHERY_DIR="$TEMP_DIR/periphery" - mkdir -p "$PERIPHERY_DIR" + if [[ -n "$PERIPHERY_DIR" ]]; then + mkdir -p "$PERIPHERY_DIR" + else + PERIPHERY_DIR=$(mktemp -d) + # use a trap here as a fallback since it's self-contained if no dir is passed + trap 'rm -rf "$PERIPHERY_DIR" 2>/dev/null' EXIT + fi # determine concurrency (fallback to 10 if not set) local CONCURRENCY=${MAX_CONCURRENT_JOBS:-10} @@ -1182,9 +1169,6 @@ function saveDiamondPeriphery() { ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") printf %s "$result" >"$DIAMOND_FILE" fi - - # cleanup - rm -rf "$TEMP_DIR" } function saveContract() { @@ -4390,7 +4374,7 @@ function updateDiamondLogForNetwork() { if [[ $? -ne 0 || -z "$DIAMOND_ADDRESS" ]]; then warning "[$NETWORK] Failed to get $DIAMOND_CONTRACT_NAME address on $NETWORK in $ENVIRONMENT environment - contract may not be deployed. Skipping." - return 0 # Return success but skip processing + return 0 # Return success but skip processing fi # get list of facets @@ -4435,10 +4419,17 @@ function updateDiamondLogForNetwork() { USE_MUTABLE_DIAMOND="false" fi + # create one isolated temp directory for this entire parallel operation local TEMP_DIR TEMP_DIR=$(mktemp -d -t "diamond_${DIAMOND_CONTRACT_NAME}_${NETWORK}_${ENVIRONMENT}_XXXXXX") + # ensure this directory is cleaned up when this function exits + trap 'rm -rf "$TEMP_DIR" 2>/dev/null' RETURN + local FACETS_TMP="$TEMP_DIR/facets_${DIAMOND_CONTRACT_NAME}.json" local PERIPHERY_TMP="$TEMP_DIR/periphery_${DIAMOND_CONTRACT_NAME}.json" + # define paths for the intermediate files within the isolated directory + local FACETS_SUBDIR="$TEMP_DIR/facets-intermediate" + local PERIPHERY_SUBDIR="$TEMP_DIR/periphery-intermediate" # export DIAMOND_CONTRACT_NAME for the functions to use export DIAMOND_CONTRACT_NAME="$DIAMOND_CONTRACT_NAME" @@ -4448,7 +4439,7 @@ function updateDiamondLogForNetwork() { # only process periphery for LiFiDiamond and LiFiDiamondImmutable, skip for LiFiDEXAggregatorDiamond if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond" ]]; then - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "periphery-only" "$PERIPHERY_TMP" & + saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "periphery-only" "$PERIPHERY_TMP" "$PERIPHERY_SUBDIR" & pids+=($!) else # For LiFiDEXAggregatorDiamond, create empty periphery file @@ -4460,12 +4451,16 @@ function updateDiamondLogForNetwork() { warning "[$NETWORK] no facets found in $DIAMOND_CONTRACT_NAME $DIAMOND_ADDRESS" echo '{}' >"$FACETS_TMP" else - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" & + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" "$FACETS_SUBDIR" & pids+=($!) fi # wait for both background jobs to complete - if [[ -n "$pids" ]]; then wait "$pids"; fi + if [[ -n "${pids[*]}" ]]; then + for pid in "${pids[@]}"; do + wait "$pid" + done + fi # validate temp outputs exist if [[ ! -s "$FACETS_TMP" ]]; then echo '{}' >"$FACETS_TMP"; fi @@ -4485,9 +4480,7 @@ function updateDiamondLogForNetwork() { ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") printf %s "$MERGED" >"$DIAMOND_FILE" - # clean up - rm -rf "$TEMP_DIR" - + # the trap automatically cleans up the TEMP_DIR success "[$NETWORK] updated diamond log for $DIAMOND_CONTRACT_NAME" } diff --git a/script/tasks/diamondUpdateFacet.sh b/script/tasks/diamondUpdateFacet.sh index f96f2eea3..6f8d3fe61 100755 --- a/script/tasks/diamondUpdateFacet.sh +++ b/script/tasks/diamondUpdateFacet.sh @@ -216,7 +216,7 @@ diamondUpdateFacet() { # save facet addresses (only if deploying to staging, otherwise we update the logs after the diamondCut tx gets signed in the SAFE) if [[ "$ENVIRONMENT" != "production" ]]; then # Using default behavior: update diamond file (not facets-only mode) - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" "" fi echo "[info] $SCRIPT successfully executed on network $NETWORK in $ENVIRONMENT environment" From eb4511c97638879cdca3e7cdb30b4e3132af0952 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 21:59:59 +0200 Subject: [PATCH 195/220] refactor updateDiamondLogForNetwork to return a special exit code for skipped processing --- script/helperFunctions.sh | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 7485718ed..6e27011f5 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4374,7 +4374,7 @@ function updateDiamondLogForNetwork() { if [[ $? -ne 0 || -z "$DIAMOND_ADDRESS" ]]; then warning "[$NETWORK] Failed to get $DIAMOND_CONTRACT_NAME address on $NETWORK in $ENVIRONMENT environment - contract may not be deployed. Skipping." - return 0 # Return success but skip processing + return 2 # Return a special exit code (2) for "skipped" instead of 0 (success) fi # get list of facets @@ -4544,22 +4544,29 @@ function updateDiamondLogs() { # Wait for all background jobs to complete and capture exit codes echo "Waiting for all diamond log updates to complete..." + echo "Waiting for all diamond log updates to complete..." local failed_jobs=() local job_count=${#pids[@]} for i in "${!pids[@]}"; do local pid="${pids[$i]}" local info="${job_info[$i]}" + local exit_code=0 # Default to success + + # wait for this specific job and then capture its exit code from the '$?' variable + wait "$pid" + exit_code=$? - # Wait for this specific job and capture its exit code - if wait "$pid"; then + if [[ $exit_code -eq 0 ]]; then echo "[$info] Completed successfully" + elif [[ $exit_code -eq 2 ]]; then + # Handle our custom "skipped" code + echo "[$info] Skipped (contract not found in deployments)" else - echo "[$info] Failed with exit code $?" + echo "[$info] Failed with exit code $exit_code" failed_jobs+=("$info") fi done - # Check if any jobs failed if [ ${#failed_jobs[@]} -gt 0 ]; then error "Some diamond log updates failed: ${failed_jobs[*]}" From e724ff3f48d372fc4b5831f4b5dbe8d6aa735faf Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 22:26:01 +0200 Subject: [PATCH 196/220] changes --- deployments/_deployments_log_file.json | 6 +++--- deployments/arbitrum.staging.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index dee6f57c8..d06738efe 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -40231,11 +40231,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "ADDRESS": "0xAF8479A8edb34D606A0Ef40bD823dC1804BC4DA9", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:26:30", + "TIMESTAMP": "2025-09-09 22:25:23", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345116", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 59dd4b61e..6e579f3f8 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -65,7 +65,7 @@ "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", "KatanaV3Facet": "0xb11208638f71585C02b23c2E9cfE4BB791a0d952", "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", - "SyncSwapV2Facet": "0x283831120F19fd293206AB6FaEF1C45Cf83487D0", + "SyncSwapV2Facet": "0xAF8479A8edb34D606A0Ef40bD823dC1804BC4DA9", "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", From 215e9b886b79e71abb2d8f39c483fddbe11584f6 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 23:06:10 +0200 Subject: [PATCH 197/220] changes --- deployments/arbitrum.diamond.json | 179 ------------------ deployments/arbitrum.diamond.lda.staging.json | 5 +- deployments/bsc.diamond.lda.staging.json | 3 +- deployments/optimism.diamond.lda.staging.json | 3 +- script/helperFunctions.sh | 25 ++- 5 files changed, 21 insertions(+), 194 deletions(-) delete mode 100644 deployments/arbitrum.diamond.json diff --git a/deployments/arbitrum.diamond.json b/deployments/arbitrum.diamond.json deleted file mode 100644 index a8177f343..000000000 --- a/deployments/arbitrum.diamond.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "LiFiDiamond": { - "Facets": { - "0xf7993A8df974AD022647E63402d6315137c58ABf": { - "Name": "", - "Version": "" - }, - "0x7D507e6E89C52aE610b8D0151c8cb24c24e43bdb": { - "Name": "HopFacetOptimized", - "Version": "2.0.0" - }, - "0x6faA6906b9e4A59020e673910105567e809789E0": { - "Name": "OwnershipFacet", - "Version": "1.0.0" - }, - "0xE397c4883ec89ed4Fc9D258F00C689708b2799c9": { - "Name": "AcrossFacetPacked", - "Version": "1.0.0" - }, - "0x22B31a1a81d5e594315c866616db793E799556c5": { - "Name": "DexManagerFacet", - "Version": "1.0.1" - }, - "0x77A13abB679A0DAFB4435D1Fa4cCC95D1ab51cfc": { - "Name": "AccessManagerFacet", - "Version": "1.0.0" - }, - "0x69cb467EfD8044ac9eDB88F363309ab1cbFA0A15": { - "Name": "PeripheryRegistryFacet", - "Version": "1.0.0" - }, - "0x66861f292099cAF644F4A8b6091De49BEC5E8a15": { - "Name": "LIFuelFacet", - "Version": "1.0.1" - }, - "0xE0c5e721b40D54f2aA09418B1237db9d88220C73": { - "Name": "GenericSwapFacet", - "Version": "1.0.0" - }, - "0xBeE13d99dD633fEAa2a0935f00CbC859F8305FA7": { - "Name": "AcrossFacet", - "Version": "2.0.0" - }, - "0xAE5325C02b87ff1D3752a1d3100Ec82537605B72": { - "Name": "AllBridgeFacet", - "Version": "2.1.0" - }, - "0x3F95b05a77FDC6D82162D86A72b156b55030627f": { - "Name": "AmarokFacet", - "Version": "3.0.0" - }, - "0xE7Bf43C55551B1036e796E7Fd3b125d1F9903e2E": { - "Name": "CBridgeFacetPacked", - "Version": "1.0.3" - }, - "0x3b70Eb33948Fbfdc3f2F2491b96DFB1Aa18054E0": { - "Name": "CBridgeFacet", - "Version": "1.0.0" - }, - "0x6731C946747bA54c78e7a65d416Cde39E478ECeb": { - "Name": "CelerCircleBridgeFacet", - "Version": "1.0.1" - }, - "0xFa93141130a11FdaB7C6c800dfB93a5d19Da6aA4": { - "Name": "ChainflipFacet", - "Version": "1.0.0" - }, - "0x6eF81a18E1E432C289DC0d1a670B78E8bbF9AA35": { - "Name": "HopFacetPacked", - "Version": "1.0.6" - }, - "0xd84d9A8Bf830496C4DEc917bC27D22E09E01cB8A": { - "Name": "HopFacet", - "Version": "2.0.0" - }, - "0xF2c63815eBD0c4E048eF216C77E2c80aa4ecD59c": { - "Name": "HyphenFacet", - "Version": "1.0.0" - }, - "0x02063A0d7a222c16D5b63213262596B83b07150c": { - "Name": "MultichainFacet", - "Version": "1.0.1" - }, - "0x7D1940fDfF0B37c137B105ce7967B3B86DB42648": { - "Name": "StargateFacet", - "Version": "2.2.0" - }, - "0x52a29e1f32DEd47B6FfF036e95667125921faE50": { - "Name": "WormholeFacet", - "Version": "1.0.0" - }, - "0xc21a00a346d5b29955449Ca912343a3bB4c5552f": { - "Name": "DiamondLoupeFacet", - "Version": "1.0.0" - }, - "0x175E7799DA0CD40E641352EaB90D8e39e02a4Ca9": { - "Name": "StandardizedCallFacet", - "Version": "1.1.0" - }, - "0x7A5c119ec5dDbF9631cf40f6e5DB28f31d4332a0": { - "Name": "CalldataVerificationFacet", - "Version": "1.1.1" - }, - "0x5052fc5c7486162deDf7458E1f7c6ABaFbcd6895": { - "Name": "AcrossFacetV3", - "Version": "1.1.0" - }, - "0x711e80A9c1eB906d9Ae9d37E5432E6E7aCeEdA0B": { - "Name": "WithdrawFacet", - "Version": "1.0.0" - }, - "0xF18A285f4e6f720Eb9b4e05df71f88b9552E6ADB": { - "Name": "AmarokFacetPacked", - "Version": "1.0.0" - }, - "0xe12b2488c71432F9a116E9ac244D3Ef4c2386d3a": { - "Name": "SymbiosisFacet", - "Version": "1.0.0" - }, - "0x5C2C3F56e33F45389aa4e1DA4D3a807A532a910c": { - "Name": "SquidFacet", - "Version": "1.0.0" - }, - "0xBd5cf5C53A14a69FFf27Fe8b23e09bF76bA4De58": { - "Name": "MayanFacet", - "Version": "1.2.0" - }, - "0x31a9b1835864706Af10103b31Ea2b79bdb995F5F": { - "Name": "GenericSwapFacetV3", - "Version": "1.0.0" - }, - "0x6e378C84e657C57b2a8d183CFf30ee5CC8989b61": { - "Name": "StargateFacetV2", - "Version": "1.0.1" - }, - "0x21a786957c69424A4353Afe743242Bd9Db3cC07b": { - "Name": "AcrossFacetPackedV3", - "Version": "1.2.0" - }, - "0x18C85B940c29ECC3c210Ea40a5B6d91F5aeE2803": { - "Name": "DeBridgeDlnFacet", - "Version": "1.0.0" - }, - "0x6F2baA7cd5F156CA1B132F7FF11E0fa2aD775F61": { - "Name": "EmergencyPauseFacet", - "Version": "1.0.0" - }, - "0x65d6B9A368Be49bcA4964B66e54F828cAB64B8F9": { - "Name": "GasZipFacet", - "Version": "2.0.4" - }, - "0x424BDbbaEda89732443fb1B737b6Dc194a6Ddbd5": { - "Name": "RelayFacet", - "Version": "1.0.0" - }, - "0xdC49bca8314e7cd7C90EDb8652375a0b15EFe611": { - "Name": "GlacisFacet", - "Version": "1.0.0" - }, - "0x756Be848a6636D618B94f3196CBD89645eBa71c2": { - "Name": "PioneerFacet", - "Version": "1.0.0" - } - }, - "Periphery": { - "ERC20Proxy": "0x5741A7FfE7c39Ca175546a54985fA79211290b51", - "Executor": "0x2dfaDAB8266483beD9Fd9A292Ce56596a2D1378D", - "FeeCollector": "0xB0210dE78E28e2633Ca200609D9f528c13c26cD9", - "GasZipPeriphery": "0x7339d0d71DD79FE3Bc4A2650F7E025ffeac170FA", - "LidoWrapper": "", - "LiFiDEXAggregator": "0x8189AFcC5B73Dc90600FeE92e5267Aff1D192884", - "Permit2Proxy": "0x89c6340B1a1f4b25D36cd8B063D49045caF3f818", - "ReceiverAcrossV3": "0xca6e6B692F568055adA0bF72A06D1EBbC938Fb23", - "ReceiverChainflip": "0x37F584941242C8eada60bd6D8480cC0B6E36a0Cf", - "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", - "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d" - } - } -} \ No newline at end of file diff --git a/deployments/arbitrum.diamond.lda.staging.json b/deployments/arbitrum.diamond.lda.staging.json index a67ea3d81..a709b0f83 100644 --- a/deployments/arbitrum.diamond.lda.staging.json +++ b/deployments/arbitrum.diamond.lda.staging.json @@ -37,7 +37,7 @@ "Name": "NativeWrapperFacet", "Version": "1.0.0" }, - "0x283831120F19fd293206AB6FaEF1C45Cf83487D0": { + "0xAF8479A8edb34D606A0Ef40bD823dC1804BC4DA9": { "Name": "SyncSwapV2Facet", "Version": "1.0.0" }, @@ -53,7 +53,6 @@ "Name": "VelodromeV2Facet", "Version": "1.0.0" } - }, - "Periphery": {} + } } } \ No newline at end of file diff --git a/deployments/bsc.diamond.lda.staging.json b/deployments/bsc.diamond.lda.staging.json index 3f3f05ef5..3b16a040f 100644 --- a/deployments/bsc.diamond.lda.staging.json +++ b/deployments/bsc.diamond.lda.staging.json @@ -53,7 +53,6 @@ "Name": "VelodromeV2Facet", "Version": "1.0.0" } - }, - "Periphery": {} + } } } \ No newline at end of file diff --git a/deployments/optimism.diamond.lda.staging.json b/deployments/optimism.diamond.lda.staging.json index 42671d06e..fb16d2ca1 100644 --- a/deployments/optimism.diamond.lda.staging.json +++ b/deployments/optimism.diamond.lda.staging.json @@ -53,7 +53,6 @@ "Name": "VelodromeV2Facet", "Version": "1.0.0" } - }, - "Periphery": {} + } } } \ No newline at end of file diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 6e27011f5..0eac75bfd 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -924,6 +924,8 @@ function saveDiamondFacets() { JSON_ENTRY=$(findContractInMasterLogByAddress "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") if [[ $? -ne 0 || -z "$JSON_ENTRY" ]]; then warning "could not find any information about this facet address ($FACET_ADDRESS) in master log file while creating $DIAMOND_FILE (ENVIRONMENT=$ENVIRONMENT), " + # try to find name of contract from network-specific deployments file + # load JSON FILE that contains deployment addresses NAME=$(getContractNameFromDeploymentLogs "$NETWORK" "$ENVIRONMENT" "$FACET_ADDRESS") JSON_ENTRY="{\"$FACET_ADDRESS\": {\"Name\": \"$NAME\", \"Version\": \"\"}}" fi @@ -961,14 +963,16 @@ function saveDiamondFacets() { fi # write merged facets to diamond file in a single atomic update - result=$(jq -r --arg diamond_name "$DIAMOND_NAME" --argjson facets_obj "$FACETS_JSON" ' + result=$(jq -r --arg diamond_name "$DIAMOND_CONTRACT_NAME" --argjson facets_obj "$FACETS_JSON" ' .[$diamond_name] = (.[$diamond_name] // {}) | .[$diamond_name].Facets = ((.[$diamond_name].Facets // {}) + $facets_obj) ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") printf %s "$result" >"$DIAMOND_FILE" - # add information about registered periphery contracts - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" + # add information about registered periphery contracts (only for regular diamonds, not LDA) + if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond" ]]; then + saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" + fi fi } @@ -1101,8 +1105,11 @@ function saveDiamondPeriphery() { echoDebug "in function saveDiamondPeriphery" echoDebug "NETWORK=$NETWORK" echoDebug "ENVIRONMENT=$ENVIRONMENT" + echoDebug "USE_MUTABLE_DIAMOND=$USE_MUTABLE_DIAMOND" + echoDebug "FILE_SUFFIX=$FILE_SUFFIX" + echoDebug "RPC_URL=$RPC_URL" echoDebug "DIAMOND_ADDRESS=$DIAMOND_ADDRESS" - echoDebug "PERIPHERY_DIR=$PERIPHERY_DIR" # +++ ADD THIS for debugging + echoDebug "DIAMOND_FILE=$DIAMOND_FILE" # get a list of all periphery contracts PERIPHERY_CONTRACTS=$(getContractNamesInFolder "src/Periphery/") @@ -4441,9 +4448,6 @@ function updateDiamondLogForNetwork() { if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond" ]]; then saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "periphery-only" "$PERIPHERY_TMP" "$PERIPHERY_SUBDIR" & pids+=($!) - else - # For LiFiDEXAggregatorDiamond, create empty periphery file - echo '{}' >"$PERIPHERY_TMP" fi # Start facets resolution (for all diamond types) @@ -4476,7 +4480,12 @@ function updateDiamondLogForNetwork() { MERGED=$(jq -r --arg diamond_name "$DIAMOND_CONTRACT_NAME" --slurpfile facets "$FACETS_TMP" --slurpfile periphery "$PERIPHERY_TMP" ' .[$diamond_name] = (.[$diamond_name] // {}) | .[$diamond_name].Facets = $facets[0] | - .[$diamond_name].Periphery = $periphery[0] + # Conditionally add the Periphery key only if it is not the LDA diamond + if $diamond_name != "LiFiDEXAggregatorDiamond" then + .[$diamond_name].Periphery = $periphery[0] + else + . # Otherwise, pass the object through without adding the Periphery key + end ' "$DIAMOND_FILE" || cat "$DIAMOND_FILE") printf %s "$MERGED" >"$DIAMOND_FILE" From 66b539161e852c8ed84e492a8434124745133609 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 23:51:57 +0200 Subject: [PATCH 198/220] update --- script/helperFunctions.sh | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 15104c8b2..5ce0b4b82 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4443,29 +4443,6 @@ function updateDiamondLogForNetwork() { warning "[$NETWORK] Failed to get facets from diamond $DIAMOND_ADDRESS after $MAX_ATTEMPTS_PER_SCRIPT_EXECUTION attempts" fi -<<<<<<< HEAD - # Determine if this is an LDA diamond to use the correct save function - if [[ "$DIAMOND_TYPE" == "LiFiDEXAggregatorDiamond" ]]; then - # For LDA diamonds, use LDA-specific save functions (no peripheries) - if [[ -z $KNOWN_FACET_ADDRESSES ]]; then - warning "[$NETWORK] no facets found in LDA diamond $DIAMOND_ADDRESS" - # LDA diamonds don't have peripheries, so just create empty diamond file - local FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") - local DIAMOND_FILE="./deployments/${NETWORK}.lda.diamond.${FILE_SUFFIX}json" - echo "{\"$DIAMOND_TYPE\": {\"Facets\": {}}}" >"$DIAMOND_FILE" - else - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" "LiFiDEXAggregatorDiamond" - fi - else - # For regular diamonds, use existing save functions - if [[ -z $KNOWN_FACET_ADDRESSES ]]; then - warning "[$NETWORK] no facets found in diamond $DIAMOND_ADDRESS" - saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "true" - else - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" - # saveDiamondPeriphery is executed as part of saveDiamondFacets - fi -======= # prepare for parallel facet/periphery processing and final merge local FILE_SUFFIX FILE_SUFFIX=$(getFileSuffix "$ENVIRONMENT") @@ -4488,7 +4465,6 @@ function updateDiamondLogForNetwork() { else saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "true" "$KNOWN_FACET_ADDRESSES" "facets-only" "$FACETS_TMP" & PID_FACETS=$! ->>>>>>> lf-13997-lda-2.0-addressed-comments fi # wait for both background jobs to complete From ecff84c900e871d0c6e6a723cc73c2805c3ffae9 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 23:52:31 +0200 Subject: [PATCH 199/220] space --- script/helperFunctions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 0eac75bfd..7a5d8eb6e 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4445,7 +4445,7 @@ function updateDiamondLogForNetwork() { local pids=() # only process periphery for LiFiDiamond and LiFiDiamondImmutable, skip for LiFiDEXAggregatorDiamond - if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond" ]]; then + if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond " ]]; then saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "periphery-only" "$PERIPHERY_TMP" "$PERIPHERY_SUBDIR" & pids+=($!) fi From 891a76a2b71004df785f06cfccccf624e6795591 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 23:53:24 +0200 Subject: [PATCH 200/220] removed space --- script/helperFunctions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 7a5d8eb6e..0eac75bfd 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -4445,7 +4445,7 @@ function updateDiamondLogForNetwork() { local pids=() # only process periphery for LiFiDiamond and LiFiDiamondImmutable, skip for LiFiDEXAggregatorDiamond - if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond " ]]; then + if [[ "$DIAMOND_CONTRACT_NAME" != "LiFiDEXAggregatorDiamond" ]]; then saveDiamondPeriphery "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "periphery-only" "$PERIPHERY_TMP" "$PERIPHERY_SUBDIR" & pids+=($!) fi From ea25ccc946ad800ad74e6f0529eacbf2193c9db4 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 23:55:46 +0200 Subject: [PATCH 201/220] update --- script/tasks/diamondUpdateFacet.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/script/tasks/diamondUpdateFacet.sh b/script/tasks/diamondUpdateFacet.sh index 097cab9b7..f96f2eea3 100755 --- a/script/tasks/diamondUpdateFacet.sh +++ b/script/tasks/diamondUpdateFacet.sh @@ -215,12 +215,8 @@ diamondUpdateFacet() { # save facet addresses (only if deploying to staging, otherwise we update the logs after the diamondCut tx gets signed in the SAFE) if [[ "$ENVIRONMENT" != "production" ]]; then -<<<<<<< HEAD - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "$DIAMOND_CONTRACT_NAME" -======= # Using default behavior: update diamond file (not facets-only mode) saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" ->>>>>>> lf-13997-lda-2.0-addressed-comments fi echo "[info] $SCRIPT successfully executed on network $NETWORK in $ENVIRONMENT environment" From ab2dfadd4570820df27f325dca73507fe98ed537 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 23:56:31 +0200 Subject: [PATCH 202/220] removed quotes --- script/tasks/diamondUpdateFacet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/tasks/diamondUpdateFacet.sh b/script/tasks/diamondUpdateFacet.sh index 6f8d3fe61..f96f2eea3 100755 --- a/script/tasks/diamondUpdateFacet.sh +++ b/script/tasks/diamondUpdateFacet.sh @@ -216,7 +216,7 @@ diamondUpdateFacet() { # save facet addresses (only if deploying to staging, otherwise we update the logs after the diamondCut tx gets signed in the SAFE) if [[ "$ENVIRONMENT" != "production" ]]; then # Using default behavior: update diamond file (not facets-only mode) - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" "" + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" fi echo "[info] $SCRIPT successfully executed on network $NETWORK in $ENVIRONMENT environment" From 480800e8e4468d274699649fbec5a1e9ee8eecd1 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 9 Sep 2025 23:56:52 +0200 Subject: [PATCH 203/220] added quotes --- script/tasks/diamondUpdateFacet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/tasks/diamondUpdateFacet.sh b/script/tasks/diamondUpdateFacet.sh index f96f2eea3..6f8d3fe61 100755 --- a/script/tasks/diamondUpdateFacet.sh +++ b/script/tasks/diamondUpdateFacet.sh @@ -216,7 +216,7 @@ diamondUpdateFacet() { # save facet addresses (only if deploying to staging, otherwise we update the logs after the diamondCut tx gets signed in the SAFE) if [[ "$ENVIRONMENT" != "production" ]]; then # Using default behavior: update diamond file (not facets-only mode) - saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" + saveDiamondFacets "$NETWORK" "$ENVIRONMENT" "$USE_MUTABLE_DIAMOND" "$FACETS" "" "" "" fi echo "[info] $SCRIPT successfully executed on network $NETWORK in $ENVIRONMENT environment" From 5cefa01f3383f0f086a71b715b0f34f0d976e317 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 10 Sep 2025 00:05:15 +0200 Subject: [PATCH 204/220] Update GitHub Actions workflow to run version control checks only for non-draft PRs targeting the main branch --- .github/workflows/versionControlAndAuditCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/versionControlAndAuditCheck.yml b/.github/workflows/versionControlAndAuditCheck.yml index 967f97238..ed66ccc31 100644 --- a/.github/workflows/versionControlAndAuditCheck.yml +++ b/.github/workflows/versionControlAndAuditCheck.yml @@ -36,7 +36,7 @@ permissions: jobs: version-control: - if: ${{ github.event.pull_request.draft == false }} + if: ${{ github.event.pull_request.draft == false && github.event.pull_request.base.ref == 'main' }} runs-on: ubuntu-latest concurrency: group: sc-core-dev-approval-${{ github.event.pull_request.number }} From a775644ea403c66e1b1f2e40dffabf05bf171dce Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 12 Sep 2025 11:05:10 +0200 Subject: [PATCH 205/220] update --- script/helperFunctions.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/script/helperFunctions.sh b/script/helperFunctions.sh index 0eac75bfd..e632fb1c5 100755 --- a/script/helperFunctions.sh +++ b/script/helperFunctions.sh @@ -896,7 +896,8 @@ function saveDiamondFacets() { if [[ -n "$FACETS_DIR" ]]; then mkdir -p "$FACETS_DIR" else - # fallback for safety, though the caller should always provide it now + # fallback for safety, for single-threaded operation where FACETS_DIR is not provided + # for parallel operation, the caller should always provide it FACETS_DIR=$(mktemp -d) trap 'rm -rf "$FACETS_DIR" 2>/dev/null' EXIT fi @@ -4392,7 +4393,6 @@ function updateDiamondLogForNetwork() { echo "[$NETWORK] Trying to get facets for diamond $DIAMOND_ADDRESS now - attempt $((attempts + 1))" # try to execute call local KNOWN_FACET_ADDRESSES=$(cast call "$DIAMOND_ADDRESS" "facetAddresses() returns (address[])" --rpc-url "$RPC_URL") 2>/dev/null - echoDebug "KNOWN_FACET_ADDRESSES=$KNOWN_FACET_ADDRESSES" # check the return code the last call if [ $? -eq 0 ]; then break # exit the loop if the operation was successful @@ -4499,7 +4499,7 @@ function updateDiamondLogs() { local NETWORK=$2 # Define all diamond contract names to process - local DIAMOND_CONTRACT_NAMES=("LiFiDiamond" "LiFiDiamondImmutable" "LiFiDEXAggregatorDiamond") + local DIAMOND_CONTRACT_NAMES=("LiFiDiamond" "LiFiDEXAggregatorDiamond") # if no network was passed to this function, update all networks if [[ -z $NETWORK ]]; then @@ -4534,7 +4534,6 @@ function updateDiamondLogs() { for ENVIRONMENT in "${ENVIRONMENTS[@]}"; do echo " -----------------------" echo " current ENVIRONMENT: $ENVIRONMENT" - echo " current ENVIRONMENT: $ENVIRONMENT" # Process all diamond contract names for this network/environment in parallel for DIAMOND_CONTRACT_NAME in "${DIAMOND_CONTRACT_NAMES[@]}"; do @@ -4553,7 +4552,6 @@ function updateDiamondLogs() { # Wait for all background jobs to complete and capture exit codes echo "Waiting for all diamond log updates to complete..." - echo "Waiting for all diamond log updates to complete..." local failed_jobs=() local job_count=${#pids[@]} From 2004678047dc8e9001572e0da9eb6a37d5bc1e1a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 12 Sep 2025 15:17:50 +0200 Subject: [PATCH 206/220] new staging arbitrum deployments --- deployments/_deployments_log_file.json | 60 +++++++++---------- deployments/arbitrum.diamond.lda.staging.json | 20 +++---- deployments/arbitrum.diamond.staging.json | 6 +- deployments/arbitrum.staging.json | 22 +++---- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index db613b91d..2f2feb95f 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39921,11 +39921,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", + "ADDRESS": "0x0d47366d1Fbe6BC60B9633589EbEFDC8A5403bcC", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-06 18:31:22", + "TIMESTAMP": "2025-09-12 15:10:45", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345125", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -39968,11 +39968,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", + "ADDRESS": "0x8162d12A6Db97C601F16BD474670DC8bF2456a41", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:27:13", + "TIMESTAMP": "2025-09-12 15:13:39", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40015,11 +40015,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x181a353054883D9DdE6864Ba074226E5b77cf511", + "ADDRESS": "0x29999754A40dc303C1aC0bEF058658DE6098fbB7", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:26:48", + "TIMESTAMP": "2025-09-12 15:13:18", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40062,11 +40062,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", + "ADDRESS": "0x32Dde41a556Ad153371FcA83e11723Bb7682C61d", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:26:09", + "TIMESTAMP": "2025-09-12 15:12:17", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40109,11 +40109,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41", + "ADDRESS": "0x62cb24d3D3847af24b82D087E82bebC3305d16c6", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:05:55", + "TIMESTAMP": "2025-09-12 15:11:27", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40203,11 +40203,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", + "ADDRESS": "0xC02fBdb4a97f14E73F456aE77a603F85C1a5fD67", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:02:26", + "TIMESTAMP": "2025-09-12 15:10:24", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40250,11 +40250,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", + "ADDRESS": "0x839b4B0aa0B68dE14814609C48067e4CBf559F67", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:05:34", + "TIMESTAMP": "2025-09-12 15:11:06", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40297,11 +40297,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xb11208638f71585C02b23c2E9cfE4BB791a0d952", + "ADDRESS": "0x475d3f49A51f8ff44d84257C46Ee389333B1BF42", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-09 00:57:01", + "TIMESTAMP": "2025-09-12 15:11:55", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345117", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40344,11 +40344,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xAF8479A8edb34D606A0Ef40bD823dC1804BC4DA9", + "ADDRESS": "0xB99d4DBb0A6C6cdeBb1Df59e243600A0795a1055", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-09 22:25:23", + "TIMESTAMP": "2025-09-12 15:12:46", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345116", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40391,11 +40391,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", + "ADDRESS": "0x4f5EbfA073440800722DbaD2A57805ec3065C93e", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-04 14:27:34", + "TIMESTAMP": "2025-09-12 15:14:03", "CONSTRUCTOR_ARGS": "0x", - "SALT": "", + "SALT": "22345115", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } diff --git a/deployments/arbitrum.diamond.lda.staging.json b/deployments/arbitrum.diamond.lda.staging.json index a709b0f83..dd40c090a 100644 --- a/deployments/arbitrum.diamond.lda.staging.json +++ b/deployments/arbitrum.diamond.lda.staging.json @@ -13,43 +13,43 @@ "Name": "OwnershipFacet", "Version": "1.0.0" }, - "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF": { + "0xC02fBdb4a97f14E73F456aE77a603F85C1a5fD67": { "Name": "AlgebraFacet", "Version": "1.0.0" }, - "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240": { + "0x0d47366d1Fbe6BC60B9633589EbEFDC8A5403bcC": { "Name": "CoreRouteFacet", "Version": "1.0.0" }, - "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d": { + "0x839b4B0aa0B68dE14814609C48067e4CBf559F67": { "Name": "CurveFacet", "Version": "1.0.0" }, - "0x7a7D7101a9A56882b34C0AC06328367f2356BB41": { + "0x62cb24d3D3847af24b82D087E82bebC3305d16c6": { "Name": "IzumiV3Facet", "Version": "1.0.0" }, - "0xb11208638f71585C02b23c2E9cfE4BB791a0d952": { + "0x475d3f49A51f8ff44d84257C46Ee389333B1BF42": { "Name": "KatanaV3Facet", "Version": "1.0.0" }, - "0x59A1514CD90a4c3662b3003450C8878448E6D6dD": { + "0x32Dde41a556Ad153371FcA83e11723Bb7682C61d": { "Name": "NativeWrapperFacet", "Version": "1.0.0" }, - "0xAF8479A8edb34D606A0Ef40bD823dC1804BC4DA9": { + "0xB99d4DBb0A6C6cdeBb1Df59e243600A0795a1055": { "Name": "SyncSwapV2Facet", "Version": "1.0.0" }, - "0x181a353054883D9DdE6864Ba074226E5b77cf511": { + "0x29999754A40dc303C1aC0bEF058658DE6098fbB7": { "Name": "UniV2StyleFacet", "Version": "1.0.0" }, - "0xbE76705E06154dAb3A95837166ef04d890bDeA15": { + "0x8162d12A6Db97C601F16BD474670DC8bF2456a41": { "Name": "UniV3StyleFacet", "Version": "1.0.0" }, - "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374": { + "0x4f5EbfA073440800722DbaD2A57805ec3065C93e": { "Name": "VelodromeV2Facet", "Version": "1.0.0" } diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index 4b1bd2509..30ec90024 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -125,9 +125,9 @@ "Name": "MayanFacet", "Version": "1.2.0" }, - "0x681a3409c35F12224c436D50Ce14F25f954B6Ea2": { - "Name": "RelayFacet", - "Version": "1.0.0" + "0xdaD5da5FB53EAe15f490Fb31F4573e56277b59CA": { + "Name": "", + "Version": "" }, "0x7C27b0FD92dbC5a1cA268255A649320E8C649e70": { "Name": "GasZipFacet", diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index cb04bb65c..746e1c0e7 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -62,15 +62,15 @@ "Patcher": "0x18069208cA7c2D55aa0073E047dD45587B26F6D4", "WhitelistManagerFacet": "0x603f0c31B37E5ca3eA75D5730CCfaBCFF6D17aa3", "RelayDepositoryFacet": "0x004E291b9244C811B0BE00cA2C179d54FAA5073D", - "LiFiDEXAggregatorDiamond": "0x897e12b5f25187648561A2e719e2ad22125626Ca", - "AlgebraFacet": "0xfeE467d825052aa8347ee7Ed2D90D2568DA96EbF", - "CoreRouteFacet": "0x505fF7139b9e4dd2a2C20C6BA9202b134046F240", - "CurveFacet": "0x02B8238bE17F05E9578D1F06876aE4BDc465dF7d", - "KatanaV3Facet": "0xb11208638f71585C02b23c2E9cfE4BB791a0d952", - "NativeWrapperFacet": "0x59A1514CD90a4c3662b3003450C8878448E6D6dD", - "SyncSwapV2Facet": "0xAF8479A8edb34D606A0Ef40bD823dC1804BC4DA9", - "UniV2StyleFacet": "0x181a353054883D9DdE6864Ba074226E5b77cf511", - "UniV3StyleFacet": "0xbE76705E06154dAb3A95837166ef04d890bDeA15", - "VelodromeV2Facet": "0xb516E77032DC56Ff21eeb3F18463b9a120E5C374", - "IzumiV3Facet": "0x7a7D7101a9A56882b34C0AC06328367f2356BB41" + "LiFiDEXAggregatorDiamond": "0xF5f53304F9E82bEaBe3eE99cc12a3AB9990557de", + "AlgebraFacet": "0xC02fBdb4a97f14E73F456aE77a603F85C1a5fD67", + "CoreRouteFacet": "0x0d47366d1Fbe6BC60B9633589EbEFDC8A5403bcC", + "CurveFacet": "0x839b4B0aa0B68dE14814609C48067e4CBf559F67", + "KatanaV3Facet": "0x475d3f49A51f8ff44d84257C46Ee389333B1BF42", + "NativeWrapperFacet": "0x32Dde41a556Ad153371FcA83e11723Bb7682C61d", + "SyncSwapV2Facet": "0xB99d4DBb0A6C6cdeBb1Df59e243600A0795a1055", + "UniV2StyleFacet": "0x29999754A40dc303C1aC0bEF058658DE6098fbB7", + "UniV3StyleFacet": "0x8162d12A6Db97C601F16BD474670DC8bF2456a41", + "VelodromeV2Facet": "0x4f5EbfA073440800722DbaD2A57805ec3065C93e", + "IzumiV3Facet": "0x62cb24d3D3847af24b82D087E82bebC3305d16c6" } \ No newline at end of file From 77c720e2532f0e0bc6cdbf2cd1770c141b391754 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Sun, 14 Sep 2025 21:42:16 +0200 Subject: [PATCH 207/220] Changes --- script/deploy/zksync/utils/DeployScriptBase.sol | 2 +- src/Periphery/LDA/Facets/KatanaV3Facet.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/script/deploy/zksync/utils/DeployScriptBase.sol b/script/deploy/zksync/utils/DeployScriptBase.sol index 2806f6ce2..415595ee1 100644 --- a/script/deploy/zksync/utils/DeployScriptBase.sol +++ b/script/deploy/zksync/utils/DeployScriptBase.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import { stdJson } from "forge-std/Script.sol"; import { LibAsset } from "lifi/Libraries/LibAsset.sol"; -import { ScriptBase } from "../../facets/utils/ScriptBase.sol"; +import { ScriptBase } from "./ScriptBase.sol"; interface IContractDeployer { function getNewAddressCreate2( diff --git a/src/Periphery/LDA/Facets/KatanaV3Facet.sol b/src/Periphery/LDA/Facets/KatanaV3Facet.sol index 4524bc118..62cf56fdd 100644 --- a/src/Periphery/LDA/Facets/KatanaV3Facet.sol +++ b/src/Periphery/LDA/Facets/KatanaV3Facet.sol @@ -23,7 +23,7 @@ contract KatanaV3Facet is BaseRouteConstants { // ==== External Functions ==== /// @notice Performs a swap through KatanaV3 pools /// @dev This function handles swaps through KatanaV3 pools. - /// @param swapData Encoded swap parameters [pool, direction, recipient] + /// @param swapData Encoded swap parameters [pool, direction, destinationAddress] /// @param from Where to take liquidity for swap /// @param tokenIn Input token /// @param amountIn Amount of tokenIn to take for swap @@ -37,9 +37,9 @@ contract KatanaV3Facet is BaseRouteConstants { address pool = stream.readAddress(); bool direction = stream.readUint8() == DIRECTION_TOKEN0_TO_TOKEN1; - address recipient = stream.readAddress(); + address destinationAddress = stream.readAddress(); - if (pool == address(0) || recipient == address(0)) + if (pool == address(0) || destinationAddress == address(0)) revert InvalidCallData(); // get router address from pool governance @@ -65,7 +65,7 @@ contract KatanaV3Facet is BaseRouteConstants { // set payerIsUser to false since we already transferred tokens to the router bytes[] memory inputs = new bytes[](1); inputs[0] = abi.encode( - recipient, // recipient + destinationAddress, // destinationAddress amountIn, // amountIn 0, // amountOutMin (0, as we handle slippage at higher level) abi.encodePacked(tokenIn, fee, tokenOut), // construct the path for V3 swap (tokenIn -> tokenOut with fee) From 33a05ef29e82ff2fef64eebb89f83e17360db2ea Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Thu, 25 Sep 2025 19:02:22 +0200 Subject: [PATCH 208/220] Mark external swap functions as `payable` to support multihop routes starting with native assets, ensuring `msg.value` is preserved through the `delegatecall` chain --- src/Periphery/LDA/Facets/AlgebraFacet.sol | 6 +++++- src/Periphery/LDA/Facets/IzumiV3Facet.sol | 6 +++++- src/Periphery/LDA/Facets/KatanaV3Facet.sol | 6 +++++- src/Periphery/LDA/Facets/NativeWrapperFacet.sol | 6 +++++- src/Periphery/LDA/Facets/SyncSwapV2Facet.sol | 6 +++++- src/Periphery/LDA/Facets/UniV2StyleFacet.sol | 6 +++++- src/Periphery/LDA/Facets/UniV3StyleFacet.sol | 6 +++++- src/Periphery/LDA/Facets/VelodromeV2Facet.sol | 6 +++++- 8 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/Periphery/LDA/Facets/AlgebraFacet.sol b/src/Periphery/LDA/Facets/AlgebraFacet.sol index 7e06b112a..6a1a46df0 100644 --- a/src/Periphery/LDA/Facets/AlgebraFacet.sol +++ b/src/Periphery/LDA/Facets/AlgebraFacet.sol @@ -30,6 +30,10 @@ contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticator { // ==== External Functions ==== /// @notice Executes a swap through an Algebra pool /// @dev Handles both regular swaps and fee-on-transfer token swaps + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, supportsFeeOnTransfer] /// @param from Token source address - if equals msg.sender, /// tokens will be pulled from the caller; otherwise assumes tokens are already at this contract @@ -40,7 +44,7 @@ contract AlgebraFacet is BaseRouteConstants, PoolCallbackAuthenticator { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); diff --git a/src/Periphery/LDA/Facets/IzumiV3Facet.sol b/src/Periphery/LDA/Facets/IzumiV3Facet.sol index 5c831e455..aa1bb4dcc 100644 --- a/src/Periphery/LDA/Facets/IzumiV3Facet.sol +++ b/src/Periphery/LDA/Facets/IzumiV3Facet.sol @@ -31,6 +31,10 @@ contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticator { // ==== External Functions ==== /// @notice Executes a swap through an iZiSwap V3 pool /// @dev Handles both X to Y and Y to X swaps with callback verification + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [pool, direction, destinationAddress] /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller /// @param tokenIn Input token address @@ -40,7 +44,7 @@ contract IzumiV3Facet is BaseRouteConstants, PoolCallbackAuthenticator { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); diff --git a/src/Periphery/LDA/Facets/KatanaV3Facet.sol b/src/Periphery/LDA/Facets/KatanaV3Facet.sol index 62cf56fdd..0f93d7591 100644 --- a/src/Periphery/LDA/Facets/KatanaV3Facet.sol +++ b/src/Periphery/LDA/Facets/KatanaV3Facet.sol @@ -23,6 +23,10 @@ contract KatanaV3Facet is BaseRouteConstants { // ==== External Functions ==== /// @notice Performs a swap through KatanaV3 pools /// @dev This function handles swaps through KatanaV3 pools. + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [pool, direction, destinationAddress] /// @param from Where to take liquidity for swap /// @param tokenIn Input token @@ -32,7 +36,7 @@ contract KatanaV3Facet is BaseRouteConstants { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); diff --git a/src/Periphery/LDA/Facets/NativeWrapperFacet.sol b/src/Periphery/LDA/Facets/NativeWrapperFacet.sol index 8dce37bdb..941bea569 100644 --- a/src/Periphery/LDA/Facets/NativeWrapperFacet.sol +++ b/src/Periphery/LDA/Facets/NativeWrapperFacet.sol @@ -17,6 +17,10 @@ contract NativeWrapperFacet is BaseRouteConstants { // ==== External Functions ==== /// @notice Unwraps WETH to native ETH /// @dev Handles unwrapping WETH and sending native ETH to recipient + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [destinationAddress] /// @param from Token source. If from == msg.sender, pull tokens via transferFrom. /// Otherwise, assume tokens are already held by this contract (e.g., address(this) or FUNDS_IN_RECEIVER). @@ -27,7 +31,7 @@ contract NativeWrapperFacet is BaseRouteConstants { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { address destinationAddress; assembly { // swapData layout: [length (32 bytes)][data...] diff --git a/src/Periphery/LDA/Facets/SyncSwapV2Facet.sol b/src/Periphery/LDA/Facets/SyncSwapV2Facet.sol index 8ced77642..dff275590 100644 --- a/src/Periphery/LDA/Facets/SyncSwapV2Facet.sol +++ b/src/Periphery/LDA/Facets/SyncSwapV2Facet.sol @@ -17,6 +17,10 @@ contract SyncSwapV2Facet { /// @notice Executes a swap through a SyncSwap V2 pool /// @dev Handles both V1 (vault-based) and V2 (direct) pool swaps + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [pool, destinationAddress, withdrawMode, isV1Pool, vault] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; /// otherwise assumes tokens are at receiver address @@ -27,7 +31,7 @@ contract SyncSwapV2Facet { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); diff --git a/src/Periphery/LDA/Facets/UniV2StyleFacet.sol b/src/Periphery/LDA/Facets/UniV2StyleFacet.sol index ec7f13e09..7ff162e7d 100644 --- a/src/Periphery/LDA/Facets/UniV2StyleFacet.sol +++ b/src/Periphery/LDA/Facets/UniV2StyleFacet.sol @@ -23,6 +23,10 @@ contract UniV2StyleFacet is BaseRouteConstants { // ==== External Functions ==== /// @notice Executes a UniswapV2-style swap /// @dev Handles token transfers and calculates output amounts based on pool reserves + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, fee] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; /// otherwise assumes tokens are at receiver address (FUNDS_IN_RECEIVER) @@ -33,7 +37,7 @@ contract UniV2StyleFacet is BaseRouteConstants { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); diff --git a/src/Periphery/LDA/Facets/UniV3StyleFacet.sol b/src/Periphery/LDA/Facets/UniV3StyleFacet.sol index 0107bd401..24a2724b9 100644 --- a/src/Periphery/LDA/Facets/UniV3StyleFacet.sol +++ b/src/Periphery/LDA/Facets/UniV3StyleFacet.sol @@ -29,6 +29,10 @@ contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticator { // ==== External Functions ==== /// @notice Executes a swap through a UniV3-style pool /// @dev Handles token transfers and manages callback verification + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [pool, direction, destinationAddress] /// @param from Token source address - if equals msg.sender, tokens will be pulled from the caller /// @param tokenIn Input token address @@ -38,7 +42,7 @@ contract UniV3StyleFacet is BaseRouteConstants, PoolCallbackAuthenticator { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); diff --git a/src/Periphery/LDA/Facets/VelodromeV2Facet.sol b/src/Periphery/LDA/Facets/VelodromeV2Facet.sol index aca380b3e..ff52a8df8 100644 --- a/src/Periphery/LDA/Facets/VelodromeV2Facet.sol +++ b/src/Periphery/LDA/Facets/VelodromeV2Facet.sol @@ -23,6 +23,10 @@ contract VelodromeV2Facet is BaseRouteConstants { // ==== External Functions ==== /// @notice Performs a swap through VelodromeV2 pools /// @dev Handles token transfers and optional callbacks, with comprehensive safety checks + /// This function is marked `payable` to support multihop routes that start + /// with a native asset (e.g., ETH -> WETH -> USDC). The initial `msg.value` + /// is preserved through the `delegatecall` chain and this function must be + /// able to receive it, even if the value is not used in this step. /// @param swapData Encoded swap parameters [pool, direction, destinationAddress, callback] /// @param from Token source address - if equals msg.sender or this contract, tokens will be transferred; /// otherwise assumes tokens are at receiver address (FUNDS_IN_RECEIVER) @@ -33,7 +37,7 @@ contract VelodromeV2Facet is BaseRouteConstants { address from, address tokenIn, uint256 amountIn - ) external { + ) external payable { uint256 stream = LibPackedStream.createStream(swapData); address pool = stream.readAddress(); From 852128c19713c43b5a61360875530a26bb38cf1f Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 26 Sep 2025 10:00:24 +0200 Subject: [PATCH 209/220] new deploy logs --- deployments/_deployments_log_file.json | 48 +++++++++++++------------- deployments/arbitrum.staging.json | 16 ++++----- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 2f2feb95f..0973287d9 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -39968,11 +39968,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x8162d12A6Db97C601F16BD474670DC8bF2456a41", + "ADDRESS": "0x8D86c444698760F8bd4FFd2828B3fEA6259062F3", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:13:39", + "TIMESTAMP": "2025-09-26 01:30:26", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40015,11 +40015,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x29999754A40dc303C1aC0bEF058658DE6098fbB7", + "ADDRESS": "0xfDE53F231c0FC0430fDb88a6c834E8766c1597C4", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:13:18", + "TIMESTAMP": "2025-09-26 01:12:08", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40062,11 +40062,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x32Dde41a556Ad153371FcA83e11723Bb7682C61d", + "ADDRESS": "0xE1914A712c15cA52e884fb82d6634229F04D9C1e", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:12:17", + "TIMESTAMP": "2025-09-26 00:50:06", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40109,11 +40109,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x62cb24d3D3847af24b82D087E82bebC3305d16c6", + "ADDRESS": "0x462e4F563D75710216BBf658542D1561b3CB3142", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:11:27", + "TIMESTAMP": "2025-09-26 00:39:43", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40203,11 +40203,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xC02fBdb4a97f14E73F456aE77a603F85C1a5fD67", + "ADDRESS": "0x23453E98fB5bdB779780eeC434297873544d706C", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:10:24", + "TIMESTAMP": "2025-09-26 00:25:59", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40297,11 +40297,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x475d3f49A51f8ff44d84257C46Ee389333B1BF42", + "ADDRESS": "0x522A52c5cae7d2740e68Eb5cDcbe997296f939E6", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:11:55", + "TIMESTAMP": "2025-09-26 00:47:04", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40344,11 +40344,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xB99d4DBb0A6C6cdeBb1Df59e243600A0795a1055", + "ADDRESS": "0x064813b9A1D501da2440a85686d6d367F19316Fa", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:12:46", + "TIMESTAMP": "2025-09-26 00:58:41", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } @@ -40391,11 +40391,11 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x4f5EbfA073440800722DbaD2A57805ec3065C93e", + "ADDRESS": "0x04BE6CC23d77038C1E0cd78c5c26962F958273Fd", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-09-12 15:14:03", + "TIMESTAMP": "2025-09-26 01:43:39", "CONSTRUCTOR_ARGS": "0x", - "SALT": "22345115", + "SALT": "", "VERIFIED": "true", "ZK_SOLC_VERSION": "" } diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 746e1c0e7..5c13013be 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -63,14 +63,14 @@ "WhitelistManagerFacet": "0x603f0c31B37E5ca3eA75D5730CCfaBCFF6D17aa3", "RelayDepositoryFacet": "0x004E291b9244C811B0BE00cA2C179d54FAA5073D", "LiFiDEXAggregatorDiamond": "0xF5f53304F9E82bEaBe3eE99cc12a3AB9990557de", - "AlgebraFacet": "0xC02fBdb4a97f14E73F456aE77a603F85C1a5fD67", + "AlgebraFacet": "0x23453E98fB5bdB779780eeC434297873544d706C", "CoreRouteFacet": "0x0d47366d1Fbe6BC60B9633589EbEFDC8A5403bcC", "CurveFacet": "0x839b4B0aa0B68dE14814609C48067e4CBf559F67", - "KatanaV3Facet": "0x475d3f49A51f8ff44d84257C46Ee389333B1BF42", - "NativeWrapperFacet": "0x32Dde41a556Ad153371FcA83e11723Bb7682C61d", - "SyncSwapV2Facet": "0xB99d4DBb0A6C6cdeBb1Df59e243600A0795a1055", - "UniV2StyleFacet": "0x29999754A40dc303C1aC0bEF058658DE6098fbB7", - "UniV3StyleFacet": "0x8162d12A6Db97C601F16BD474670DC8bF2456a41", - "VelodromeV2Facet": "0x4f5EbfA073440800722DbaD2A57805ec3065C93e", - "IzumiV3Facet": "0x62cb24d3D3847af24b82D087E82bebC3305d16c6" + "KatanaV3Facet": "0x522A52c5cae7d2740e68Eb5cDcbe997296f939E6", + "NativeWrapperFacet": "0xE1914A712c15cA52e884fb82d6634229F04D9C1e", + "SyncSwapV2Facet": "0x064813b9A1D501da2440a85686d6d367F19316Fa", + "UniV2StyleFacet": "0xfDE53F231c0FC0430fDb88a6c834E8766c1597C4", + "UniV3StyleFacet": "0x8D86c444698760F8bd4FFd2828B3fEA6259062F3", + "VelodromeV2Facet": "0x04BE6CC23d77038C1E0cd78c5c26962F958273Fd", + "IzumiV3Facet": "0x462e4F563D75710216BBf658542D1561b3CB3142" } \ No newline at end of file From 63cbebcce6fbdea44c9dde1b2da542aa168f38f9 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 26 Sep 2025 11:39:02 +0200 Subject: [PATCH 210/220] deploy core facets for lda for apechain --- deployments/_deployments_log_file.json | 39 ++++++++++++++++++++++++++ deployments/apechain.staging.json | 5 +++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 0973287d9..5b163d7d9 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -744,6 +744,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x85bc11e06719Ec8dDc3253676283F44E625598Ab", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:03:48", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "sonic": { @@ -1789,6 +1802,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xE941EE035157Ca706A82AEcC53BfAcC5abBaa12D", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:06:05", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "sonic": { @@ -2861,6 +2887,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x56db197b1d1021ec1825a9A434049AE5d722bfad", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:09:59", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "sonic": { diff --git a/deployments/apechain.staging.json b/deployments/apechain.staging.json index 83545a0f5..28b7dafc4 100644 --- a/deployments/apechain.staging.json +++ b/deployments/apechain.staging.json @@ -1,3 +1,6 @@ { - "LiFiDEXAggregator": "0x31b0b4DF8fE055532bd106b2E7C8afCFd39B65De" + "LiFiDEXAggregator": "0x31b0b4DF8fE055532bd106b2E7C8afCFd39B65De", + "DiamondCutFacet": "0x85bc11e06719Ec8dDc3253676283F44E625598Ab", + "DiamondLoupeFacet": "0xE941EE035157Ca706A82AEcC53BfAcC5abBaa12D", + "OwnershipFacet": "0x56db197b1d1021ec1825a9A434049AE5d722bfad" } \ No newline at end of file From cf448a49a101f04a05ff6e7392ca03a5060d4e68 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 26 Sep 2025 12:05:29 +0200 Subject: [PATCH 211/220] added apechain lifidexaggregator --- deployments/_deployments_log_file.json | 150 ++++++++++++++++++ deployments/apechain.diamond.lda.staging.json | 58 +++++++ deployments/apechain.staging.json | 13 +- 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 deployments/apechain.diamond.lda.staging.json diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 5b163d7d9..7788781e5 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -40000,6 +40000,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xb1cA06bEc28471fcA197a09494249c66929E37bC", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:42:37", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV3StyleFacet": { @@ -40047,6 +40062,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x6212d5d99379fa8678ca5C2Ad2a676097E7Ed037", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:59:13", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV2StyleFacet": { @@ -40094,6 +40124,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x6741Dc1295Df351C9B26210026563cEA04c657bd", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:56:05", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "NativeWrapperFacet": { @@ -40141,6 +40186,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xcC01769a3dF6F340f5CF0C7BB8a45C2d57BbA15b", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:51:33", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "IzumiV3Facet": { @@ -40188,6 +40248,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x14d94db24A2ac5dEF684e44e618Aa9052371Feb1", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:47:10", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LiFiDEXAggregatorDiamond": { @@ -40282,6 +40357,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x041b54C9079E5289F63950A7d771a432F4E83035", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:40:27", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "CurveFacet": { @@ -40329,6 +40419,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x854103F3b1bA069B86A9EFdb1e9796A873bCCf05", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:44:49", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "KatanaV3Facet": { @@ -40376,6 +40481,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xE5762D9874754a8F890A95031e1F99624EF5600E", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:49:20", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "SyncSwapV2Facet": { @@ -40423,6 +40543,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xd7078122Db3F81ac27f87FBC56BEC2E8ecB2539A", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 11:53:49", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "VelodromeV2Facet": { @@ -40470,6 +40605,21 @@ } ] } + }, + "apechain": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x696007104F48e7100be332314c6078E79bE4EAA1", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 12:01:33", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } } } diff --git a/deployments/apechain.diamond.lda.staging.json b/deployments/apechain.diamond.lda.staging.json new file mode 100644 index 000000000..27f476f99 --- /dev/null +++ b/deployments/apechain.diamond.lda.staging.json @@ -0,0 +1,58 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": { + "0x85bc11e06719Ec8dDc3253676283F44E625598Ab": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0xE941EE035157Ca706A82AEcC53BfAcC5abBaa12D": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x56db197b1d1021ec1825a9A434049AE5d722bfad": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0x041b54C9079E5289F63950A7d771a432F4E83035": { + "Name": "AlgebraFacet", + "Version": "1.0.0" + }, + "0xb1cA06bEc28471fcA197a09494249c66929E37bC": { + "Name": "CoreRouteFacet", + "Version": "1.0.0" + }, + "0x854103F3b1bA069B86A9EFdb1e9796A873bCCf05": { + "Name": "CurveFacet", + "Version": "1.0.0" + }, + "0x14d94db24A2ac5dEF684e44e618Aa9052371Feb1": { + "Name": "IzumiV3Facet", + "Version": "1.0.0" + }, + "0xE5762D9874754a8F890A95031e1F99624EF5600E": { + "Name": "KatanaV3Facet", + "Version": "1.0.0" + }, + "0xcC01769a3dF6F340f5CF0C7BB8a45C2d57BbA15b": { + "Name": "NativeWrapperFacet", + "Version": "1.0.0" + }, + "0xd7078122Db3F81ac27f87FBC56BEC2E8ecB2539A": { + "Name": "SyncSwapV2Facet", + "Version": "1.0.0" + }, + "0x6741Dc1295Df351C9B26210026563cEA04c657bd": { + "Name": "UniV2StyleFacet", + "Version": "1.0.0" + }, + "0x6212d5d99379fa8678ca5C2Ad2a676097E7Ed037": { + "Name": "UniV3StyleFacet", + "Version": "1.0.0" + }, + "0x696007104F48e7100be332314c6078E79bE4EAA1": { + "Name": "VelodromeV2Facet", + "Version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/deployments/apechain.staging.json b/deployments/apechain.staging.json index 28b7dafc4..5bba8002d 100644 --- a/deployments/apechain.staging.json +++ b/deployments/apechain.staging.json @@ -2,5 +2,16 @@ "LiFiDEXAggregator": "0x31b0b4DF8fE055532bd106b2E7C8afCFd39B65De", "DiamondCutFacet": "0x85bc11e06719Ec8dDc3253676283F44E625598Ab", "DiamondLoupeFacet": "0xE941EE035157Ca706A82AEcC53BfAcC5abBaa12D", - "OwnershipFacet": "0x56db197b1d1021ec1825a9A434049AE5d722bfad" + "OwnershipFacet": "0x56db197b1d1021ec1825a9A434049AE5d722bfad", + "LiFiDEXAggregatorDiamond": "0xAddC2ae2CAc0d457b41E746467e81c4E96964a61", + "AlgebraFacet": "0x041b54C9079E5289F63950A7d771a432F4E83035", + "CoreRouteFacet": "0xb1cA06bEc28471fcA197a09494249c66929E37bC", + "CurveFacet": "0x854103F3b1bA069B86A9EFdb1e9796A873bCCf05", + "IzumiV3Facet": "0x14d94db24A2ac5dEF684e44e618Aa9052371Feb1", + "KatanaV3Facet": "0xE5762D9874754a8F890A95031e1F99624EF5600E", + "NativeWrapperFacet": "0xcC01769a3dF6F340f5CF0C7BB8a45C2d57BbA15b", + "SyncSwapV2Facet": "0xd7078122Db3F81ac27f87FBC56BEC2E8ecB2539A", + "UniV2StyleFacet": "0x6741Dc1295Df351C9B26210026563cEA04c657bd", + "UniV3StyleFacet": "0x6212d5d99379fa8678ca5C2Ad2a676097E7Ed037", + "VelodromeV2Facet": "0x696007104F48e7100be332314c6078E79bE4EAA1" } \ No newline at end of file From ca99919619471ee656c49b875db4cec7fe2ed705 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 26 Sep 2025 14:04:56 +0200 Subject: [PATCH 212/220] added ronin staging LDA --- deployments/_deployments_log_file.json | 189 +++++++++++++++++++++ deployments/ronin.diamond.lda.staging.json | 5 + deployments/ronin.staging.json | 16 ++ 3 files changed, 210 insertions(+) create mode 100644 deployments/ronin.diamond.lda.staging.json create mode 100644 deployments/ronin.staging.json diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 7788781e5..f7b924337 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -995,6 +995,19 @@ "VERIFIED": "false" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x440Fd10949EC42Bd1034Fe7E54D742454FD9ED55", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 12:12:00", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] } }, "katana": { @@ -2067,6 +2080,19 @@ "VERIFIED": "false" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2bAd54B70e8b7AC6829791de525B6fEe52DB1dBc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 12:16:35", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] } }, "katana": { @@ -3138,6 +3164,19 @@ "VERIFIED": "false" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x4D57Be68eA18e441B16A12361195759c91dfB6aE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 12:20:42", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] } }, "katana": { @@ -40015,6 +40054,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x36f16Baf237bAa4f6fe91ea392ddB74f67fE46D5", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 12:39:44", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV3StyleFacet": { @@ -40077,6 +40131,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2c9d60D7A57171e0eaAAE38d200801e8e5C91274", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 13:54:45", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV2StyleFacet": { @@ -40139,6 +40208,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x11Bb8fCeEf956201353257292f66A1a726c414b6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 13:49:48", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "NativeWrapperFacet": { @@ -40201,6 +40285,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9CcC15B2Acdf4929C6b343673ED94cdf13e9E59C", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 13:41:32", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "IzumiV3Facet": { @@ -40263,6 +40362,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xFB29877e1ed744bCccfaAD0B55A3d6d1Ec244B45", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 13:33:24", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LiFiDEXAggregatorDiamond": { @@ -40372,6 +40486,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x50EFcBF64Cf45DE9bA8CB5B15aafB094Db402c53", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 12:35:30", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "CurveFacet": { @@ -40434,6 +40563,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x0CE83BcE19987935BfD9eb27FaF984ddc65B2fA4", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 12:43:51", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "KatanaV3Facet": { @@ -40496,6 +40640,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x5B36B428a05D6460Fc404D0c104D3Fc23cC98136", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 13:37:30", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "SyncSwapV2Facet": { @@ -40558,6 +40717,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x07616Aed6E7D346D9ef0154131e53A59d7646fDA", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 13:45:38", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "VelodromeV2Facet": { @@ -40620,6 +40794,21 @@ } ] } + }, + "ronin": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x71b9D7FFfa9F28f46351Ca0B8BeFA2744c89c74B", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 13:58:57", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "" + } + ] + } } } } diff --git a/deployments/ronin.diamond.lda.staging.json b/deployments/ronin.diamond.lda.staging.json new file mode 100644 index 000000000..4579f4d9e --- /dev/null +++ b/deployments/ronin.diamond.lda.staging.json @@ -0,0 +1,5 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": {} + } +} \ No newline at end of file diff --git a/deployments/ronin.staging.json b/deployments/ronin.staging.json new file mode 100644 index 000000000..1f0849c60 --- /dev/null +++ b/deployments/ronin.staging.json @@ -0,0 +1,16 @@ +{ + "DiamondCutFacet": "0x440Fd10949EC42Bd1034Fe7E54D742454FD9ED55", + "DiamondLoupeFacet": "0x2bAd54B70e8b7AC6829791de525B6fEe52DB1dBc", + "OwnershipFacet": "0x4D57Be68eA18e441B16A12361195759c91dfB6aE", + "LiFiDEXAggregatorDiamond": "0xb651Cc7006B60535d2287b9B283875530dCEd8B0", + "AlgebraFacet": "0x50EFcBF64Cf45DE9bA8CB5B15aafB094Db402c53", + "CoreRouteFacet": "0x36f16Baf237bAa4f6fe91ea392ddB74f67fE46D5", + "CurveFacet": "0x0CE83BcE19987935BfD9eb27FaF984ddc65B2fA4", + "IzumiV3Facet": "0xFB29877e1ed744bCccfaAD0B55A3d6d1Ec244B45", + "KatanaV3Facet": "0x5B36B428a05D6460Fc404D0c104D3Fc23cC98136", + "NativeWrapperFacet": "0x9CcC15B2Acdf4929C6b343673ED94cdf13e9E59C", + "SyncSwapV2Facet": "0x07616Aed6E7D346D9ef0154131e53A59d7646fDA", + "UniV2StyleFacet": "0x11Bb8fCeEf956201353257292f66A1a726c414b6", + "UniV3StyleFacet": "0x2c9d60D7A57171e0eaAAE38d200801e8e5C91274", + "VelodromeV2Facet": "0x71b9D7FFfa9F28f46351Ca0B8BeFA2744c89c74B" +} \ No newline at end of file From 257a4ede9b1dafac5e39f6cd686c6dc606a6424b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 26 Sep 2025 14:20:43 +0200 Subject: [PATCH 213/220] celo deployed core LDA facets --- deployments/_deployments_log_file.json | 39 ++++++++++++++++++++++++++ deployments/celo.staging.json | 5 ++++ 2 files changed, 44 insertions(+) create mode 100644 deployments/celo.staging.json diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index f7b924337..a9bb38f1b 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -330,6 +330,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x623e85b8Bb1bCb8820e92BF889b3f725e15B6b0c", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:10:56", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "cronos": { @@ -1415,6 +1428,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x6a540600082339a0F1Be494f71e22647CE8F5c65", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:13:39", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "velas": { @@ -2499,6 +2525,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2aA310807442489ace0BE42E3E5607780722375c", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:17:44", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "velas": { diff --git a/deployments/celo.staging.json b/deployments/celo.staging.json new file mode 100644 index 000000000..e96e058b2 --- /dev/null +++ b/deployments/celo.staging.json @@ -0,0 +1,5 @@ +{ + "DiamondCutFacet": "0x623e85b8Bb1bCb8820e92BF889b3f725e15B6b0c", + "DiamondLoupeFacet": "0x6a540600082339a0F1Be494f71e22647CE8F5c65", + "OwnershipFacet": "0x2aA310807442489ace0BE42E3E5607780722375c" +} \ No newline at end of file From a7dd359247171059c2329bf58951c8aaf54912d0 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 26 Sep 2025 14:40:05 +0200 Subject: [PATCH 214/220] added celo staging LDA deployment --- deployments/_deployments_log_file.json | 150 ++++++++++++++++++++++ deployments/celo.diamond.lda.staging.json | 58 +++++++++ deployments/celo.staging.json | 13 +- 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 deployments/celo.diamond.lda.staging.json diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index a9bb38f1b..ec01a1fa6 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -40108,6 +40108,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x0745C26fABf7CC09D216b85Fd0e3E4F3a2D92722", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:23:21", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV3StyleFacet": { @@ -40185,6 +40200,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x3a82c34B1D51C79beaC85A1db04873C8a1cc7FeA", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:34:49", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV2StyleFacet": { @@ -40262,6 +40292,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x0D8332Fa8d5845D68311E106a55fc264e209f9E7", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:33:10", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "NativeWrapperFacet": { @@ -40339,6 +40384,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xB3a006e66523275685BE0f4FC969A2928B4BF5cc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:29:49", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "IzumiV3Facet": { @@ -40416,6 +40476,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x6eE2187aA149aEE3C93b094A68Ce510E0736b3CD", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:26:35", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LiFiDEXAggregatorDiamond": { @@ -40540,6 +40615,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x7F7D2D2C8C58E8Ce5EF87AfE203C2931709a4Fe2", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:21:46", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "CurveFacet": { @@ -40617,6 +40707,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x29232CE04d6559E9Fa97c8CEda8CaB951eAbAfd7", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:24:58", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "KatanaV3Facet": { @@ -40694,6 +40799,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9dB2c7d7aEf3e0c5dEC9DF53903EBE4c30891e47", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:28:12", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "SyncSwapV2Facet": { @@ -40771,6 +40891,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2D097d669Db40A7C64360CAE4e64e50485089170", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:31:27", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "VelodromeV2Facet": { @@ -40848,6 +40983,21 @@ } ] } + }, + "celo": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x5138DD64a101eaB616aA8B3D6C93A6658F117Cf0", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-09-26 14:36:29", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "24354312", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } } } diff --git a/deployments/celo.diamond.lda.staging.json b/deployments/celo.diamond.lda.staging.json new file mode 100644 index 000000000..0634a614e --- /dev/null +++ b/deployments/celo.diamond.lda.staging.json @@ -0,0 +1,58 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": { + "0x623e85b8Bb1bCb8820e92BF889b3f725e15B6b0c": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0x6a540600082339a0F1Be494f71e22647CE8F5c65": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x2aA310807442489ace0BE42E3E5607780722375c": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0x7F7D2D2C8C58E8Ce5EF87AfE203C2931709a4Fe2": { + "Name": "AlgebraFacet", + "Version": "1.0.0" + }, + "0x0745C26fABf7CC09D216b85Fd0e3E4F3a2D92722": { + "Name": "CoreRouteFacet", + "Version": "1.0.0" + }, + "0x29232CE04d6559E9Fa97c8CEda8CaB951eAbAfd7": { + "Name": "CurveFacet", + "Version": "1.0.0" + }, + "0x6eE2187aA149aEE3C93b094A68Ce510E0736b3CD": { + "Name": "IzumiV3Facet", + "Version": "1.0.0" + }, + "0x9dB2c7d7aEf3e0c5dEC9DF53903EBE4c30891e47": { + "Name": "KatanaV3Facet", + "Version": "1.0.0" + }, + "0xB3a006e66523275685BE0f4FC969A2928B4BF5cc": { + "Name": "NativeWrapperFacet", + "Version": "1.0.0" + }, + "0x2D097d669Db40A7C64360CAE4e64e50485089170": { + "Name": "SyncSwapV2Facet", + "Version": "1.0.0" + }, + "0x0D8332Fa8d5845D68311E106a55fc264e209f9E7": { + "Name": "UniV2StyleFacet", + "Version": "1.0.0" + }, + "0x3a82c34B1D51C79beaC85A1db04873C8a1cc7FeA": { + "Name": "UniV3StyleFacet", + "Version": "1.0.0" + }, + "0x5138DD64a101eaB616aA8B3D6C93A6658F117Cf0": { + "Name": "VelodromeV2Facet", + "Version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/deployments/celo.staging.json b/deployments/celo.staging.json index e96e058b2..981877c8a 100644 --- a/deployments/celo.staging.json +++ b/deployments/celo.staging.json @@ -1,5 +1,16 @@ { "DiamondCutFacet": "0x623e85b8Bb1bCb8820e92BF889b3f725e15B6b0c", "DiamondLoupeFacet": "0x6a540600082339a0F1Be494f71e22647CE8F5c65", - "OwnershipFacet": "0x2aA310807442489ace0BE42E3E5607780722375c" + "OwnershipFacet": "0x2aA310807442489ace0BE42E3E5607780722375c", + "LiFiDEXAggregatorDiamond": "0x8C2bee5ED1ec136401bF8139973FB45D06b366FD", + "AlgebraFacet": "0x7F7D2D2C8C58E8Ce5EF87AfE203C2931709a4Fe2", + "CoreRouteFacet": "0x0745C26fABf7CC09D216b85Fd0e3E4F3a2D92722", + "CurveFacet": "0x29232CE04d6559E9Fa97c8CEda8CaB951eAbAfd7", + "IzumiV3Facet": "0x6eE2187aA149aEE3C93b094A68Ce510E0736b3CD", + "KatanaV3Facet": "0x9dB2c7d7aEf3e0c5dEC9DF53903EBE4c30891e47", + "NativeWrapperFacet": "0xB3a006e66523275685BE0f4FC969A2928B4BF5cc", + "SyncSwapV2Facet": "0x2D097d669Db40A7C64360CAE4e64e50485089170", + "UniV2StyleFacet": "0x0D8332Fa8d5845D68311E106a55fc264e209f9E7", + "UniV3StyleFacet": "0x3a82c34B1D51C79beaC85A1db04873C8a1cc7FeA", + "VelodromeV2Facet": "0x5138DD64a101eaB616aA8B3D6C93A6658F117Cf0" } \ No newline at end of file From 0e21a5ec85a6076213c32b947d386af587e6f903 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Wed, 1 Oct 2025 00:28:38 +0200 Subject: [PATCH 215/220] added plume staging diamond --- deployments/_deployments_log_file.json | 189 +++++++++++++++++++++ deployments/plume.diamond.lda.staging.json | 58 +++++++ deployments/plume.staging.json | 16 ++ 3 files changed, 263 insertions(+) create mode 100644 deployments/plume.diamond.lda.staging.json create mode 100644 deployments/plume.staging.json diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 246e7ee6d..5a82a2d30 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -1049,6 +1049,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x1590b7fe02b49206Ee43a53Ac2641487cbE79976", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:18:13", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "vana": { @@ -2162,6 +2175,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xD8d8b071A40db11D6479A6da25C5E8207074CfC3", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:19:16", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "vana": { @@ -3274,6 +3300,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x5ADA0122fBF4EA6f46320946469354D43d5D31e1", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:20:03", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] } }, "vana": { @@ -42437,6 +42476,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x8297890dfF469F90E10f0042E4f5ceB8e56903da", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:21:46", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV3StyleFacet": { @@ -42529,6 +42583,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xE786a27820725884880306094FEdE01C7d2B9F5b", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:26:25", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV2StyleFacet": { @@ -42621,6 +42690,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xdc592beC201b7C28cD5C60DEeaBA44D8ACdA61b7", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:25:46", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "NativeWrapperFacet": { @@ -42713,6 +42797,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x61d83FD6A30386024e33d66862e9627d53Ddf081", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:24:26", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "IzumiV3Facet": { @@ -42805,6 +42904,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xC807C36D4094395818F432A557ea2e09cd4a5e93", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:23:04", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LiFiDEXAggregatorDiamond": { @@ -42944,6 +43058,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xd61FaEDCdC9EeF3170AD2D06eC93b346e7a27f46", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:21:07", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "CurveFacet": { @@ -43036,6 +43165,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xc0942d9DbE4D5bFbC80DF044AAd1341B68dDbBd3", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:22:25", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "KatanaV3Facet": { @@ -43128,6 +43272,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9C6e1543c127C98bAd9F25e3b59f49B0dcE0daEd", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:23:46", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "SyncSwapV2Facet": { @@ -43220,6 +43379,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x60855F101E6D99204cC2B4aDC044E08efCEbD865", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:25:05", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "VelodromeV2Facet": { @@ -43312,6 +43486,21 @@ } ] } + }, + "plume": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x0583F88FEaAF3fDaEDB1331AAB87947BB518Aef9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-01 00:27:08", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345109", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } } } diff --git a/deployments/plume.diamond.lda.staging.json b/deployments/plume.diamond.lda.staging.json new file mode 100644 index 000000000..c97be9525 --- /dev/null +++ b/deployments/plume.diamond.lda.staging.json @@ -0,0 +1,58 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": { + "0x1590b7fe02b49206Ee43a53Ac2641487cbE79976": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0xD8d8b071A40db11D6479A6da25C5E8207074CfC3": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x5ADA0122fBF4EA6f46320946469354D43d5D31e1": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0xd61FaEDCdC9EeF3170AD2D06eC93b346e7a27f46": { + "Name": "AlgebraFacet", + "Version": "1.0.0" + }, + "0x8297890dfF469F90E10f0042E4f5ceB8e56903da": { + "Name": "CoreRouteFacet", + "Version": "1.0.0" + }, + "0xc0942d9DbE4D5bFbC80DF044AAd1341B68dDbBd3": { + "Name": "CurveFacet", + "Version": "1.0.0" + }, + "0xC807C36D4094395818F432A557ea2e09cd4a5e93": { + "Name": "IzumiV3Facet", + "Version": "1.0.0" + }, + "0x9C6e1543c127C98bAd9F25e3b59f49B0dcE0daEd": { + "Name": "KatanaV3Facet", + "Version": "1.0.0" + }, + "0x61d83FD6A30386024e33d66862e9627d53Ddf081": { + "Name": "NativeWrapperFacet", + "Version": "1.0.0" + }, + "0x60855F101E6D99204cC2B4aDC044E08efCEbD865": { + "Name": "SyncSwapV2Facet", + "Version": "1.0.0" + }, + "0xdc592beC201b7C28cD5C60DEeaBA44D8ACdA61b7": { + "Name": "UniV2StyleFacet", + "Version": "1.0.0" + }, + "0xE786a27820725884880306094FEdE01C7d2B9F5b": { + "Name": "UniV3StyleFacet", + "Version": "1.0.0" + }, + "0x0583F88FEaAF3fDaEDB1331AAB87947BB518Aef9": { + "Name": "VelodromeV2Facet", + "Version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/deployments/plume.staging.json b/deployments/plume.staging.json new file mode 100644 index 000000000..907b00363 --- /dev/null +++ b/deployments/plume.staging.json @@ -0,0 +1,16 @@ +{ + "DiamondCutFacet": "0x1590b7fe02b49206Ee43a53Ac2641487cbE79976", + "DiamondLoupeFacet": "0xD8d8b071A40db11D6479A6da25C5E8207074CfC3", + "OwnershipFacet": "0x5ADA0122fBF4EA6f46320946469354D43d5D31e1", + "LiFiDEXAggregatorDiamond": "0x5cFf03bc36C1dD2967b0d09Ac74C80F0e2D2612e", + "AlgebraFacet": "0xd61FaEDCdC9EeF3170AD2D06eC93b346e7a27f46", + "CoreRouteFacet": "0x8297890dfF469F90E10f0042E4f5ceB8e56903da", + "CurveFacet": "0xc0942d9DbE4D5bFbC80DF044AAd1341B68dDbBd3", + "IzumiV3Facet": "0xC807C36D4094395818F432A557ea2e09cd4a5e93", + "KatanaV3Facet": "0x9C6e1543c127C98bAd9F25e3b59f49B0dcE0daEd", + "NativeWrapperFacet": "0x61d83FD6A30386024e33d66862e9627d53Ddf081", + "SyncSwapV2Facet": "0x60855F101E6D99204cC2B4aDC044E08efCEbD865", + "UniV2StyleFacet": "0xdc592beC201b7C28cD5C60DEeaBA44D8ACdA61b7", + "UniV3StyleFacet": "0xE786a27820725884880306094FEdE01C7d2B9F5b", + "VelodromeV2Facet": "0x0583F88FEaAF3fDaEDB1331AAB87947BB518Aef9" +} From ea034d77be6a05871b416bf1f27f0bb001d22742 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Mon, 13 Oct 2025 20:51:24 +0200 Subject: [PATCH 216/220] deployed zksync lifi dex aggregator setup --- deployments/_deployments_log_file.json | 189 ++++++++++++++++++++ deployments/zksync.diamond.lda.staging.json | 58 ++++++ deployments/zksync.staging.json | 16 ++ 3 files changed, 263 insertions(+) create mode 100644 deployments/zksync.diamond.lda.staging.json create mode 100644 deployments/zksync.staging.json diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 5a82a2d30..90ad17de8 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -492,6 +492,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x60AdE68e9a048e41FAbfe3e0c4738A4f332372BB", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-10 23:11:02", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345102", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.11" + } + ] } }, "base": { @@ -1604,6 +1617,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x602FA7c37f3fe532450F7D9039b17dC13df60a65", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-10 23:18:05", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345102", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.11" + } + ] } }, "base": { @@ -2729,6 +2755,19 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x7A7d3d5caA0010a945C01ce75e4758697f841dcF", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-10-10 23:22:23", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345102", + "VERIFIED": "false", + "ZK_SOLC_VERSION": "1.5.11" + } + ] } }, "base": { @@ -42491,6 +42530,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x3598833FA88c04f4e06Fe41D44b71402f5832660", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:37:17", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV3StyleFacet": { @@ -42598,6 +42652,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x75D734DD826Aa126178B1AC5920FE7EF61252FF6", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:46:13", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "UniV2StyleFacet": { @@ -42705,6 +42774,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xf0fcd6a8db420D5DEB48F84adDF89d783e13927f", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:44:58", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "NativeWrapperFacet": { @@ -42812,6 +42896,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xb08d2084955370ba83452a55c4b79Fb895852599", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:42:16", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "IzumiV3Facet": { @@ -42919,6 +43018,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xd56c60D32fC9343Ff75386498940BA6d6bE71E92", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:39:41", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "LiFiDEXAggregatorDiamond": { @@ -43073,6 +43187,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x2215937Eda2Fb876f904BEFB78712E36369C61E7", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:35:39", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "CurveFacet": { @@ -43180,6 +43309,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x8A3e0E1De58E73cA37273613F39211fF4d29DF3d", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:38:31", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "KatanaV3Facet": { @@ -43287,6 +43431,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x6F3e876F8701264996B176AD2df2f99D5B070aD6", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:41:01", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "SyncSwapV2Facet": { @@ -43394,6 +43553,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x8028d853E44CaC441340e9b4fde6039604d8C9c2", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:43:35", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } }, "VelodromeV2Facet": { @@ -43501,6 +43675,21 @@ } ] } + }, + "zksync": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x83A82B653B30A96F2f3DefDDB3E329Bc3dA9a6D7", + "OPTIMIZER_RUNS": "1000000\n1000000", + "TIMESTAMP": "2025-10-13 20:47:34", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "22345094", + "VERIFIED": "true", + "ZK_SOLC_VERSION": "" + } + ] + } } } } diff --git a/deployments/zksync.diamond.lda.staging.json b/deployments/zksync.diamond.lda.staging.json new file mode 100644 index 000000000..dd230c301 --- /dev/null +++ b/deployments/zksync.diamond.lda.staging.json @@ -0,0 +1,58 @@ +{ + "LiFiDEXAggregatorDiamond": { + "Facets": { + "0x60AdE68e9a048e41FAbfe3e0c4738A4f332372BB": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0x602FA7c37f3fe532450F7D9039b17dC13df60a65": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x7A7d3d5caA0010a945C01ce75e4758697f841dcF": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0x2215937Eda2Fb876f904BEFB78712E36369C61E7": { + "Name": "AlgebraFacet", + "Version": "1.0.0" + }, + "0x3598833FA88c04f4e06Fe41D44b71402f5832660": { + "Name": "CoreRouteFacet", + "Version": "1.0.0" + }, + "0x8A3e0E1De58E73cA37273613F39211fF4d29DF3d": { + "Name": "CurveFacet", + "Version": "1.0.0" + }, + "0xd56c60D32fC9343Ff75386498940BA6d6bE71E92": { + "Name": "IzumiV3Facet", + "Version": "1.0.0" + }, + "0x6F3e876F8701264996B176AD2df2f99D5B070aD6": { + "Name": "KatanaV3Facet", + "Version": "1.0.0" + }, + "0xb08d2084955370ba83452a55c4b79Fb895852599": { + "Name": "NativeWrapperFacet", + "Version": "1.0.0" + }, + "0x8028d853E44CaC441340e9b4fde6039604d8C9c2": { + "Name": "SyncSwapV2Facet", + "Version": "1.0.0" + }, + "0xf0fcd6a8db420D5DEB48F84adDF89d783e13927f": { + "Name": "UniV2StyleFacet", + "Version": "1.0.0" + }, + "0x75D734DD826Aa126178B1AC5920FE7EF61252FF6": { + "Name": "UniV3StyleFacet", + "Version": "1.0.0" + }, + "0x83A82B653B30A96F2f3DefDDB3E329Bc3dA9a6D7": { + "Name": "VelodromeV2Facet", + "Version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/deployments/zksync.staging.json b/deployments/zksync.staging.json new file mode 100644 index 000000000..50f806732 --- /dev/null +++ b/deployments/zksync.staging.json @@ -0,0 +1,16 @@ +{ + "DiamondCutFacet": "0x60AdE68e9a048e41FAbfe3e0c4738A4f332372BB", + "DiamondLoupeFacet": "0x602FA7c37f3fe532450F7D9039b17dC13df60a65", + "OwnershipFacet": "0x7A7d3d5caA0010a945C01ce75e4758697f841dcF", + "LiFiDEXAggregatorDiamond": "0x1cE85531309F1F0FAaFe1a47dF1b5e90D0566834", + "AlgebraFacet": "0x2215937Eda2Fb876f904BEFB78712E36369C61E7", + "CoreRouteFacet": "0x3598833FA88c04f4e06Fe41D44b71402f5832660", + "CurveFacet": "0x8A3e0E1De58E73cA37273613F39211fF4d29DF3d", + "IzumiV3Facet": "0xd56c60D32fC9343Ff75386498940BA6d6bE71E92", + "KatanaV3Facet": "0x6F3e876F8701264996B176AD2df2f99D5B070aD6", + "NativeWrapperFacet": "0xb08d2084955370ba83452a55c4b79Fb895852599", + "SyncSwapV2Facet": "0x8028d853E44CaC441340e9b4fde6039604d8C9c2", + "UniV2StyleFacet": "0xf0fcd6a8db420D5DEB48F84adDF89d783e13927f", + "UniV3StyleFacet": "0x75D734DD826Aa126178B1AC5920FE7EF61252FF6", + "VelodromeV2Facet": "0x83A82B653B30A96F2f3DefDDB3E329Bc3dA9a6D7" +} From e86d18b92a8b621cbf2f93233d172813bd2f634a Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Tue, 14 Oct 2025 13:18:24 +0200 Subject: [PATCH 217/220] added deployment scripts for CoreRouteFacet, KatanaV3Facet, and VelodromeV2Facet in zksync LDA --- .../zksync/LDA/UpdateCoreRouteFacet.zksync.s.sol | 13 +++++++++++++ .../zksync/LDA/UpdateKatanaV3Facet.zksync.s.sol | 13 +++++++++++++ .../zksync/LDA/UpdateVelodromeV2Facet.zksync.s.sol | 13 +++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 script/deploy/zksync/LDA/UpdateCoreRouteFacet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateKatanaV3Facet.zksync.s.sol create mode 100644 script/deploy/zksync/LDA/UpdateVelodromeV2Facet.zksync.s.sol diff --git a/script/deploy/zksync/LDA/UpdateCoreRouteFacet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateCoreRouteFacet.zksync.s.sol new file mode 100644 index 000000000..b0ea64657 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateCoreRouteFacet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("CoreRouteFacet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateKatanaV3Facet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateKatanaV3Facet.zksync.s.sol new file mode 100644 index 000000000..c6328a577 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateKatanaV3Facet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("KatanaV3Facet"); + } +} diff --git a/script/deploy/zksync/LDA/UpdateVelodromeV2Facet.zksync.s.sol b/script/deploy/zksync/LDA/UpdateVelodromeV2Facet.zksync.s.sol new file mode 100644 index 000000000..2f75bf760 --- /dev/null +++ b/script/deploy/zksync/LDA/UpdateVelodromeV2Facet.zksync.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.17; + +import { UpdateLDAScriptBase } from "./utils/UpdateLDAScriptBase.sol"; + +contract DeployScript is UpdateLDAScriptBase { + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("VelodromeV2Facet"); + } +} From 997a688c9336033f9e7937d29cf9d76a8a56e19b Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 7 Nov 2025 17:03:15 +0100 Subject: [PATCH 218/220] Enhance ABI cleaning script to remove duplicate contract ABIs and events. The script now deletes draft versions of contracts that duplicate main versions, ensuring cleaner typechain type generation and preventing "Duplicate identifier" TypeScript errors. --- script/removeDuplicateEventsFromABI.ts | 36 ++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/script/removeDuplicateEventsFromABI.ts b/script/removeDuplicateEventsFromABI.ts index acd08cd96..b2f931205 100644 --- a/script/removeDuplicateEventsFromABI.ts +++ b/script/removeDuplicateEventsFromABI.ts @@ -1,21 +1,25 @@ /** - * This script cleans duplicate events from ABI files before typechain type generation. + * This script cleans duplicate events from ABI files and removes duplicate contract ABIs + * before typechain type generation. * * Background: * Due to our pattern inheritance structure in Solidity contracts, some events * are inherited multiple times through different paths, resulting in duplicate event - * definitions in the compiled ABI files. This causes typechain to generate duplicate + * definitions in the compiled ABI files. Additionally, some contracts exist in both + * main and draft versions (e.g., ERC20Permit in both lib/openzeppelin-contracts and + * lib/Permit2/lib/openzeppelin-contracts), causing typechain to generate duplicate * type definitions, leading to "Duplicate identifier" TypeScript errors. * * For example: * - OwnershipFacet.sol inherits from multiple interfaces that define the same events * - DiamondCutFacet.sol has similar inheritance patterns + * - ERC20Permit and IERC20Permit exist in both main and draft versions * * Solution: * Instead of modifying our contract inheritance structure, we clean the ABI files * after compilation but before typechain type generation. This script: - * 1. Reads specified ABI files - * 2. Removes duplicate events while preserving the first occurrence + * 1. Removes duplicate contract ABI files (draft versions that duplicate main versions) + * 2. Reads specified ABI files and removes duplicate events while preserving the first occurrence * 3. Saves the cleaned ABIs back to the files * * Usage: @@ -25,6 +29,7 @@ * 3. typechain --target ethers-v5 ... * * Note: + * - We remove draft versions of contracts that duplicate main versions * - We only clean specific files that we know have duplicate events * - The cleaning is based on event name and input parameters * - First occurrence of each event is preserved @@ -35,7 +40,7 @@ * ensures that the CI/CD pipeline can successfully generate and commit type bindings. */ -import { existsSync } from 'fs' +import { existsSync, unlinkSync } from 'fs' import { readFile, writeFile } from 'fs/promises' interface IEvent { @@ -46,6 +51,27 @@ interface IEvent { } async function removeDuplicateEventsFromABI() { + // Remove duplicate contract ABI files that cause duplicate type exports + // These are draft versions that duplicate the main versions + const duplicateContractFiles = [ + 'out/draft-ERC20Permit.sol/ERC20Permit.json', + 'out/draft-IERC20Permit.sol/IERC20Permit.json', + ] + + for (const file of duplicateContractFiles) { + if (existsSync(file)) { + try { + unlinkSync(file) + console.log(`Removed duplicate contract ABI: ${file}`) + } catch (error) { + console.error( + `Error removing ${file}:`, + error instanceof Error ? error.message : String(error) + ) + } + } + } + // Files that we know have duplicate events const filesToClean = [ 'out/OwnershipFacet.sol/OwnershipFacet.json', From b005577b609173aad68323f1f6987522afba8dec Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 7 Nov 2025 17:06:22 +0100 Subject: [PATCH 219/220] Fix json deployment file --- deployments/optimism.diamond.staging.json | 1 + 1 file changed, 1 insertion(+) diff --git a/deployments/optimism.diamond.staging.json b/deployments/optimism.diamond.staging.json index afb358a3f..65ece2883 100644 --- a/deployments/optimism.diamond.staging.json +++ b/deployments/optimism.diamond.staging.json @@ -162,3 +162,4 @@ } } } +} From 6718394cfa1150f5cd18aa3b4057498fcea8ef41 Mon Sep 17 00:00:00 2001 From: Michal Mironczuk Date: Fri, 7 Nov 2025 17:30:18 +0100 Subject: [PATCH 220/220] Update deployment log files to correct OPTIMIZER_RUNS format and clean up unused entries in optimism.diamond.staging.json --- deployments/_deployments_log_file.json | 20 ++++++++++---------- deployments/optimism.diamond.staging.json | 4 ---- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 694c8c0d1..d764b4843 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -46039,7 +46039,7 @@ "1.0.0": [ { "ADDRESS": "0x3598833FA88c04f4e06Fe41D44b71402f5832660", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:37:17", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -46161,7 +46161,7 @@ "1.0.0": [ { "ADDRESS": "0x75D734DD826Aa126178B1AC5920FE7EF61252FF6", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:46:13", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -46283,7 +46283,7 @@ "1.0.0": [ { "ADDRESS": "0xf0fcd6a8db420D5DEB48F84adDF89d783e13927f", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:44:58", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -46405,7 +46405,7 @@ "1.0.0": [ { "ADDRESS": "0xb08d2084955370ba83452a55c4b79Fb895852599", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:42:16", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -46527,7 +46527,7 @@ "1.0.0": [ { "ADDRESS": "0xd56c60D32fC9343Ff75386498940BA6d6bE71E92", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:39:41", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -46696,7 +46696,7 @@ "1.0.0": [ { "ADDRESS": "0x2215937Eda2Fb876f904BEFB78712E36369C61E7", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:35:39", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -46818,7 +46818,7 @@ "1.0.0": [ { "ADDRESS": "0x8A3e0E1De58E73cA37273613F39211fF4d29DF3d", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:38:31", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -46940,7 +46940,7 @@ "1.0.0": [ { "ADDRESS": "0x6F3e876F8701264996B176AD2df2f99D5B070aD6", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:41:01", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -47062,7 +47062,7 @@ "1.0.0": [ { "ADDRESS": "0x8028d853E44CaC441340e9b4fde6039604d8C9c2", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:43:35", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", @@ -47184,7 +47184,7 @@ "1.0.0": [ { "ADDRESS": "0x83A82B653B30A96F2f3DefDDB3E329Bc3dA9a6D7", - "OPTIMIZER_RUNS": "1000000\n1000000", + "OPTIMIZER_RUNS": "1000000", "TIMESTAMP": "2025-10-13 20:47:34", "CONSTRUCTOR_ARGS": "0x", "SALT": "22345094", diff --git a/deployments/optimism.diamond.staging.json b/deployments/optimism.diamond.staging.json index 65ece2883..4d76e08b6 100644 --- a/deployments/optimism.diamond.staging.json +++ b/deployments/optimism.diamond.staging.json @@ -123,10 +123,6 @@ "Name": "AcrossFacetPackedV4", "Version": "1.0.0" }, - "0xf536ed5A4310455FF39dBf90336e17d11550E7b4": { - "Name": "", - "Version": "" - }, "0x36e1375B0755162d720276dFF6893DF02bd49225": { "Name": "GlacisFacet", "Version": "1.1.0"