From a831dc14c567fb8548afacee31e32b4bb41cbec0 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 18 Sep 2023 16:43:34 +0300 Subject: [PATCH 01/20] Generate boilerplate --- config/ccip.json | 16 +++ docs/CCIPFacet.md | 92 ++++++++++++++ docs/README.md | 1 + script/deploy/facets/DeployCCIPFacet.s.sol | 45 +++++++ script/deploy/facets/UpdateCCIPFacet.s.sol | 58 +++++++++ src/Facets/CCIPFacet.sol | 138 +++++++++++++++++++++ test/solidity/Facets/CCIPFacet.t.sol | 126 +++++++++++++++++++ 7 files changed, 476 insertions(+) create mode 100644 config/ccip.json create mode 100644 docs/CCIPFacet.md create mode 100644 script/deploy/facets/DeployCCIPFacet.s.sol create mode 100644 script/deploy/facets/UpdateCCIPFacet.s.sol create mode 100644 src/Facets/CCIPFacet.sol create mode 100644 test/solidity/Facets/CCIPFacet.t.sol diff --git a/config/ccip.json b/config/ccip.json new file mode 100644 index 000000000..bcf06aa51 --- /dev/null +++ b/config/ccip.json @@ -0,0 +1,16 @@ +{ + "mainnet": { + "example": "0x0000000000000000000000000000000000000000", + "exampleAllowedTokens": [ + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ] + }, + "arbitrum": { + "example": "0x0000000000000000000000000000000000000000", + "exampleAllowedTokens": [ + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ] + } +} diff --git a/docs/CCIPFacet.md b/docs/CCIPFacet.md new file mode 100644 index 000000000..796792cf5 --- /dev/null +++ b/docs/CCIPFacet.md @@ -0,0 +1,92 @@ +# CCIP Facet + +## How it works + +The CCIP Facet works by ... + +```mermaid +graph LR; + D{LiFiDiamond}-- DELEGATECALL -->CCIPFacet; + CCIPFacet -- CALL --> C(CCIP) +``` + +## Public Methods + +- `function startBridgeTokensViaCCIP(BridgeData calldata _bridgeData, CCIPData calldata _ccipData)` + - Simply bridges tokens using ccip +- `swapAndStartBridgeTokensViaccip(BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, ccipData memory _ccipData)` + - Performs swap(s) before bridging tokens using ccip + +## ccip Specific Parameters + +The methods listed above take a variable labeled `_ccipData`. This data is specific to ccip and is represented as the following struct type: + +```solidity +/// @param example Example parameter. +struct ccipData { + string example; +} +``` + +## Swap Data + +Some methods accept a `SwapData _swapData` parameter. + +Swapping is performed by a swap specific library that expects an array of calldata to can be run on variaous DEXs (i.e. Uniswap) to make one or multiple swaps before performing another action. + +The swap library can be found [here](../src/Libraries/LibSwap.sol). + +## LiFi Data + +Some methods accept a `BridgeData _bridgeData` parameter. + +This parameter is strictly for analytics purposes. It's used to emit events that we can later track and index in our subgraphs and provide data on how our contracts are being used. `BridgeData` and the events we can emit can be found [here](../src/Interfaces/ILiFi.sol). + +## Getting Sample Calls to interact with the Facet + +In the following some sample calls are shown that allow you to retrieve a populated transaction that can be sent to our contract via your wallet. + +All examples use our [/quote endpoint](https://apidocs.li.fi/reference/get_quote) to retrieve a quote which contains a `transactionRequest`. This request can directly be sent to your wallet to trigger the transaction. + +The quote result looks like the following: + +```javascript +const quoteResult = { + id: '0x...', // quote id + type: 'lifi', // the type of the quote (all lifi contract calls have the type "lifi") + tool: 'ccip', // the bridge tool used for the transaction + action: {}, // information about what is going to happen + estimate: {}, // information about the estimated outcome of the call + includedSteps: [], // steps that are executed by the contract as part of this transaction, e.g. a swap step and a cross step + transactionRequest: { + // the transaction that can be sent using a wallet + data: '0x...', + to: '0x...', + value: '0x00', + from: '{YOUR_WALLET_ADDRESS}', + chainId: 100, + gasLimit: '0x...', + gasPrice: '0x...', + }, +} +``` + +A detailed explanation on how to use the /quote endpoint and how to trigger the transaction can be found [here](https://docs.li.fi/products/more-integration-options/li.fi-api/transferring-tokens-example). + +**Hint**: Don't forget to replace `{YOUR_WALLET_ADDRESS}` with your real wallet address in the examples. + +### Cross Only + +To get a transaction for a transfer from 30 USDC.e on Avalanche to USDC on Binance you can execute the following request: + +```shell +curl 'https://li.quest/v1/quote?fromChain=AVA&fromAmount=30000000&fromToken=USDC&toChain=BSC&toToken=USDC&slippage=0.03&allowBridges=ccip&fromAddress={YOUR_WALLET_ADDRESS}' +``` + +### Swap & Cross + +To get a transaction for a transfer from 30 USDT on Avalanche to USDC on Binance you can execute the following request: + +```shell +curl 'https://li.quest/v1/quote?fromChain=AVA&fromAmount=30000000&fromToken=USDT&toChain=BSC&toToken=USDC&slippage=0.03&allowBridges=ccip&fromAddress={YOUR_WALLET_ADDRESS}' +``` diff --git a/docs/README.md b/docs/README.md index 2693eb214..7fea6ef61 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ - [Arbitrum Bridge Facet](./ArbitrumBridgeFacet.md) - [CalldataVerification Facet](./CalldataVerificationFacet.md) - [CBridge Facet](./CBridgeFacet.md) +- [CCIP Facet](./CCIPFacet.md) - [Celer Circle Bridge Facet](./CelerCircleBridgeFacet.md) - [Circle Bridge Facet](./CircleBridgeFacet.md) - [DeBridge Facet](./DeBridgeFacet.md) diff --git a/script/deploy/facets/DeployCCIPFacet.s.sol b/script/deploy/facets/DeployCCIPFacet.s.sol new file mode 100644 index 000000000..e1201987a --- /dev/null +++ b/script/deploy/facets/DeployCCIPFacet.s.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { CCIPFacet } from "lifi/Facets/CCIPFacet.sol"; + +contract DeployScript is DeployScriptBase { + using stdJson for string; + + constructor() DeployScriptBase("CCIPFacet") {} + + function run() + public + returns (CCIPFacet deployed, bytes memory constructorArgs) + { + string memory path = string.concat( + vm.projectRoot(), + "/config/ccip.json" + ); + string memory json = vm.readFile(path); + address example = json.readAddress( + string.concat(".", network, ".example") + ); + + constructorArgs = abi.encode(example); + + vm.startBroadcast(deployerPrivateKey); + + if (isDeployed()) { + return (CCIPFacet(payable(predicted)), constructorArgs); + } + + deployed = CCIPFacet( + payable( + factory.deploy( + salt, + bytes.concat(type(CCIPFacet).creationCode, constructorArgs) + ) + ) + ); + + vm.stopBroadcast(); + } +} diff --git a/script/deploy/facets/UpdateCCIPFacet.s.sol b/script/deploy/facets/UpdateCCIPFacet.s.sol new file mode 100644 index 000000000..4306e821c --- /dev/null +++ b/script/deploy/facets/UpdateCCIPFacet.s.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { DiamondCutFacet, IDiamondCut } from "lifi/Facets/DiamondCutFacet.sol"; +import { CCIPFacet } from "lifi/Facets/CCIPFacet.sol"; + +contract DeployScript is UpdateScriptBase { + using stdJson for string; + + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + address facet = json.readAddress(".CCIPFacet"); + + path = string.concat(root, "/config/ccip.json"); + json = vm.readFile(path); + + address[] memory exampleAllowedTokens = json.readAddressArray( + string.concat(".", network, ".exampleAllowedTokens") + ); + + /// You can remove this if you don't need to call init on the facet + bytes memory callData = abi.encodeWithSelector( + CCIPFacet.initCCIP.selector, + exampleAllowedTokens + ); + + // CCIP + bytes4[] memory exclude; + buildDiamondCut(getSelectors("CCIPFacet", exclude), facet); + if (noBroadcast) { + if (cut.length > 0) { + cutData = abi.encodeWithSelector( + DiamondCutFacet.diamondCut.selector, + cut, + address(facet), // address(0) if not calling init + callData // "" if not calling init + ); + } + return (facets, cutData); + } + + vm.startBroadcast(deployerPrivateKey); + if (cut.length > 0) { + cutter.diamondCut( + cut, + address(facet), // address(0) if not calling init + callData // "" if not calling init + ); + } + facets = loupe.facetAddresses(); + + vm.stopBroadcast(); + } +} diff --git a/src/Facets/CCIPFacet.sol b/src/Facets/CCIPFacet.sol new file mode 100644 index 000000000..382f79a87 --- /dev/null +++ b/src/Facets/CCIPFacet.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { ILiFi } from "../Interfaces/ILiFi.sol"; +import { LibDiamond } from "../Libraries/LibDiamond.sol"; +import { LibAsset } from "../Libraries/LibAsset.sol"; +import { LibSwap } from "../Libraries/LibSwap.sol"; +import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; +import { SwapperV2 } from "../Helpers/SwapperV2.sol"; +import { Validatable } from "../Helpers/Validatable.sol"; + +/// @title CCIP Facet +/// @author Li.Finance (https://li.finance) +/// @notice Allows for bridging assets using Chainlink's CCIP protocol +/// @custom:version 1.0.0 +contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { + /// Storage /// + + bytes32 internal constant NAMESPACE = keccak256("com.lifi.facets.ccip"); // Optional. Only use if you need to store data in the diamond storage. + + /// @dev Local storage for the contract (optional) + struct Storage { + address[] exampleAllowedTokens; + } + + address public immutable example; + + /// Types /// + + /// @dev Optional bridge specific struct + /// @param exampleParam Example paramter + struct CCIPData { + string exampleParam; + } + + /// Events /// + + event CCIPInitialized(); + + /// Constructor /// + + /// @notice Constructor for the contract. + /// Should only be used to set immutable variables. + /// Anything that cannot be set as immutable should be set + /// in an init() function called during a diamondCut(). + /// @param _example Example paramter. + constructor(address _example) { + example = _example; + } + + /// Init /// + + /// @notice Init function. Called in the context + /// of the diamond contract when added as part of + /// a diamondCut(). Use for config that can't be + /// set as immutable or needs to change for any reason. + /// @param _exampleAllowedTokens Example array of allowed tokens for this chain. + function initCCIP(address[] memory _exampleAllowedTokens) external { + LibDiamond.enforceIsContractOwner(); + + Storage storage s = getStorage(); + s.exampleAllowedTokens = _exampleAllowedTokens; + + emit CCIPInitialized(); + } + + /// External Methods /// + + /// @notice Bridges tokens via CCIP + /// @param _bridgeData The core information needed for bridging + /// @param _ccipData Data specific to CCIP + function startBridgeTokensViaCCIP( + ILiFi.BridgeData memory _bridgeData, + CCIPData calldata _ccipData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + validateBridgeData(_bridgeData) + doesNotContainSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + { + LibAsset.depositAsset( + _bridgeData.sendingAssetId, + _bridgeData.minAmount + ); + _startBridge(_bridgeData, _ccipData); + } + + /// @notice Performs a swap before bridging via CCIP + /// @param _bridgeData The core information needed for bridging + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _ccipData Data specific to CCIP + function swapAndStartBridgeTokensViaCCIP( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + CCIPData calldata _ccipData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + containsSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + validateBridgeData(_bridgeData) + { + _bridgeData.minAmount = _depositAndSwap( + _bridgeData.transactionId, + _bridgeData.minAmount, + _swapData, + payable(msg.sender) + ); + _startBridge(_bridgeData, _ccipData); + } + + /// Internal Methods /// + + /// @dev Contains the business logic for the bridge via CCIP + /// @param _bridgeData The core information needed for bridging + /// @param _ccipData Data specific to CCIP + function _startBridge( + ILiFi.BridgeData memory _bridgeData, + CCIPData calldata _ccipData + ) internal { + // TODO: Implement business logic + emit LiFiTransferStarted(_bridgeData); + } + + /// @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/Facets/CCIPFacet.t.sol b/test/solidity/Facets/CCIPFacet.t.sol new file mode 100644 index 000000000..fecaa58c0 --- /dev/null +++ b/test/solidity/Facets/CCIPFacet.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.17; + +import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFacet.sol"; +import { CCIPFacet } from "lifi/Facets/CCIPFacet.sol"; + +// Stub CCIPFacet Contract +contract TestCCIPFacet is CCIPFacet { + constructor(address _example) CCIPFacet(_example) {} + + function addDex(address _dex) external { + LibAllowList.addAllowedContract(_dex); + } + + function setFunctionApprovalBySignature(bytes4 _signature) external { + LibAllowList.addAllowedSelector(_signature); + } +} + +contract CCIPFacetTest is TestBaseFacet { + CCIPFacet.CCIPData internal validCCIPData; + TestCCIPFacet internal ccipFacet; + address internal EXAMPLE_PARAM = address(0xb33f); + + function setUp() public { + customBlockNumberForForking = 17130542; + initTestBase(); + + address[] memory EXAMPLE_ALLOWED_TOKENS = new address[](2); + EXAMPLE_ALLOWED_TOKENS[0] = address(1); + EXAMPLE_ALLOWED_TOKENS[1] = address(2); + + ccipFacet = new TestCCIPFacet(EXAMPLE_PARAM); + ccipFacet.initCCIP(EXAMPLE_ALLOWED_TOKENS); + bytes4[] memory functionSelectors = new bytes4[](4); + functionSelectors[0] = ccipFacet.startBridgeTokensViaCCIP.selector; + functionSelectors[1] = ccipFacet + .swapAndStartBridgeTokensViaCCIP + .selector; + functionSelectors[2] = ccipFacet.addDex.selector; + functionSelectors[3] = ccipFacet + .setFunctionApprovalBySignature + .selector; + + addFacet(diamond, address(ccipFacet), functionSelectors); + ccipFacet = TestCCIPFacet(address(diamond)); + ccipFacet.addDex(ADDRESS_UNISWAP); + ccipFacet.setFunctionApprovalBySignature( + uniswap.swapExactTokensForTokens.selector + ); + ccipFacet.setFunctionApprovalBySignature( + uniswap.swapTokensForExactETH.selector + ); + ccipFacet.setFunctionApprovalBySignature( + uniswap.swapETHForExactTokens.selector + ); + + setFacetAddressInTestBase(address(ccipFacet), "CCIPFacet"); + + // adjust bridgeData + bridgeData.bridge = "ccip"; + bridgeData.destinationChainId = 137; + + // produce valid CCIPData + validCCIPData = CCIPFacet.CCIPData({ exampleParam: "foo bar baz" }); + } + + // All facet test files inherit from `utils/TestBaseFacet.sol` and require the following method overrides: + // - function initiateBridgeTxWithFacet(bool isNative) + // - function initiateSwapAndBridgeTxWithFacet(bool isNative) + // + // These methods are used to run the following tests which must pass: + // - testBase_CanBridgeNativeTokens() + // - testBase_CanBridgeTokens() + // - testBase_CanBridgeTokens_fuzzed(uint256) + // - testBase_CanSwapAndBridgeNativeTokens() + // - testBase_CanSwapAndBridgeTokens() + // - testBase_Revert_BridgeAndSwapWithInvalidReceiverAddress() + // - testBase_Revert_BridgeToSameChainId() + // - testBase_Revert_BridgeWithInvalidAmount() + // - testBase_Revert_BridgeWithInvalidDestinationCallFlag() + // - testBase_Revert_BridgeWithInvalidReceiverAddress() + // - testBase_Revert_CallBridgeOnlyFunctionWithSourceSwapFlag() + // - testBase_Revert_CallerHasInsufficientFunds() + // - testBase_Revert_SwapAndBridgeToSameChainId() + // - testBase_Revert_SwapAndBridgeWithInvalidAmount() + // - testBase_Revert_SwapAndBridgeWithInvalidSwapData() + // + // In some cases it doesn't make sense to have all tests. For example the bridge may not support native tokens. + // In that case you can override the test method and leave it empty. For example: + // + // function testBase_CanBridgeNativeTokens() public override { + // // facet does not support bridging of native assets + // } + // + // function testBase_CanSwapAndBridgeNativeTokens() public override { + // // facet does not support bridging of native assets + // } + + function initiateBridgeTxWithFacet(bool isNative) internal override { + if (isNative) { + ccipFacet.startBridgeTokensViaCCIP{ value: bridgeData.minAmount }( + bridgeData, + validCCIPData + ); + } else { + ccipFacet.startBridgeTokensViaCCIP(bridgeData, validCCIPData); + } + } + + function initiateSwapAndBridgeTxWithFacet( + bool isNative + ) internal override { + if (isNative) { + ccipFacet.swapAndStartBridgeTokensViaCCIP{ + value: swapData[0].fromAmount + }(bridgeData, swapData, validCCIPData); + } else { + ccipFacet.swapAndStartBridgeTokensViaCCIP( + bridgeData, + swapData, + validCCIPData + ); + } + } +} From a4aeab12eb996ea65d1748c7a1a6991f37e5cba4 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 20 Sep 2023 12:53:19 +0300 Subject: [PATCH 02/20] Add configuration --- config/ccip.json | 67 ++++++++++++++++++++++++++++++++-------- package.json | 1 + remappings.txt | 2 +- src/Facets/CCIPFacet.sol | 17 +++++----- yarn.lock | 34 ++++++++++++++++++++ 5 files changed, 100 insertions(+), 21 deletions(-) diff --git a/config/ccip.json b/config/ccip.json index bcf06aa51..92c921946 100644 --- a/config/ccip.json +++ b/config/ccip.json @@ -1,16 +1,57 @@ { - "mainnet": { - "example": "0x0000000000000000000000000000000000000000", - "exampleAllowedTokens": [ - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ] + "routers": { + "mainnet": { + "router": "0xE561d5E02207fb5eB32cca20a699E0d8919a1476" + }, + "sepolia": { + "router": "0xD0daae2231E9CB96b94C8512223533293C3693Bf" + }, + "optimism": { + "router": "0x261c05167db67B2b619f9d312e0753f3721ad6E8" + }, + "avalanche": { + "router": "0x27F39D0af3303703750D4001fCc1844c6491563c" + }, + "mumbai": { + "router": "0x70499c328e1E2a3c41108bd3730F6670a44595D1" + }, + "polygon": { + "router": "0x3C3D92629A02a8D95D5CB9650fe49C3544f69B43" + }, + "bsc-testnet": { + "router": "0x9527e2d01a3064ef6b50c1da1c0cc523803bcff2" + } }, - "arbitrum": { - "example": "0x0000000000000000000000000000000000000000", - "exampleAllowedTokens": [ - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ] - } + "chainSelectors": [ + { + "chainId": 1, + "selector": 5009297550715157269 + }, + { + "chainId": 10, + "selector": 3734403246176062136 + }, + { + "chainId": 43114, + "selector": 6433500567565415381 + }, + { + "chainId": 137, + "selector": 4051577828743386545 + } + ], + "testChainSelectors": [ + { + "chainId": 11155111, + "selector": 16015286601757825753 + }, + { + "chainId": 80001, + "selector": 12532609583862916517 + }, + { + "chainId": 97, + "selector": 13264668187771770619 + } + ] } diff --git a/package.json b/package.json index 4a75a72f0..8ac08320d 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ }, "dependencies": { "@arbitrum/sdk": "^3.0.0", + "@chainlink/contracts-ccip": "^0.7.6", "@hop-protocol/sdk": "0.0.1-beta.310", "@safe-global/api-kit": "^1.1.0", "@safe-global/protocol-kit": "^1.0.1", diff --git a/remappings.txt b/remappings.txt index a933e8bf9..da4422629 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,8 +3,8 @@ eth-gas-reporter/=node_modules/eth-gas-reporter/ hardhat/=node_modules/hardhat/ hardhat-deploy/=node_modules/hardhat-deploy/ - @openzeppelin/=lib/openzeppelin-contracts/ +@chainlink-ccip/=node_modules/@chainlink/contracts-ccip/src/ celer-network/=lib/sgn-v2-contracts/ create3-factory/=lib/create3-factory/src/ solmate/=lib/solmate/src/ diff --git a/src/Facets/CCIPFacet.sol b/src/Facets/CCIPFacet.sol index 382f79a87..8b3146b7a 100644 --- a/src/Facets/CCIPFacet.sol +++ b/src/Facets/CCIPFacet.sol @@ -8,6 +8,8 @@ import { LibSwap } from "../Libraries/LibSwap.sol"; import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; import { SwapperV2 } from "../Helpers/SwapperV2.sol"; import { Validatable } from "../Helpers/Validatable.sol"; +import { Client } from "@chainlink-ccip/v0.8/ccip/libraries/Client.sol"; +import { IRouterClient } from "@chainlink-ccip/v0.8/ccip/interfaces/IRouterClient.sol"; /// @title CCIP Facet /// @author Li.Finance (https://li.finance) @@ -18,6 +20,9 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { bytes32 internal constant NAMESPACE = keccak256("com.lifi.facets.ccip"); // Optional. Only use if you need to store data in the diamond storage. + // @notice the CCIP router contract + IRouterClient public immutable routerClient; + /// @dev Local storage for the contract (optional) struct Storage { address[] exampleAllowedTokens; @@ -30,7 +35,8 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// @dev Optional bridge specific struct /// @param exampleParam Example paramter struct CCIPData { - string exampleParam; + bytes callData; + bytes extraArgs; } /// Events /// @@ -40,12 +46,9 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Constructor /// /// @notice Constructor for the contract. - /// Should only be used to set immutable variables. - /// Anything that cannot be set as immutable should be set - /// in an init() function called during a diamondCut(). - /// @param _example Example paramter. - constructor(address _example) { - example = _example; + /// @param _routerClient CCIP router contract. + constructor(IRouterClient _routerClient) { + routerClient = _routerClient; } /// Init /// diff --git a/yarn.lock b/yarn.lock index 74cdafcc4..5e8207d8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,16 @@ resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== +"@chainlink/contracts-ccip@^0.7.6": + version "0.7.6" + resolved "https://registry.yarnpkg.com/@chainlink/contracts-ccip/-/contracts-ccip-0.7.6.tgz#5bf4568a0bbf4e29d2e8c32348e5ecc6ced006d2" + integrity sha512-yNbCBFpLs3R+ALymto9dQYKz3vatnjqYGu1pnMD0i2fHEMthiXe0+otaNCGNht6n8k7ruNaA0DNpz3F+2jHQXw== + dependencies: + "@eth-optimism/contracts" "^0.5.21" + "@openzeppelin/contracts" "~4.3.3" + "@openzeppelin/contracts-upgradeable-4.7.3" "npm:@openzeppelin/contracts-upgradeable@v4.7.3" + "@openzeppelin/contracts-v0.7" "npm:@openzeppelin/contracts@v3.4.2" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -69,6 +79,15 @@ "@ethersproject/abstract-provider" "^5.7.0" "@ethersproject/abstract-signer" "^5.7.0" +"@eth-optimism/contracts@^0.5.21": + version "0.5.40" + resolved "https://registry.yarnpkg.com/@eth-optimism/contracts/-/contracts-0.5.40.tgz#d13a04a15ea947a69055e6fc74d87e215d4c936a" + integrity sha512-MrzV0nvsymfO/fursTB7m/KunkPsCndltVgfdHaT1Aj5Vi6R/doKIGGkOofHX+8B6VMZpuZosKCMQ5lQuqjt8w== + dependencies: + "@eth-optimism/core-utils" "0.12.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@eth-optimism/core-utils@0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@eth-optimism/core-utils/-/core-utils-0.12.0.tgz#6337e4599a34de23f8eceb20378de2a2de82b0ea" @@ -920,6 +939,21 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.5.tgz#97c217f1db795395c04404291937edb528f3f218" integrity sha512-U1RH9OQ1mWYQfb+moX5aTgGjpVVlOcpiFI47wwnaGG4kLhcTy90cNiapoqZenxcRAITVbr0/+QSduINL5EsUIQ== +"@openzeppelin/contracts-upgradeable-4.7.3@npm:@openzeppelin/contracts-upgradeable@v4.7.3": + version "4.7.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.7.3.tgz#f1d606e2827d409053f3e908ba4eb8adb1dd6995" + integrity sha512-+wuegAMaLcZnLCJIvrVUDzA9z/Wp93f0Dla/4jJvIhijRrPabjQbZe6fWiECLaJyfn5ci9fqf9vTw3xpQOad2A== + +"@openzeppelin/contracts-v0.7@npm:@openzeppelin/contracts@v3.4.2": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" + integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA== + +"@openzeppelin/contracts@~4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.3.tgz#ff6ee919fc2a1abaf72b22814bfb72ed129ec137" + integrity sha512-tDBopO1c98Yk7Cv/PZlHqrvtVjlgK5R4J6jxLwoO7qxK4xqOiZG+zSkIvGFpPZ0ikc3QOED3plgdqjgNTnBc7g== + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" From 77e78effd8347d55ab292f1c5e8cbc8013bc206b Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 22 Sep 2023 11:43:15 +0300 Subject: [PATCH 03/20] Implement CCIP business logic --- src/Facets/CCIPFacet.sol | 90 ++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/src/Facets/CCIPFacet.sol b/src/Facets/CCIPFacet.sol index 8b3146b7a..d8a8d6fcc 100644 --- a/src/Facets/CCIPFacet.sol +++ b/src/Facets/CCIPFacet.sol @@ -23,13 +23,6 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { // @notice the CCIP router contract IRouterClient public immutable routerClient; - /// @dev Local storage for the contract (optional) - struct Storage { - address[] exampleAllowedTokens; - } - - address public immutable example; - /// Types /// /// @dev Optional bridge specific struct @@ -39,9 +32,25 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { bytes extraArgs; } + /// @dev Local storage layout for CCIP + struct Storage { + mapping(uint256 => uint64) chainSelectors; + } + + struct ChainSelector { + uint256 chainId; + uint64 selector; + } + + /// Errors /// + + error UnknownCCIPChainSelector(); + /// Events /// - event CCIPInitialized(); + event CCIPInitialized(ChainSelector[] chainSelectors); + + event CCIPChainSelectorUpdated(uint256 indexed chainId, uint64 selector); /// Constructor /// @@ -53,18 +62,19 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Init /// - /// @notice Init function. Called in the context - /// of the diamond contract when added as part of - /// a diamondCut(). Use for config that can't be - /// set as immutable or needs to change for any reason. - /// @param _exampleAllowedTokens Example array of allowed tokens for this chain. - function initCCIP(address[] memory _exampleAllowedTokens) external { + /// @notice Initializes the CCIP facet. + /// @param chainSelectors An array of chain selectors for CCIP + function initCCIP(ChainSelector[] calldata chainSelectors) external { LibDiamond.enforceIsContractOwner(); Storage storage s = getStorage(); - s.exampleAllowedTokens = _exampleAllowedTokens; - emit CCIPInitialized(); + for (uint256 i = 0; i < chainSelectors.length; i++) { + s.chainSelectors[chainSelectors[i].chainId] = chainSelectors[i] + .selector; + } + + emit CCIPInitialized(chainSelectors); } /// External Methods /// @@ -126,10 +136,56 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { ILiFi.BridgeData memory _bridgeData, CCIPData calldata _ccipData ) internal { - // TODO: Implement business logic + Client.EVMTokenAmount[] memory amounts = new Client.EVMTokenAmount[]( + 1 + ); + amounts[0] = Client.EVMTokenAmount({ + token: _bridgeData.sendingAssetId, + amount: _bridgeData.minAmount + }); + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(_bridgeData), + data: _ccipData.callData, + tokenAmounts: amounts, + feeToken: address(0), + extraArgs: _ccipData.extraArgs + }); + + routerClient.ccipSend( + getCCIPChainSelector(_bridgeData.destinationChainId), + message + ); emit LiFiTransferStarted(_bridgeData); } + /// @notice Sets the CCIP chain selector for a given chain ID + /// @param _chainId Standard chain ID + /// @param _selector CCIP specific chain selector + /// @dev This is used to map a chain ID to its CCIP chain selector + function setCCIPChainSelector( + uint256 _chainId, + uint64 _selector + ) external { + LibDiamond.enforceIsContractOwner(); + Storage storage s = getStorage(); + + s.chainSelectors[_chainId] = _selector; + emit CCIPChainSelectorUpdated(_chainId, _selector); + } + + /// @notice Gets the CCIP chain selector for a given chain ID + /// @param _chainId Standard chain ID + /// @return selector CCIP specific chain selector + function getCCIPChainSelector( + uint256 _chainId + ) private view returns (uint64) { + Storage storage s = getStorage(); + uint64 selector = s.chainSelectors[_chainId]; + if (selector == 0) revert UnknownCCIPChainSelector(); + return selector; + } + /// @dev fetch local storage function getStorage() private pure returns (Storage storage s) { bytes32 namespace = NAMESPACE; From 3dfae65bc08d41632f37ebbf6a825de1c7aaafa0 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 25 Sep 2023 11:02:48 +0300 Subject: [PATCH 04/20] Start implementing tests --- test/solidity/Facets/CCIPFacet.t.sol | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/test/solidity/Facets/CCIPFacet.t.sol b/test/solidity/Facets/CCIPFacet.t.sol index fecaa58c0..8caad5e47 100644 --- a/test/solidity/Facets/CCIPFacet.t.sol +++ b/test/solidity/Facets/CCIPFacet.t.sol @@ -3,10 +3,11 @@ pragma solidity 0.8.17; import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFacet.sol"; import { CCIPFacet } from "lifi/Facets/CCIPFacet.sol"; +import { IRouterClient } from "@chainlink-ccip/v0.8/ccip/interfaces/IRouterClient.sol"; // Stub CCIPFacet Contract contract TestCCIPFacet is CCIPFacet { - constructor(address _example) CCIPFacet(_example) {} + constructor(IRouterClient _routerClient) CCIPFacet(_routerClient) {} function addDex(address _dex) external { LibAllowList.addAllowedContract(_dex); @@ -20,18 +21,14 @@ contract TestCCIPFacet is CCIPFacet { contract CCIPFacetTest is TestBaseFacet { CCIPFacet.CCIPData internal validCCIPData; TestCCIPFacet internal ccipFacet; - address internal EXAMPLE_PARAM = address(0xb33f); + IRouterClient internal ROUTER_CLIENT = + IRouterClient(0x9527E2d01A3064ef6b50c1Da1C0cC523803BCFF2); function setUp() public { - customBlockNumberForForking = 17130542; + customBlockNumberForForking = 32915829; initTestBase(); - address[] memory EXAMPLE_ALLOWED_TOKENS = new address[](2); - EXAMPLE_ALLOWED_TOKENS[0] = address(1); - EXAMPLE_ALLOWED_TOKENS[1] = address(2); - - ccipFacet = new TestCCIPFacet(EXAMPLE_PARAM); - ccipFacet.initCCIP(EXAMPLE_ALLOWED_TOKENS); + ccipFacet = new TestCCIPFacet(ROUTER_CLIENT); bytes4[] memory functionSelectors = new bytes4[](4); functionSelectors[0] = ccipFacet.startBridgeTokensViaCCIP.selector; functionSelectors[1] = ccipFacet @@ -62,7 +59,7 @@ contract CCIPFacetTest is TestBaseFacet { bridgeData.destinationChainId = 137; // produce valid CCIPData - validCCIPData = CCIPFacet.CCIPData({ exampleParam: "foo bar baz" }); + validCCIPData = CCIPFacet.CCIPData({ callData: "", extraArgs: "" }); } // All facet test files inherit from `utils/TestBaseFacet.sol` and require the following method overrides: From fcd85ef3476631ad1f2b8c360b40dbd8f6d4d262 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 25 Sep 2023 14:07:06 +0300 Subject: [PATCH 05/20] Formatting --- test/solidity/Facets/CCIPFacet.t.sol | 272 +++++++++++++++++++-------- 1 file changed, 193 insertions(+), 79 deletions(-) diff --git a/test/solidity/Facets/CCIPFacet.t.sol b/test/solidity/Facets/CCIPFacet.t.sol index 8caad5e47..fcfa82890 100644 --- a/test/solidity/Facets/CCIPFacet.t.sol +++ b/test/solidity/Facets/CCIPFacet.t.sol @@ -1,9 +1,18 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFacet.sol"; +import { DSTest } from "ds-test/test.sol"; +import { DiamondTest, LiFiDiamond } from "../utils/DiamondTest.sol"; +import { Vm } from "forge-std/Vm.sol"; +import { Test } from "forge-std/Test.sol"; import { CCIPFacet } from "lifi/Facets/CCIPFacet.sol"; +import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; +import { LibSwap } from "lifi/Libraries/LibSwap.sol"; +import { LibAllowList } from "lifi/Libraries/LibAllowList.sol"; +import { ERC20 } from "solmate/tokens/ERC20.sol"; +import { UniswapV2Router02 } from "../utils/Interfaces.sol"; import { IRouterClient } from "@chainlink-ccip/v0.8/ccip/interfaces/IRouterClient.sol"; +import "lifi/Errors/GenericErrors.sol"; // Stub CCIPFacet Contract contract TestCCIPFacet is CCIPFacet { @@ -18,106 +27,211 @@ contract TestCCIPFacet is CCIPFacet { } } -contract CCIPFacetTest is TestBaseFacet { - CCIPFacet.CCIPData internal validCCIPData; +contract FooSwap is Test { + function swap( + ERC20 inToken, + ERC20 outToken, + uint256 inAmount, + uint256 outAmount + ) external { + inToken.transferFrom(msg.sender, address(this), inAmount); + deal(address(outToken), msg.sender, outAmount); + } +} + +contract CCIPFacetTest is Test, DiamondTest { + // These values are for BSC Testnet + address internal constant USDC_ADDRESS = + 0x64544969ed7EBf5f083679233325356EbE738930; + address internal constant CCIP_TEST_TOKEN_ADDRESS = + 0x79a4Fc27f69323660f5Bfc12dEe21c3cC14f5901; // CCIP Burn & Mint Test Token + address internal constant ROUTER_CLIENT = + 0x677311Fd2cCc511Bbc0f581E8d9a07B033D5E840; + uint256 internal constant DSTCHAIN_ID = 11155111; // Sepolia + + // ----- + + LiFiDiamond internal diamond; TestCCIPFacet internal ccipFacet; - IRouterClient internal ROUTER_CLIENT = - IRouterClient(0x9527E2d01A3064ef6b50c1Da1C0cC523803BCFF2); + ERC20 internal usdc; + ERC20 internal ccipTestToken; + ILiFi.BridgeData internal validBridgeData; + CCIPFacet.CCIPData internal validCCIPData; + FooSwap internal fooSwap; + + function fork() internal { + string memory rpcUrl = vm.envString("ETH_NODE_URI_BSC_TESTNET"); + uint256 blockNumber = 33259557; + vm.createSelectFork(rpcUrl, blockNumber); + } function setUp() public { - customBlockNumberForForking = 32915829; - initTestBase(); + fork(); - ccipFacet = new TestCCIPFacet(ROUTER_CLIENT); - bytes4[] memory functionSelectors = new bytes4[](4); + diamond = createDiamond(); + ccipFacet = new TestCCIPFacet(IRouterClient(ROUTER_CLIENT)); + ccipTestToken = ERC20(CCIP_TEST_TOKEN_ADDRESS); + usdc = ERC20(USDC_ADDRESS); + fooSwap = new FooSwap(); + + bytes4[] memory functionSelectors = new bytes4[](5); functionSelectors[0] = ccipFacet.startBridgeTokensViaCCIP.selector; functionSelectors[1] = ccipFacet .swapAndStartBridgeTokensViaCCIP .selector; - functionSelectors[2] = ccipFacet.addDex.selector; - functionSelectors[3] = ccipFacet + functionSelectors[2] = ccipFacet.initCCIP.selector; + functionSelectors[3] = ccipFacet.addDex.selector; + functionSelectors[4] = ccipFacet .setFunctionApprovalBySignature .selector; addFacet(diamond, address(ccipFacet), functionSelectors); + + CCIPFacet.ChainSelector[] + memory configs = new CCIPFacet.ChainSelector[](2); + configs[0] = CCIPFacet.ChainSelector(97, 13264668187771770619); // BSC Testnet + configs[1] = CCIPFacet.ChainSelector(11155111, 16015286601757825753); // Sepolia + ccipFacet = TestCCIPFacet(address(diamond)); - ccipFacet.addDex(ADDRESS_UNISWAP); - ccipFacet.setFunctionApprovalBySignature( - uniswap.swapExactTokensForTokens.selector + ccipFacet.initCCIP(configs); + + ccipFacet.addDex(address(fooSwap)); + ccipFacet.setFunctionApprovalBySignature(fooSwap.swap.selector); + + vm.label(CCIP_TEST_TOKEN_ADDRESS, "CCIP Test Token"); + vm.label(USDC_ADDRESS, "USDC Token"); + vm.label(ROUTER_CLIENT, "CCIP Router"); + + validBridgeData = ILiFi.BridgeData({ + transactionId: "ccipId", + bridge: "ccip", + integrator: "", + referrer: address(0), + sendingAssetId: CCIP_TEST_TOKEN_ADDRESS, + receiver: address(this), + minAmount: 10 * 10 ** ccipTestToken.decimals(), + destinationChainId: DSTCHAIN_ID, + hasSourceSwaps: false, + hasDestinationCall: false + }); + validCCIPData = CCIPFacet.CCIPData("", ""); + } + + function testRevertToBridgeTokensWhenSendingAmountIsZero() public { + deal( + address(ccipTestToken), + address(this), + 10 * 10 ** ccipTestToken.decimals() + ); + + ccipTestToken.approve( + address(ccipFacet), + 10_000 * 10 ** ccipTestToken.decimals() + ); + + ILiFi.BridgeData memory bridgeData = validBridgeData; + bridgeData.minAmount = 0; + + vm.expectRevert(InvalidAmount.selector); + ccipFacet.startBridgeTokensViaCCIP(bridgeData, validCCIPData); + } + + function testRevertToBridgeTokensWhenReceiverIsZeroAddress() public { + deal( + address(ccipTestToken), + address(this), + 10 * 10 ** ccipTestToken.decimals() ); - ccipFacet.setFunctionApprovalBySignature( - uniswap.swapTokensForExactETH.selector + + ccipTestToken.approve( + address(ccipFacet), + 10_000 * 10 ** ccipTestToken.decimals() ); - ccipFacet.setFunctionApprovalBySignature( - uniswap.swapETHForExactTokens.selector + + ILiFi.BridgeData memory bridgeData = validBridgeData; + bridgeData.receiver = address(0); + + vm.expectRevert(InvalidReceiver.selector); + ccipFacet.startBridgeTokensViaCCIP(bridgeData, validCCIPData); + } + + function testRevertToBridgeTokensWhenInformationMismatch() public { + deal( + address(ccipTestToken), + address(this), + 10 * 10 ** ccipTestToken.decimals() ); - setFacetAddressInTestBase(address(ccipFacet), "CCIPFacet"); + ccipTestToken.approve( + address(ccipFacet), + 10_000 * 10 ** ccipTestToken.decimals() + ); - // adjust bridgeData - bridgeData.bridge = "ccip"; - bridgeData.destinationChainId = 137; + ILiFi.BridgeData memory bridgeData = validBridgeData; + bridgeData.hasSourceSwaps = true; - // produce valid CCIPData - validCCIPData = CCIPFacet.CCIPData({ callData: "", extraArgs: "" }); + vm.expectRevert(InformationMismatch.selector); + ccipFacet.startBridgeTokensViaCCIP(bridgeData, validCCIPData); } - // All facet test files inherit from `utils/TestBaseFacet.sol` and require the following method overrides: - // - function initiateBridgeTxWithFacet(bool isNative) - // - function initiateSwapAndBridgeTxWithFacet(bool isNative) - // - // These methods are used to run the following tests which must pass: - // - testBase_CanBridgeNativeTokens() - // - testBase_CanBridgeTokens() - // - testBase_CanBridgeTokens_fuzzed(uint256) - // - testBase_CanSwapAndBridgeNativeTokens() - // - testBase_CanSwapAndBridgeTokens() - // - testBase_Revert_BridgeAndSwapWithInvalidReceiverAddress() - // - testBase_Revert_BridgeToSameChainId() - // - testBase_Revert_BridgeWithInvalidAmount() - // - testBase_Revert_BridgeWithInvalidDestinationCallFlag() - // - testBase_Revert_BridgeWithInvalidReceiverAddress() - // - testBase_Revert_CallBridgeOnlyFunctionWithSourceSwapFlag() - // - testBase_Revert_CallerHasInsufficientFunds() - // - testBase_Revert_SwapAndBridgeToSameChainId() - // - testBase_Revert_SwapAndBridgeWithInvalidAmount() - // - testBase_Revert_SwapAndBridgeWithInvalidSwapData() - // - // In some cases it doesn't make sense to have all tests. For example the bridge may not support native tokens. - // In that case you can override the test method and leave it empty. For example: - // - // function testBase_CanBridgeNativeTokens() public override { - // // facet does not support bridging of native assets - // } - // - // function testBase_CanSwapAndBridgeNativeTokens() public override { - // // facet does not support bridging of native assets - // } - - function initiateBridgeTxWithFacet(bool isNative) internal override { - if (isNative) { - ccipFacet.startBridgeTokensViaCCIP{ value: bridgeData.minAmount }( - bridgeData, - validCCIPData - ); - } else { - ccipFacet.startBridgeTokensViaCCIP(bridgeData, validCCIPData); - } + function testCanBridgeERC20Tokens() public { + deal( + address(ccipTestToken), + address(this), + 10 * 10 ** ccipTestToken.decimals() + ); + + ccipTestToken.approve( + address(ccipFacet), + 10_000 * 10 ** ccipTestToken.decimals() + ); + + ccipFacet.startBridgeTokensViaCCIP(validBridgeData, validCCIPData); } - function initiateSwapAndBridgeTxWithFacet( - bool isNative - ) internal override { - if (isNative) { - ccipFacet.swapAndStartBridgeTokensViaCCIP{ - value: swapData[0].fromAmount - }(bridgeData, swapData, validCCIPData); - } else { - ccipFacet.swapAndStartBridgeTokensViaCCIP( - bridgeData, - swapData, - validCCIPData - ); - } + function testCanSwapAndBridgeTokens() public { + usdc.approve(address(ccipFacet), 10_000 * 10 ** usdc.decimals()); + deal( + address(ccipTestToken), + address(this), + 10 * 10 ** ccipTestToken.decimals() + ); + + ccipTestToken.approve( + address(ccipFacet), + 10_000 * 10 ** ccipTestToken.decimals() + ); + + uint256 inAmount = 10_000 * 10 ** usdc.decimals(); + uint256 outAmount = 10_000 * 10 ** ccipTestToken.decimals(); + + // Calculate DAI amount + LibSwap.SwapData[] memory swapData = new LibSwap.SwapData[](1); + swapData[0] = LibSwap.SwapData( + address(fooSwap), + address(fooSwap), + USDC_ADDRESS, + CCIP_TEST_TOKEN_ADDRESS, + inAmount, + abi.encodeWithSelector( + fooSwap.swap.selector, + ERC20(USDC_ADDRESS), + ERC20(CCIP_TEST_TOKEN_ADDRESS), + inAmount, + outAmount + ), + true + ); + + ILiFi.BridgeData memory bridgeData = validBridgeData; + bridgeData.hasSourceSwaps = true; + + ccipFacet.swapAndStartBridgeTokensViaCCIP( + bridgeData, + swapData, + validCCIPData + ); + + vm.stopPrank(); } } From 0ec4b5a1fff4d53fdebd5c241ee6150d4b4e4a01 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 25 Sep 2023 17:24:55 +0300 Subject: [PATCH 06/20] Update tests --- src/Facets/CCIPFacet.sol | 12 +++++++++--- test/solidity/Facets/CCIPFacet.t.sol | 22 +++++++--------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Facets/CCIPFacet.sol b/src/Facets/CCIPFacet.sol index d8a8d6fcc..3e0ea97ec 100644 --- a/src/Facets/CCIPFacet.sol +++ b/src/Facets/CCIPFacet.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.17; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { LibDiamond } from "../Libraries/LibDiamond.sol"; -import { LibAsset } from "../Libraries/LibAsset.sol"; +import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol"; import { LibSwap } from "../Libraries/LibSwap.sol"; import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; import { SwapperV2 } from "../Helpers/SwapperV2.sol"; @@ -145,14 +145,20 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(_bridgeData), + receiver: abi.encode(_bridgeData.receiver), data: _ccipData.callData, tokenAmounts: amounts, feeToken: address(0), extraArgs: _ccipData.extraArgs }); - routerClient.ccipSend( + LibAsset.maxApproveERC20( + IERC20(_bridgeData.sendingAssetId), + address(routerClient), + _bridgeData.minAmount + ); + + routerClient.ccipSend{ value: msg.value }( getCCIPChainSelector(_bridgeData.destinationChainId), message ); diff --git a/test/solidity/Facets/CCIPFacet.t.sol b/test/solidity/Facets/CCIPFacet.t.sol index fcfa82890..0cd7bb5ed 100644 --- a/test/solidity/Facets/CCIPFacet.t.sol +++ b/test/solidity/Facets/CCIPFacet.t.sol @@ -46,7 +46,7 @@ contract CCIPFacetTest is Test, DiamondTest { address internal constant CCIP_TEST_TOKEN_ADDRESS = 0x79a4Fc27f69323660f5Bfc12dEe21c3cC14f5901; // CCIP Burn & Mint Test Token address internal constant ROUTER_CLIENT = - 0x677311Fd2cCc511Bbc0f581E8d9a07B033D5E840; + 0x9527E2d01A3064ef6b50c1Da1C0cC523803BCFF2; uint256 internal constant DSTCHAIN_ID = 11155111; // Sepolia // ----- @@ -186,21 +186,15 @@ contract CCIPFacetTest is Test, DiamondTest { 10_000 * 10 ** ccipTestToken.decimals() ); - ccipFacet.startBridgeTokensViaCCIP(validBridgeData, validCCIPData); + ccipFacet.startBridgeTokensViaCCIP{ value: 0.1 ether }( + validBridgeData, + validCCIPData + ); } function testCanSwapAndBridgeTokens() public { + deal(address(usdc), address(this), 10_000 * 10 ** usdc.decimals()); usdc.approve(address(ccipFacet), 10_000 * 10 ** usdc.decimals()); - deal( - address(ccipTestToken), - address(this), - 10 * 10 ** ccipTestToken.decimals() - ); - - ccipTestToken.approve( - address(ccipFacet), - 10_000 * 10 ** ccipTestToken.decimals() - ); uint256 inAmount = 10_000 * 10 ** usdc.decimals(); uint256 outAmount = 10_000 * 10 ** ccipTestToken.decimals(); @@ -226,12 +220,10 @@ contract CCIPFacetTest is Test, DiamondTest { ILiFi.BridgeData memory bridgeData = validBridgeData; bridgeData.hasSourceSwaps = true; - ccipFacet.swapAndStartBridgeTokensViaCCIP( + ccipFacet.swapAndStartBridgeTokensViaCCIP{ value: 0.1 ether }( bridgeData, swapData, validCCIPData ); - - vm.stopPrank(); } } From 657c5d87fc3c8ae8a55f8cef06d4d7609067b811 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 29 Sep 2023 13:41:20 +0300 Subject: [PATCH 07/20] Add convenience functions --- src/Facets/CCIPFacet.sol | 63 +++++++++++++++++++- test/solidity/Facets/CCIPFacet.t.sol | 89 +++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 6 deletions(-) diff --git a/src/Facets/CCIPFacet.sol b/src/Facets/CCIPFacet.sol index 3e0ea97ec..45e3e931a 100644 --- a/src/Facets/CCIPFacet.sol +++ b/src/Facets/CCIPFacet.sol @@ -10,11 +10,12 @@ import { SwapperV2 } from "../Helpers/SwapperV2.sol"; import { Validatable } from "../Helpers/Validatable.sol"; import { Client } from "@chainlink-ccip/v0.8/ccip/libraries/Client.sol"; import { IRouterClient } from "@chainlink-ccip/v0.8/ccip/interfaces/IRouterClient.sol"; +import { InformationMismatch } from "../Errors/GenericErrors.sol"; /// @title CCIP Facet /// @author Li.Finance (https://li.finance) /// @notice Allows for bridging assets using Chainlink's CCIP protocol -/// @custom:version 1.0.0 +/// @custom:version 0.0.1 contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Storage /// @@ -92,8 +93,8 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { refundExcessNative(payable(msg.sender)) validateBridgeData(_bridgeData) doesNotContainSourceSwaps(_bridgeData) - doesNotContainDestinationCalls(_bridgeData) { + validateDestinationCallFlag(_bridgeData, _ccipData); LibAsset.depositAsset( _bridgeData.sendingAssetId, _bridgeData.minAmount @@ -115,9 +116,9 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { nonReentrant refundExcessNative(payable(msg.sender)) containsSourceSwaps(_bridgeData) - doesNotContainDestinationCalls(_bridgeData) validateBridgeData(_bridgeData) { + validateDestinationCallFlag(_bridgeData, _ccipData); _bridgeData.minAmount = _depositAndSwap( _bridgeData.transactionId, _bridgeData.minAmount, @@ -127,6 +128,50 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { _startBridge(_bridgeData, _ccipData); } + /// @notice Quotes the fee for bridging via CCIP + /// @param _bridgeData The core information needed for bridging + /// @param _ccipData Data specific to CCIP + function quoteCCIPFee( + ILiFi.BridgeData memory _bridgeData, + CCIPData calldata _ccipData + ) external view returns (uint256) { + Client.EVMTokenAmount[] memory amounts = new Client.EVMTokenAmount[]( + 1 + ); + amounts[0] = Client.EVMTokenAmount({ + token: _bridgeData.sendingAssetId, + amount: _bridgeData.minAmount + }); + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(_bridgeData.receiver), + data: _ccipData.callData, + tokenAmounts: amounts, + feeToken: address(0), + extraArgs: _ccipData.extraArgs + }); + + return + routerClient.getFee( + getCCIPChainSelector(_bridgeData.destinationChainId), + message + ); + } + + /// @notice Encodes the extra arguments for the destination call + /// @param gasLimit The gas limit for the destination call + /// @param strictSequencing Whether or not to use strict sequencing (see https://docs.chain.link/ccip/best-practices#sequencing) + function encodeDestinationArgs( + uint256 gasLimit, + bool strictSequencing + ) external pure returns (bytes memory) { + Client.EVMExtraArgsV1 memory args = Client.EVMExtraArgsV1({ + gasLimit: gasLimit, + strict: strictSequencing + }); + return Client._argsToBytes(args); + } + /// Internal Methods /// /// @dev Contains the business logic for the bridge via CCIP @@ -192,6 +237,18 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { return selector; } + /// @dev Validates the destination call flag + function validateDestinationCallFlag( + ILiFi.BridgeData memory _bridgeData, + CCIPData calldata _ccipData + ) private pure { + if ( + (_ccipData.callData.length > 0) != _bridgeData.hasDestinationCall + ) { + revert InformationMismatch(); + } + } + /// @dev fetch local storage function getStorage() private pure returns (Storage storage s) { bytes32 namespace = NAMESPACE; diff --git a/test/solidity/Facets/CCIPFacet.t.sol b/test/solidity/Facets/CCIPFacet.t.sol index 0cd7bb5ed..72635eb8a 100644 --- a/test/solidity/Facets/CCIPFacet.t.sol +++ b/test/solidity/Facets/CCIPFacet.t.sol @@ -74,7 +74,7 @@ contract CCIPFacetTest is Test, DiamondTest { usdc = ERC20(USDC_ADDRESS); fooSwap = new FooSwap(); - bytes4[] memory functionSelectors = new bytes4[](5); + bytes4[] memory functionSelectors = new bytes4[](7); functionSelectors[0] = ccipFacet.startBridgeTokensViaCCIP.selector; functionSelectors[1] = ccipFacet .swapAndStartBridgeTokensViaCCIP @@ -84,6 +84,8 @@ contract CCIPFacetTest is Test, DiamondTest { functionSelectors[4] = ccipFacet .setFunctionApprovalBySignature .selector; + functionSelectors[5] = ccipFacet.quoteCCIPFee.selector; + functionSelectors[6] = ccipFacet.encodeDestinationArgs.selector; addFacet(diamond, address(ccipFacet), functionSelectors); @@ -186,7 +188,9 @@ contract CCIPFacetTest is Test, DiamondTest { 10_000 * 10 ** ccipTestToken.decimals() ); - ccipFacet.startBridgeTokensViaCCIP{ value: 0.1 ether }( + uint256 fee = ccipFacet.quoteCCIPFee(validBridgeData, validCCIPData); + + ccipFacet.startBridgeTokensViaCCIP{ value: fee }( validBridgeData, validCCIPData ); @@ -220,7 +224,86 @@ contract CCIPFacetTest is Test, DiamondTest { ILiFi.BridgeData memory bridgeData = validBridgeData; bridgeData.hasSourceSwaps = true; - ccipFacet.swapAndStartBridgeTokensViaCCIP{ value: 0.1 ether }( + uint256 fee = ccipFacet.quoteCCIPFee(validBridgeData, validCCIPData); + + ccipFacet.swapAndStartBridgeTokensViaCCIP{ value: fee }( + bridgeData, + swapData, + validCCIPData + ); + } + + function testCanBridgeERC20TokensWithDestinationCall() public { + deal( + address(ccipTestToken), + address(this), + 10 * 10 ** ccipTestToken.decimals() + ); + + ccipTestToken.approve( + address(ccipFacet), + 10_000 * 10 ** ccipTestToken.decimals() + ); + + bytes memory callData = abi.encodeWithSignature("foo()"); + bytes memory args = ccipFacet.encodeDestinationArgs({ + gasLimit: 100000, + strictSequencing: false + }); + + validCCIPData.callData = callData; + validCCIPData.extraArgs = args; + validBridgeData.hasDestinationCall = true; + + uint256 fee = ccipFacet.quoteCCIPFee(validBridgeData, validCCIPData); + + ccipFacet.startBridgeTokensViaCCIP{ value: fee }( + validBridgeData, + validCCIPData + ); + } + + function testCanSwapAndBridgeTokensWithDestinationCall() public { + deal(address(usdc), address(this), 10_000 * 10 ** usdc.decimals()); + usdc.approve(address(ccipFacet), 10_000 * 10 ** usdc.decimals()); + + uint256 inAmount = 10_000 * 10 ** usdc.decimals(); + uint256 outAmount = 10_000 * 10 ** ccipTestToken.decimals(); + + // Calculate DAI amount + LibSwap.SwapData[] memory swapData = new LibSwap.SwapData[](1); + swapData[0] = LibSwap.SwapData( + address(fooSwap), + address(fooSwap), + USDC_ADDRESS, + CCIP_TEST_TOKEN_ADDRESS, + inAmount, + abi.encodeWithSelector( + fooSwap.swap.selector, + ERC20(USDC_ADDRESS), + ERC20(CCIP_TEST_TOKEN_ADDRESS), + inAmount, + outAmount + ), + true + ); + + ILiFi.BridgeData memory bridgeData = validBridgeData; + bridgeData.hasSourceSwaps = true; + + bytes memory callData = abi.encodeWithSignature("foo()"); + bytes memory args = ccipFacet.encodeDestinationArgs({ + gasLimit: 100000, + strictSequencing: false + }); + + validCCIPData.callData = callData; + validCCIPData.extraArgs = args; + bridgeData.hasDestinationCall = true; + + uint256 fee = ccipFacet.quoteCCIPFee(validBridgeData, validCCIPData); + + ccipFacet.swapAndStartBridgeTokensViaCCIP{ value: fee }( bridgeData, swapData, validCCIPData From acd60afac7f5e5bb8a4ed1ad793863c366d17e68 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 29 Sep 2023 16:45:47 +0300 Subject: [PATCH 08/20] Update deploy scripts --- script/deploy/facets/DeployCCIPFacet.s.sol | 6 +-- script/deploy/facets/UpdateCCIPFacet.s.sol | 49 +++++++--------------- 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/script/deploy/facets/DeployCCIPFacet.s.sol b/script/deploy/facets/DeployCCIPFacet.s.sol index e1201987a..cfcbe5ffc 100644 --- a/script/deploy/facets/DeployCCIPFacet.s.sol +++ b/script/deploy/facets/DeployCCIPFacet.s.sol @@ -19,11 +19,11 @@ contract DeployScript is DeployScriptBase { "/config/ccip.json" ); string memory json = vm.readFile(path); - address example = json.readAddress( - string.concat(".", network, ".example") + address router = json.readAddress( + string.concat(".routers.", network, ".router") ); - constructorArgs = abi.encode(example); + constructorArgs = abi.encode(router); vm.startBroadcast(deployerPrivateKey); diff --git a/script/deploy/facets/UpdateCCIPFacet.s.sol b/script/deploy/facets/UpdateCCIPFacet.s.sol index 4306e821c..3f8f89dd0 100644 --- a/script/deploy/facets/UpdateCCIPFacet.s.sol +++ b/script/deploy/facets/UpdateCCIPFacet.s.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.17; import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; import { stdJson } from "forge-std/StdJson.sol"; -import { DiamondCutFacet, IDiamondCut } from "lifi/Facets/DiamondCutFacet.sol"; import { CCIPFacet } from "lifi/Facets/CCIPFacet.sol"; contract DeployScript is UpdateScriptBase { @@ -13,46 +12,30 @@ contract DeployScript is UpdateScriptBase { public returns (address[] memory facets, bytes memory cutData) { - address facet = json.readAddress(".CCIPFacet"); + return update("CCIPFacet"); + } + + function getExcludes() internal pure override returns (bytes4[] memory) { + bytes4[] memory excludes = new bytes4[](1); + excludes[0] = CCIPFacet.initCCIP.selector; + return excludes; + } + + function getCallData() internal override returns (bytes memory) { path = string.concat(root, "/config/ccip.json"); json = vm.readFile(path); - - address[] memory exampleAllowedTokens = json.readAddressArray( - string.concat(".", network, ".exampleAllowedTokens") + bytes memory rawChainSelectors = json.parseRaw(".chainSelectors"); + CCIPFacet.ChainSelector[] memory chainSelectors = abi.decode( + rawChainSelectors, + (CCIPFacet.ChainSelector[]) ); - /// You can remove this if you don't need to call init on the facet bytes memory callData = abi.encodeWithSelector( CCIPFacet.initCCIP.selector, - exampleAllowedTokens + chainSelectors ); - // CCIP - bytes4[] memory exclude; - buildDiamondCut(getSelectors("CCIPFacet", exclude), facet); - if (noBroadcast) { - if (cut.length > 0) { - cutData = abi.encodeWithSelector( - DiamondCutFacet.diamondCut.selector, - cut, - address(facet), // address(0) if not calling init - callData // "" if not calling init - ); - } - return (facets, cutData); - } - - vm.startBroadcast(deployerPrivateKey); - if (cut.length > 0) { - cutter.diamondCut( - cut, - address(facet), // address(0) if not calling init - callData // "" if not calling init - ); - } - facets = loupe.facetAddresses(); - - vm.stopBroadcast(); + return callData; } } From 77271819e4237a3f786662b0957085cadb887307 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 29 Sep 2023 16:57:52 +0300 Subject: [PATCH 09/20] Deploy to staging --- deployments/_deployments_log_file.json | 30 ++++++++++++++++++++++ deployments/avalanche.diamond.staging.json | 4 +++ deployments/avalanche.staging.json | 3 ++- deployments/mainnet.diamond.staging.json | 6 ++++- deployments/mainnet.staging.json | 5 ++-- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 506d9853e..501d4d22c 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -13375,5 +13375,35 @@ ] } } + }, + "CCIPFacet": { + "avalanche": { + "staging": { + "0.0.1": [ + { + "ADDRESS": "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-09-29 16:56:13", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000027f39d0af3303703750d4001fcc1844c6491563c", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "mainnet": { + "staging": { + "0.0.1": [ + { + "ADDRESS": "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-09-29 16:55:28", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000e561d5e02207fb5eb32cca20a699e0d8919a1476", + "SALT": "", + "VERIFIED": "true" + } + ] + } + } } } diff --git a/deployments/avalanche.diamond.staging.json b/deployments/avalanche.diamond.staging.json index 5c3795fd6..57d5c865c 100644 --- a/deployments/avalanche.diamond.staging.json +++ b/deployments/avalanche.diamond.staging.json @@ -48,6 +48,10 @@ "0xe1FaF1759cAB242c5A790Da72c8f0cC7F5e09f59": { "Name": "CircleBridgeFacet", "Version": "1.0.0" + }, + "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc": { + "Name": "CCIPFacet", + "Version": "0.0.1" } }, "Periphery": { diff --git a/deployments/avalanche.staging.json b/deployments/avalanche.staging.json index 9a38f735d..a66db980d 100644 --- a/deployments/avalanche.staging.json +++ b/deployments/avalanche.staging.json @@ -16,5 +16,6 @@ "FeeCollector": "0x0222D030e8DFAEDE2a4e7B5F181Ac1A4206A75f0", "Receiver": "0x59B341fF54543D66C7393FfD2A050E256c97669E", "ServiceFeeCollector": "0x9cc3164f01ED3796Fdf7Da538484D634608D2203", - "CircleBridgeFacet": "0xe1FaF1759cAB242c5A790Da72c8f0cC7F5e09f59" + "CircleBridgeFacet": "0xe1FaF1759cAB242c5A790Da72c8f0cC7F5e09f59", + "CCIPFacet": "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc" } \ No newline at end of file diff --git a/deployments/mainnet.diamond.staging.json b/deployments/mainnet.diamond.staging.json index 0bf3c729c..728c29bc5 100644 --- a/deployments/mainnet.diamond.staging.json +++ b/deployments/mainnet.diamond.staging.json @@ -92,6 +92,10 @@ "0xE8Ff7BFEF5DacB57E87bC2d0B6CCFefBE5f546BC": { "Name": "OptimismBridgeFacet", "Version": "1.0.0" + }, + "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc": { + "Name": "CCIPFacet", + "Version": "0.0.1" } }, "Periphery": { @@ -103,4 +107,4 @@ "ServiceFeeCollector": "0xf068cc770f32042Ff4a8fD196045641234dFaa47" } } -} \ No newline at end of file +} diff --git a/deployments/mainnet.staging.json b/deployments/mainnet.staging.json index 31a03866d..912ab7408 100644 --- a/deployments/mainnet.staging.json +++ b/deployments/mainnet.staging.json @@ -28,5 +28,6 @@ "RoninBridgeFacet": "0xFB4C992Cc7cfA7Eb3e44b928C6f756C07a3feb04", "SquidFacet": "0x933A3AfE2087FB8F5c9EE9A033477C42CC14c18E", "SynapseBridgeFacet": "0x57F98A94AC66e197AF6776D5c094FF0da2C0B198", - "ThorSwapFacet": "0xa696287F37d21D566B9A80AC29b2640FF910C176" -} + "ThorSwapFacet": "0xa696287F37d21D566B9A80AC29b2640FF910C176", + "CCIPFacet": "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc" +} \ No newline at end of file From 6d5ec6fe1308a7484ce65c4828a2ba7e0f2bdd18 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 3 Oct 2023 12:47:35 +0300 Subject: [PATCH 10/20] Create demo script --- script/demoScripts/demoCCIP.ts | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 script/demoScripts/demoCCIP.ts diff --git a/script/demoScripts/demoCCIP.ts b/script/demoScripts/demoCCIP.ts new file mode 100644 index 000000000..07c7ff1cb --- /dev/null +++ b/script/demoScripts/demoCCIP.ts @@ -0,0 +1,82 @@ +import { providers, Wallet, utils, constants } from 'ethers' +import { CCIPFacet__factory, ERC20__factory } from '../../typechain' +import chalk from 'chalk' +import dotenv from 'dotenv' + +dotenv.config() + +const msg = (msg: string) => { + console.log(chalk.green(msg)) +} + +const LIFI_ADDRESS = '0xbEbCDb5093B47Cd7add8211E4c77B6826aF7bc5F' // LiFiDiamond address on AVAX stating +const BETS_TOKEN_ADDRESS = '0x94025780a1aB58868D9B2dBBB775f44b32e8E6e5' +const L2_GAS = 20000 // L2 Gas, Don't need to change it. +const destinationChainId = 1 // Mainnet + +async function main() { + const jsonProvider = new providers.JsonRpcProvider( + process.env.ETH_NODE_URI_AVALANCHE + ) + const provider = new providers.FallbackProvider([jsonProvider]) + + let wallet = new Wallet(process.env.PRIVATE_KEY) + wallet = wallet.connect(provider) + const walletAddress = await wallet.getAddress() + + const lifi = CCIPFacet__factory.connect(LIFI_ADDRESS, wallet) + + // Bridge amount + const amount = utils.parseEther('10') + + // LIFI Data + const lifiData = { + transactionId: utils.randomBytes(32), + bridge: 'CCIP', + integrator: 'ACME Devs', + referrer: constants.AddressZero, + sendingAssetId: constants.AddressZero, + receiver: walletAddress, + minAmount: amount, + destinationChainId: destinationChainId, + hasSourceSwaps: false, + hasDestinationCall: false, + } + + // Bridge ERC20 + lifiData.sendingAssetId = BETS_TOKEN_ADDRESS + + const extraArgs = await lifi.encodeDestinationArgs(L2_GAS, false) + + const bridgeData = { + callData: '0x', + extraArgs, + } + + const fee = await lifi.quoteCCIPFee(lifiData, bridgeData) + + msg('Approving BETS...') + const BETS = ERC20__factory.connect(BETS_TOKEN_ADDRESS, wallet) + let tx = await BETS.approve(LIFI_ADDRESS, amount) + await tx.wait() + + msg('Sending BETS to Mainnet via CCIP...') + tx = await lifi.startBridgeTokensViaCCIP(lifiData, bridgeData, { + gasLimit: '500000', + value: fee, + }) + msg('TX Sent. Waiting for receipt...') + await tx.wait() + msg('Done!') +} + +main() + .then(() => { + console.error('Success') + process.exit(0) + }) + .catch((error) => { + console.error('error') + console.error(error) + process.exit(1) + }) From 9da5958aa9d31f46133e4fd80c6c41c919f861a5 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 3 Oct 2023 18:10:33 +0300 Subject: [PATCH 11/20] Add basic documentation --- docs/CCIPFacet.md | 14 ++++++++++---- src/Facets/CCIPFacet.sol | 6 +++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/CCIPFacet.md b/docs/CCIPFacet.md index 796792cf5..76267020e 100644 --- a/docs/CCIPFacet.md +++ b/docs/CCIPFacet.md @@ -2,7 +2,7 @@ ## How it works -The CCIP Facet works by ... +The CCIP Facet works by forwarding CCIP specific calls to the CCIP router contract. ```mermaid graph LR; @@ -16,15 +16,21 @@ graph LR; - Simply bridges tokens using ccip - `swapAndStartBridgeTokensViaccip(BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, ccipData memory _ccipData)` - Performs swap(s) before bridging tokens using ccip +- `quoteCCIPFee(BridgeData memory _bridgeData, CCIPData memory _ccipData) returns (uint256)` + - Returns the amount of fee in native tokens needed to bridge the tokens using ccip +- `encodeDestinationArgs(uint256 gasLimit, bool strictSequencing) returns (bytes memory)` + - Encodes the destination args for ccip. Only needed if doing calls on the destination chain. ## ccip Specific Parameters The methods listed above take a variable labeled `_ccipData`. This data is specific to ccip and is represented as the following struct type: ```solidity -/// @param example Example parameter. -struct ccipData { - string example; +/// @param callData destination calldata (optional). +/// @param extraArgs extra arguments for destination call (only needed for destination calls) +struct CCIPData { + bytes callData; + bytes extraArgs; } ``` diff --git a/src/Facets/CCIPFacet.sol b/src/Facets/CCIPFacet.sol index 45e3e931a..b129e6b03 100644 --- a/src/Facets/CCIPFacet.sol +++ b/src/Facets/CCIPFacet.sol @@ -27,7 +27,8 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Types /// /// @dev Optional bridge specific struct - /// @param exampleParam Example paramter + /// @param callData The calldata for the destination calldata + /// @param extraArgs The extra arguments for the destination call struct CCIPData { bytes callData; bytes extraArgs; @@ -38,6 +39,9 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { mapping(uint256 => uint64) chainSelectors; } + /// @dev Chain selector for CCIP + /// @param chainId Standard chain ID + /// @param selector CCIP specific chain selector struct ChainSelector { uint256 chainId; uint64 selector; From 7376af6653653763a38f171076a6c02e680e4209 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 24 Oct 2023 10:54:59 +0300 Subject: [PATCH 12/20] Update demo --- script/demoScripts/demoCCIP.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/script/demoScripts/demoCCIP.ts b/script/demoScripts/demoCCIP.ts index 07c7ff1cb..59a823031 100644 --- a/script/demoScripts/demoCCIP.ts +++ b/script/demoScripts/demoCCIP.ts @@ -9,14 +9,14 @@ const msg = (msg: string) => { console.log(chalk.green(msg)) } -const LIFI_ADDRESS = '0xbEbCDb5093B47Cd7add8211E4c77B6826aF7bc5F' // LiFiDiamond address on AVAX stating -const BETS_TOKEN_ADDRESS = '0x94025780a1aB58868D9B2dBBB775f44b32e8E6e5' +const LIFI_ADDRESS = '0xbEbCDb5093B47Cd7add8211E4c77B6826aF7bc5F' // LiFiDiamond address on MAINNET stating +const R_TOKEN_ADDRESS = '0x183015a9ba6ff60230fdeadc3f43b3d788b13e21' const L2_GAS = 20000 // L2 Gas, Don't need to change it. -const destinationChainId = 1 // Mainnet +const destinationChainId = 8453 // Base Chain async function main() { const jsonProvider = new providers.JsonRpcProvider( - process.env.ETH_NODE_URI_AVALANCHE + process.env.ETH_NODE_URI_MAINNET ) const provider = new providers.FallbackProvider([jsonProvider]) @@ -27,7 +27,7 @@ async function main() { const lifi = CCIPFacet__factory.connect(LIFI_ADDRESS, wallet) // Bridge amount - const amount = utils.parseEther('10') + const amount = utils.parseEther('1') // LIFI Data const lifiData = { @@ -44,7 +44,7 @@ async function main() { } // Bridge ERC20 - lifiData.sendingAssetId = BETS_TOKEN_ADDRESS + lifiData.sendingAssetId = R_TOKEN_ADDRESS const extraArgs = await lifi.encodeDestinationArgs(L2_GAS, false) @@ -55,12 +55,14 @@ async function main() { const fee = await lifi.quoteCCIPFee(lifiData, bridgeData) - msg('Approving BETS...') - const BETS = ERC20__factory.connect(BETS_TOKEN_ADDRESS, wallet) + msg('Wallet Address: ' + walletAddress) + + msg('Approving R...') + const BETS = ERC20__factory.connect(R_TOKEN_ADDRESS, wallet) let tx = await BETS.approve(LIFI_ADDRESS, amount) await tx.wait() - msg('Sending BETS to Mainnet via CCIP...') + msg('Sending R to Base via CCIP...') tx = await lifi.startBridgeTokensViaCCIP(lifiData, bridgeData, { gasLimit: '500000', value: fee, From 972b7b2c9cf2406dc2ff250f0ac681e4df347dd1 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 25 Oct 2023 15:30:46 +0300 Subject: [PATCH 13/20] Add missing URI --- .github/workflows/forge.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/forge.yml b/.github/workflows/forge.yml index c4c1d0e87..54de67423 100644 --- a/.github/workflows/forge.yml +++ b/.github/workflows/forge.yml @@ -1,7 +1,7 @@ name: Forge on: push: - + # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -14,6 +14,7 @@ jobs: ETH_NODE_URI_GOERLI: ${{ secrets.ETH_NODE_URI_GOERLI }} ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} ETH_NODE_URI_GNOSIS: ${{ secrets.ETH_NODE_URI_GNOSIS }} + ETH_NODE_URI_BSC_TESTNET: ${{ secrets.ETH_NODE_URI_BSC_TESTNET }} FORK_NUMBER: ${{ secrets.FORK_NUMBER }} POLYGON_FORK_NUMBER: ${{ secrets.POLYGON_FORK_NUMBER }} FORK_NUMBER_POLYGON: 36004499 From bd5c1544f9a0d89b86d592a5a82f9cb490cddf04 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 27 Oct 2023 11:32:33 +0300 Subject: [PATCH 14/20] Implement destination calls for CCIP --- src/Periphery/CCIPReceiver.sol | 191 +++++++++++++++++++ test/solidity/Periphery/CCIPReceiver.t.sol | 205 +++++++++++++++++++++ 2 files changed, 396 insertions(+) create mode 100644 src/Periphery/CCIPReceiver.sol create mode 100644 test/solidity/Periphery/CCIPReceiver.t.sol diff --git a/src/Periphery/CCIPReceiver.sol b/src/Periphery/CCIPReceiver.sol new file mode 100644 index 000000000..494f770ae --- /dev/null +++ b/src/Periphery/CCIPReceiver.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; +import { LibSwap } from "../Libraries/LibSwap.sol"; +import { LibAsset } from "../Libraries/LibAsset.sol"; +import { ILiFi } from "../Interfaces/ILiFi.sol"; +import { IExecutor } from "../Interfaces/IExecutor.sol"; +import { TransferrableOwnership } from "../Helpers/TransferrableOwnership.sol"; +import { ExternalCallFailed, UnAuthorized } from "../Errors/GenericErrors.sol"; +import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; +import { CCIPReceiver as CCIPRcvr } from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; + +/// @title CCIPReceiver +/// @author LI.FI (https://li.fi) +/// @notice Arbitrary execution contract used for cross-chain swaps +// and message passing via Chainlink CCIP +/// @custom:version 2.0.2 +contract CCIPReceiver is + CCIPRcvr, + ILiFi, + ReentrancyGuard, + TransferrableOwnership +{ + using SafeERC20 for IERC20; + + /// Storage /// + IExecutor public executor; + uint256 public recoverGas; + + /// Events /// + event CCIPRouterSet(address indexed router); + event ExecutorSet(address indexed executor); + event RecoverGasSet(uint256 indexed recoverGas); + + constructor( + address _owner, + address _router, + address _executor, + uint256 _recoverGas + ) TransferrableOwnership(_owner) CCIPRcvr(_router) { + executor = IExecutor(_executor); + recoverGas = _recoverGas; + emit CCIPRouterSet(_router); + emit ExecutorSet(_executor); + emit RecoverGasSet(_recoverGas); + } + + /// @notice Send remaining token to receiver + /// @param assetId token received from the other chain + /// @param receiver address that will receive tokens in the end + /// @param amount amount of token + function pullToken( + address assetId, + address payable receiver, + uint256 amount + ) external onlyOwner { + if (LibAsset.isNativeAsset(assetId)) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = receiver.call{ value: amount }(""); + if (!success) revert ExternalCallFailed(); + } else { + IERC20(assetId).safeTransfer(receiver, amount); + } + } + + /// Internal Functions /// + + /// @notice Receive CCIP message and execute arbitrary calls + /// @param message CCIP message + function _ccipReceive( + Client.Any2EVMMessage memory message + ) internal override { + // Extract swap data from message + ( + bytes32 transactionId, + LibSwap.SwapData[] memory swapData, + , + address receiver + ) = abi.decode( + message.data, + (bytes32, LibSwap.SwapData[], address, address) + ); + _swapAndCompleteBridgeTokens( + transactionId, + swapData, + message.destTokenAmounts[0].token, + payable(receiver), + message.destTokenAmounts[0].amount + ); + } + + /// @notice Performs a swap before completing a cross-chain transaction + /// @param _transactionId the transaction id associated with the operation + /// @param _swapData array of data needed for swaps + /// @param assetId token received from the other chain + /// @param receiver address that will receive tokens in the end + /// @param amount amount of token + function _swapAndCompleteBridgeTokens( + bytes32 _transactionId, + LibSwap.SwapData[] memory _swapData, + address assetId, + address payable receiver, + uint256 amount + ) private { + if (LibAsset.isNativeAsset(assetId)) { + // case 1: native asset + uint256 cacheGasLeft = gasleft(); + if (cacheGasLeft < recoverGas) { + // case 1a: not enough gas left to execute calls + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = receiver.call{ value: amount }(""); + if (!success) revert ExternalCallFailed(); + + emit LiFiTransferRecovered( + _transactionId, + assetId, + receiver, + amount, + block.timestamp + ); + return; + } + + // case 1b: enough gas left to execute calls + // solhint-disable no-empty-blocks + try + executor.swapAndCompleteBridgeTokens{ + value: amount, + gas: cacheGasLeft - recoverGas + }(_transactionId, _swapData, assetId, receiver) + {} catch { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = receiver.call{ value: amount }(""); + if (!success) revert ExternalCallFailed(); + + emit LiFiTransferRecovered( + _transactionId, + assetId, + receiver, + amount, + block.timestamp + ); + } + } else { + // case 2: ERC20 asset + uint256 cacheGasLeft = gasleft(); + IERC20 token = IERC20(assetId); + token.safeApprove(address(executor), 0); + + if (cacheGasLeft < recoverGas) { + // case 2a: not enough gas left to execute calls + token.safeTransfer(receiver, amount); + + emit LiFiTransferRecovered( + _transactionId, + assetId, + receiver, + amount, + block.timestamp + ); + return; + } + + // case 2b: enough gas left to execute calls + token.safeIncreaseAllowance(address(executor), amount); + try + executor.swapAndCompleteBridgeTokens{ + gas: cacheGasLeft - recoverGas + }(_transactionId, _swapData, assetId, receiver) + {} catch { + token.safeTransfer(receiver, amount); + emit LiFiTransferRecovered( + _transactionId, + assetId, + receiver, + amount, + block.timestamp + ); + } + + token.safeApprove(address(executor), 0); + } + } + + /// @notice Receive native asset directly. + /// @dev Some bridges may send native asset before execute external calls. + // solhint-disable-next-line no-empty-blocks + receive() external payable {} +} diff --git a/test/solidity/Periphery/CCIPReceiver.t.sol b/test/solidity/Periphery/CCIPReceiver.t.sol new file mode 100644 index 000000000..95ace10ab --- /dev/null +++ b/test/solidity/Periphery/CCIPReceiver.t.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.17; + +import { Test, TestBase, LiFiDiamond, DSTest, ILiFi, LibSwap, LibAllowList, console, InvalidAmount, ERC20, UniswapV2Router02 } from "../utils/TestBase.sol"; +import { OnlyContractOwner } from "src/Errors/GenericErrors.sol"; +import { CCIPReceiver } from "lifi/Periphery/CCIPReceiver.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { ERC20Proxy } from "lifi/Periphery/ERC20Proxy.sol"; +import { Executor } from "lifi/Periphery/Executor.sol"; +import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; + +contract CCIPReceiverTest is TestBase { + using stdJson for string; + + CCIPReceiver internal receiver; + + error UnAuthorized(); + + string path; + string json; + address ccipRouter; + Executor executor; + ERC20Proxy erc20Proxy; + + event CCIPRouterSet(address indexed router); + event ExecutorSet(address indexed executor); + event RecoverGasSet(uint256 indexed recoverGas); + + function setUp() public { + initTestBase(); + + // obtain address of Stargate router in current network from config file + path = string.concat(vm.projectRoot(), "/config/ccip.json"); + json = vm.readFile(path); + ccipRouter = json.readAddress( + string.concat(".routers.mainnet.router") + ); + + erc20Proxy = new ERC20Proxy(address(this)); + executor = new Executor(address(erc20Proxy)); + receiver = new CCIPReceiver( + address(this), + ccipRouter, + address(executor), + 100000 + ); + vm.label(address(receiver), "Receiver"); + vm.label(address(executor), "Executor"); + vm.label(address(erc20Proxy), "ERC20Proxy"); + vm.label(ccipRouter, "CCIPRouter"); + } + + function test_revert_OwnerCanPullToken() public { + // send token to receiver + vm.startPrank(USER_SENDER); + dai.transfer(address(receiver), 1000); + vm.stopPrank(); + + // pull token + vm.startPrank(USER_DIAMOND_OWNER); + + receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + + assertEq(1000, dai.balanceOf(USER_RECEIVER)); + } + + function test_revert_PullTokenNonOwner() public { + vm.startPrank(USER_SENDER); + vm.expectRevert(UnAuthorized.selector); + receiver.pullToken(ADDRESS_DAI, payable(USER_RECEIVER), 1000); + } + + function test_CCIP_ExecutesCrossChainMessage() public { + // create swap data + delete swapData; + // Swap DAI -> USDC + address[] memory swapPath = new address[](2); + swapPath[0] = ADDRESS_DAI; + swapPath[1] = ADDRESS_USDC; + + uint256 amountOut = defaultUSDCAmount; + + // Calculate DAI amount + uint256[] memory amounts = uniswap.getAmountsIn(amountOut, swapPath); + uint256 amountIn = amounts[0]; + + swapData.push( + LibSwap.SwapData({ + callTo: address(uniswap), + approveTo: address(uniswap), + sendingAssetId: ADDRESS_DAI, + receivingAssetId: ADDRESS_USDC, + fromAmount: amountIn, + callData: abi.encodeWithSelector( + uniswap.swapExactTokensForTokens.selector, + amountIn, + amountOut, + swapPath, + address(executor), + block.timestamp + 20 minutes + ), + requiresDeposit: true + }) + ); + + // create callData that will be sent to our CCIPReceiver + bytes32 txId = keccak256("txId"); + bytes memory payload = abi.encode( + txId, + swapData, + USER_RECEIVER, + USER_RECEIVER + ); + + // fund receiver with sufficient DAI to execute swap + vm.startPrank(USER_DAI_WHALE); + dai.transfer(address(receiver), swapData[0].fromAmount); + vm.stopPrank(); + + // call sgReceive function as Stargate router + vm.startPrank(ccipRouter); + dai.approve(address(receiver), swapData[0].fromAmount); + + // prepare check for events + vm.expectEmit(true, true, true, true, address(executor)); + emit AssetSwapped( + txId, + address(uniswap), + ADDRESS_DAI, + ADDRESS_USDC, + swapData[0].fromAmount, + defaultUSDCAmount, + block.timestamp + ); + vm.expectEmit(true, true, true, true, address(executor)); + emit LiFiTransferCompleted( + txId, + ADDRESS_DAI, + USER_RECEIVER, + defaultUSDCAmount, + block.timestamp + ); + + Client.EVMTokenAmount[] + memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: ADDRESS_DAI, + amount: swapData[0].fromAmount + }); + + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: txId, + sourceChainSelector: 3734403246176062136, // Optimism chain selector, + sender: abi.encode(address(receiver)), + data: payload, + destTokenAmounts: tokenAmounts + }); + + // call ccipReceive function to complete transaction + receiver.ccipReceive(message); + } + + function test_CCIP_EmitsCorrectEventOnRecovery() public { + // (mock) transfer "bridged funds" to CCIPReceiver.sol + bytes32 txId = keccak256("txId"); + vm.startPrank(USER_SENDER); + usdc.transfer(address(receiver), defaultUSDCAmount); + vm.stopPrank(); + + bytes memory payload = abi.encode( + txId, + swapData, + address(1), + address(1) + ); + + vm.startPrank(ccipRouter); + vm.expectEmit(true, true, true, true, address(receiver)); + emit LiFiTransferRecovered( + txId, + ADDRESS_USDC, + address(1), + defaultUSDCAmount, + block.timestamp + ); + + Client.EVMTokenAmount[] + memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: ADDRESS_USDC, + amount: defaultUSDCAmount + }); + + Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ + messageId: txId, + sourceChainSelector: 3734403246176062136, // Optimism chain selector, + sender: abi.encode(address(receiver)), + data: payload, + destTokenAmounts: tokenAmounts + }); + + // call ccipReceive function to complete transaction + receiver.ccipReceive(message); + } +} From bffeaf156596d4920972cd2c78e279fbc6e7d0dc Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 27 Oct 2023 16:29:44 +0300 Subject: [PATCH 15/20] Update demo --- config/ccip.json | 7 + deployments/_deployments_log_file.json | 963 +++++++++++------- deployments/base.diamond.staging.json | 60 ++ deployments/base.staging.json | 17 + deployments/mainnet.diamond.staging.json | 8 +- deployments/mainnet.staging.json | 2 +- hardhat.config.ts | 2 +- script/demoScripts/demoCCIP.ts | 58 +- script/deploy/_targetState.json | 7 + .../deploy/facets/DeployCCIPMsgReceiver.s.sol | 58 ++ src/Facets/CCIPFacet.sol | 5 +- .../{CCIPReceiver.sol => CCIPMsgReceiver.sol} | 10 +- test/solidity/Facets/CCIPFacet.t.sol | 2 +- ...IPReceiver.t.sol => CCIPMsgReceiver.t.sol} | 26 +- 14 files changed, 793 insertions(+), 432 deletions(-) create mode 100644 deployments/base.diamond.staging.json create mode 100644 deployments/base.staging.json create mode 100644 script/deploy/facets/DeployCCIPMsgReceiver.s.sol rename src/Periphery/{CCIPReceiver.sol => CCIPMsgReceiver.sol} (97%) rename test/solidity/Periphery/{CCIPReceiver.t.sol => CCIPMsgReceiver.t.sol} (91%) diff --git a/config/ccip.json b/config/ccip.json index 92c921946..54bcc8981 100644 --- a/config/ccip.json +++ b/config/ccip.json @@ -12,6 +12,9 @@ "avalanche": { "router": "0x27F39D0af3303703750D4001fCc1844c6491563c" }, + "base": { + "router": "0x673aa85efd75080031d44fca061575d1da427a28" + }, "mumbai": { "router": "0x70499c328e1E2a3c41108bd3730F6670a44595D1" }, @@ -38,6 +41,10 @@ { "chainId": 137, "selector": 4051577828743386545 + }, + { + "chainId": 8453, + "selector": 15971525489660198786 } ], "testChainSelectors": [ diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index a7160f16b..79c082edf 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -465,6 +465,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x0E7C95Cd5087B9cf28848938544794Df18060364", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 14:59:37", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -974,6 +986,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xa07464a132C0C3588ff3e2857920267072407B18", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:10:02", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -1484,6 +1508,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x8d9A296ABcfe7F3a4046633eD0685D12264628Cf", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:11:00", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -1956,6 +1992,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9530d861dbe3002d6466541aA95AA77f77657c74", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:29:37", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -2427,6 +2475,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x769b85186cB944F33deFE2C57e389EE20a136Ef0", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:21:19", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false" + } + ] } }, "linea": { @@ -2899,6 +2959,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x3b77FCFaD7426C905050dB60e880EfC2825e07B5", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:47:32", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -3398,6 +3470,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x78c16da94Ba24F62e340653e2a4eBf960B9C5771", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:33:32", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -3872,6 +3956,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x276Ba9B74EaB0c0B544CC6506Db71a956E4B1282", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:49:41", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000552008c0f6870c2f77e5cc1d2eb9bdff03e30ea00000000000000000000000000e7c95cd5087b9cf28848938544794df18060364", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -5190,6 +5286,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x9BFADfb17157599a6ac0Cd3aADb4d5b06c5ec405", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:54:23", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -6082,6 +6190,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x3A9e75FD1EDd3746572A3932FCf2d1084ECf9EC8", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:53:26", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -7689,6 +7809,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x133E6EA10e1c70C17D957A3c36078bEc443ef7D5", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:50:42", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000552008c0f6870c2f77e5cc1d2eb9bdff03e30ea0", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -8347,6 +8479,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "2.0.0": [ + { + "ADDRESS": "0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:55:32", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000133e6ea10e1c70c17d957a3c36078bec443ef7d5", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -13266,6 +13410,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x09071b88aDf0E95D064296d635ca63a1Cc06c304", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:44:50", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "boba": { @@ -13574,6 +13730,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.1.1": [ + { + "ADDRESS": "0x438c07D8a3121619a3DAF24795E59700d94afe55", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-27 15:46:34", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "bsc": { @@ -13941,421 +14109,426 @@ } ] } - }, - "LiFuelFeeCollector": { - "polygon": { - "staging": { - "1.0.0": [ - { - "ADDRESS": "0x86130169737b68fAF1b619A75c92205784601Da6", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-09 11:51:33", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] + }, + "LiFuelFeeCollector": { + "polygon": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x86130169737b68fAF1b619A75c92205784601Da6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-09 11:51:33", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "mainnet": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-26 11:28:49", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "goerli": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-10 17:16:05", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "lineatest": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-09 16:12:01", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "false" + } + ] + } + }, + "arbitrum": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:23:19", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "aurora": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:24:09", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "avalanche": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:24:56", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "base": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:26:04", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "boba": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:26:50", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "bsc": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:27:38", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "cronos": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-10 17:00:10", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "false" + } + ] + } + }, + "evmos": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:29:02", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "fantom": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:29:46", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "fuse": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:30:39", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "gnosis": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:31:47", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "moonbeam": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:33:47", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "moonriver": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:35:02", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "okx": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:36:17", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "false" + } + ] + } + }, + "optimism": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:37:25", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "polygonzkevm": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 18:05:42", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "false" + } + ] + } + }, + "linea": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0x6CC48E94C1148A0787D7F137745af58e3Eb47780", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-26 12:27:48", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "mumbai": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-10 18:27:05", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "false" + } + ] + } + }, + "zksync": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", + "OPTIMIZER_RUNS": "10000", + "TIMESTAMP": "2023-10-11 14:04:54", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "true" + }, + { + "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", + "OPTIMIZER_RUNS": "10000", + "TIMESTAMP": "2023-10-11 14:04:54", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "VERIFIED": "false" + }, + { + "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", + "OPTIMIZER_RUNS": "10000", + "TIMESTAMP": "2023-10-11 14:04:54", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "VERIFIED": "false" + }, + { + "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", + "OPTIMIZER_RUNS": "10000", + "TIMESTAMP": "2023-10-11 14:04:54", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "VERIFIED": "false" + }, + { + "ADDRESS": "0x4A751A91647CA1749d7ec505026427FF601C6966", + "OPTIMIZER_RUNS": "10000", + "TIMESTAMP": "2023-10-26 09:10:07", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "VERIFIED": "false" + } + ] + } + }, + "opbnb": { + "production": { + "1.1.0": [ + { + "ADDRESS": "0xaE77c9aD4af61fAec96f04bD6723F6F6A804a567", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-09-18 11:36:54", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "false" + } + ], + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 17:36:54", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "false" + } + ] + } + }, + "velas": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2023-10-25 18:03:06", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "SALT": "", + "VERIFIED": "false" + } + ] + } } }, "mainnet": { "staging": { "0.0.1": [ { - "ADDRESS": "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc", + "ADDRESS": "0x9BE903AB2ad61dfC71f7E2171A3dBf0884a4cdBF", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-09-29 16:55:28", + "TIMESTAMP": "2023-10-27 15:42:31", "CONSTRUCTOR_ARGS": "0x000000000000000000000000e561d5e02207fb5eb32cca20a699e0d8919a1476", "SALT": "", - "VERIFIED": "true" - } - ] - }, - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:38:28", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "goerli": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-10 17:16:05", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "lineatest": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-09 16:12:01", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", "VERIFIED": "false" } ] } - }, - "mainnet": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-26 11:28:49", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "arbitrum": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:23:19", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "aurora": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:24:09", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "avalanche": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:24:56", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, + } + }, + "CCIPMsgReceiver": { "base": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:26:04", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "boba": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:26:50", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "bsc": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:27:38", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "cronos": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-10 17:00:10", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "false" - } - ] - } - }, - "evmos": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:29:02", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "fantom": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:29:46", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "fuse": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:30:39", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "gnosis": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:31:47", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "moonbeam": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:33:47", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "moonriver": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:35:02", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "okx": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:36:17", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "false" - } - ] - } - }, - "optimism": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:37:25", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - } - ] - } - }, - "polygonzkevm": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 18:05:42", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "false" - } - ] - } - }, - "linea": { - "production": { - "1.0.0": [ + "staging": { + "0.0.1": [ { - "ADDRESS": "0x6CC48E94C1148A0787D7F137745af58e3Eb47780", + "ADDRESS": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-26 12:27:48", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", + "TIMESTAMP": "2023-10-27 15:56:47", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f000000000000000000000000673aa85efd75080031d44fca061575d1da427a28000000000000000000000000eced7eb0ff5c63de883f7dee66af24f58fac417b00000000000000000000000000000000000000000000000000000000000186a0", "SALT": "", "VERIFIED": "true" } ] } - }, - "mumbai": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xdFC2983401614118E1F2D5A5FD93C17Fecf8BdC6", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-10 18:27:05", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "false" - } - ] - } - }, - "zksync": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", - "OPTIMIZER_RUNS": "10000", - "TIMESTAMP": "2023-10-11 14:04:54", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "true" - }, - { - "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", - "OPTIMIZER_RUNS": "10000", - "TIMESTAMP": "2023-10-11 14:04:54", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "VERIFIED": "false" - }, - { - "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", - "OPTIMIZER_RUNS": "10000", - "TIMESTAMP": "2023-10-11 14:04:54", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "VERIFIED": "false" - }, - { - "ADDRESS": "0xbE07Edbb7d0fc7eb941078486F6b59061bF0336C", - "OPTIMIZER_RUNS": "10000", - "TIMESTAMP": "2023-10-11 14:04:54", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "VERIFIED": "false" - }, - { - "ADDRESS": "0x4A751A91647CA1749d7ec505026427FF601C6966", - "OPTIMIZER_RUNS": "10000", - "TIMESTAMP": "2023-10-26 09:10:07", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "VERIFIED": "false" - } - ] - } - }, - "opbnb": { - "production": { - "1.1.0": [ - { - "ADDRESS": "0xaE77c9aD4af61fAec96f04bD6723F6F6A804a567", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-09-18 11:36:54", - "CONSTRUCTOR_ARGS": "0x", - "SALT": "", - "VERIFIED": "false" - } - ], - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 17:36:54", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "false" - } - ] - } - }, - "velas": { - "production": { - "1.0.0": [ - { - "ADDRESS": "0xc4f7A34b8d283f66925eF0f5CCdFC2AF3030DeaE", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-25 18:03:06", - "CONSTRUCTOR_ARGS": "0x000000000000000000000000c71284231a726a18ac85c94d75f9fe17a185beaf", - "SALT": "", - "VERIFIED": "false" - } - ] - } } } -} \ No newline at end of file +} diff --git a/deployments/base.diamond.staging.json b/deployments/base.diamond.staging.json new file mode 100644 index 000000000..d9002d8e4 --- /dev/null +++ b/deployments/base.diamond.staging.json @@ -0,0 +1,60 @@ +{ + "LiFiDiamond": { + "Facets": { + "0x0E7C95Cd5087B9cf28848938544794Df18060364": { + "Name": "DiamondCutFacet", + "Version": "1.0.0" + }, + "0xa07464a132C0C3588ff3e2857920267072407B18": { + "Name": "DiamondLoupeFacet", + "Version": "1.0.0" + }, + "0x8d9A296ABcfe7F3a4046633eD0685D12264628Cf": { + "Name": "OwnershipFacet", + "Version": "1.0.0" + }, + "0x3b77FCFaD7426C905050dB60e880EfC2825e07B5": { + "Name": "WithdrawFacet", + "Version": "1.0.0" + }, + "0x9530d861dbe3002d6466541aA95AA77f77657c74": { + "Name": "DexManagerFacet", + "Version": "1.0.0" + }, + "0x769b85186cB944F33deFE2C57e389EE20a136Ef0": { + "Name": "AccessManagerFacet", + "Version": "1.0.0" + }, + "0x78c16da94Ba24F62e340653e2a4eBf960B9C5771": { + "Name": "PeripheryRegistryFacet", + "Version": "1.0.0" + }, + "0x3A9e75FD1EDd3746572A3932FCf2d1084ECf9EC8": { + "Name": "LIFuelFacet", + "Version": "1.0.0" + }, + "0x9BFADfb17157599a6ac0Cd3aADb4d5b06c5ec405": { + "Name": "GenericSwapFacet", + "Version": "1.0.0" + }, + "0x09071b88aDf0E95D064296d635ca63a1Cc06c304": { + "Name": "StandardizedCallFacet", + "Version": "1.0.0" + }, + "0x438c07D8a3121619a3DAF24795E59700d94afe55": { + "Name": "CalldataVerificationFacet", + "Version": "1.1.1" + } + }, + "Periphery": { + "CCIPMsgReceiver": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976", + "ERC20Proxy": "0x133E6EA10e1c70C17D957A3c36078bEc443ef7D5", + "Executor": "0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b", + "FeeCollector": "", + "LiFuelFeeCollector": "", + "Receiver": "", + "RelayerCelerIM": "", + "ServiceFeeCollector": "" + } + } +} \ No newline at end of file diff --git a/deployments/base.staging.json b/deployments/base.staging.json new file mode 100644 index 000000000..b13c1e4e8 --- /dev/null +++ b/deployments/base.staging.json @@ -0,0 +1,17 @@ +{ + "DiamondCutFacet": "0x0E7C95Cd5087B9cf28848938544794Df18060364", + "DiamondLoupeFacet": "0xa07464a132C0C3588ff3e2857920267072407B18", + "OwnershipFacet": "0x8d9A296ABcfe7F3a4046633eD0685D12264628Cf", + "AccessManagerFacet": "0x769b85186cB944F33deFE2C57e389EE20a136Ef0", + "DexManagerFacet": "0x9530d861dbe3002d6466541aA95AA77f77657c74", + "PeripheryRegistryFacet": "0x78c16da94Ba24F62e340653e2a4eBf960B9C5771", + "StandardizedCallFacet": "0x09071b88aDf0E95D064296d635ca63a1Cc06c304", + "CalldataVerificationFacet": "0x438c07D8a3121619a3DAF24795E59700d94afe55", + "WithdrawFacet": "0x3b77FCFaD7426C905050dB60e880EfC2825e07B5", + "LiFiDiamond": "0x276Ba9B74EaB0c0B544CC6506Db71a956E4B1282", + "ERC20Proxy": "0x133E6EA10e1c70C17D957A3c36078bEc443ef7D5", + "LIFuelFacet": "0x3A9e75FD1EDd3746572A3932FCf2d1084ECf9EC8", + "GenericSwapFacet": "0x9BFADfb17157599a6ac0Cd3aADb4d5b06c5ec405", + "Executor": "0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b", + "CCIPMsgReceiver": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976" +} \ No newline at end of file diff --git a/deployments/mainnet.diamond.staging.json b/deployments/mainnet.diamond.staging.json index 728c29bc5..798ce1722 100644 --- a/deployments/mainnet.diamond.staging.json +++ b/deployments/mainnet.diamond.staging.json @@ -63,7 +63,7 @@ }, "0xe7072402217EfF9b73cf457731cEE2A3824360dc": { "Name": "AllBridgeFacet", - "Version": "2.0.0" + "Version": "" }, "0x987f67811Ef841da0466746E10B4139Daff95053": { "Name": "ArbitrumBridgeFacet", @@ -93,18 +93,20 @@ "Name": "OptimismBridgeFacet", "Version": "1.0.0" }, - "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc": { + "0x9BE903AB2ad61dfC71f7E2171A3dBf0884a4cdBF": { "Name": "CCIPFacet", "Version": "0.0.1" } }, "Periphery": { + "CCIPMsgReceiver": "", "ERC20Proxy": "0x0654EbA982ec082036A3D0f59964D302f1ba5cdA", "Executor": "0xBe27F03C8e6a61E2a4B1EE7940dbcb9204744d1c", "FeeCollector": "0x9ca271A532392230EAe919Fb5460aEa9D9718424", + "LiFuelFeeCollector": "", "Receiver": "0xC850013FC01A264018D58D112000E32835D15fBC", "RelayerCelerIM": "", "ServiceFeeCollector": "0xf068cc770f32042Ff4a8fD196045641234dFaa47" } } -} +} \ No newline at end of file diff --git a/deployments/mainnet.staging.json b/deployments/mainnet.staging.json index 912ab7408..cd3cd54c3 100644 --- a/deployments/mainnet.staging.json +++ b/deployments/mainnet.staging.json @@ -29,5 +29,5 @@ "SquidFacet": "0x933A3AfE2087FB8F5c9EE9A033477C42CC14c18E", "SynapseBridgeFacet": "0x57F98A94AC66e197AF6776D5c094FF0da2C0B198", "ThorSwapFacet": "0xa696287F37d21D566B9A80AC29b2640FF910C176", - "CCIPFacet": "0x6982237928de8e4a1a3622226D3a9C2bd89515Fc" + "CCIPFacet": "0x9BE903AB2ad61dfC71f7E2171A3dBf0884a4cdBF" } \ No newline at end of file diff --git a/hardhat.config.ts b/hardhat.config.ts index 7b9a401ec..83de921c7 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -14,7 +14,7 @@ import { HardhatUserConfig } from 'hardhat/types' require('./tasks/generateDiamondABI.ts') -const PKEY = process.env.PRIVATE_KEY || null +const PKEY = process.env.PRIVATE_KEY_PRODUCTION || null function getRemappings() { return fs diff --git a/script/demoScripts/demoCCIP.ts b/script/demoScripts/demoCCIP.ts index 59a823031..972bdc951 100644 --- a/script/demoScripts/demoCCIP.ts +++ b/script/demoScripts/demoCCIP.ts @@ -1,4 +1,4 @@ -import { providers, Wallet, utils, constants } from 'ethers' +import { providers, Wallet, utils, constants, Contract } from 'ethers' import { CCIPFacet__factory, ERC20__factory } from '../../typechain' import chalk from 'chalk' import dotenv from 'dotenv' @@ -11,6 +11,10 @@ const msg = (msg: string) => { const LIFI_ADDRESS = '0xbEbCDb5093B47Cd7add8211E4c77B6826aF7bc5F' // LiFiDiamond address on MAINNET stating const R_TOKEN_ADDRESS = '0x183015a9ba6ff60230fdeadc3f43b3d788b13e21' +const R_TOKEN_ADDRESS_BASE = '0xaFB2820316e7Bc5Ef78d295AB9b8Bb2257534576' +const USDC_TOKEN_ADDRESS_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' +const UNISWAP_ADDRESS = '0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43' +const CCIP_MSG_RECEIVER_ADDR = '0x867C971a7411eE369EA18d282Df06393236bAb77' const L2_GAS = 20000 // L2 Gas, Don't need to change it. const destinationChainId = 8453 // Base Chain @@ -18,7 +22,11 @@ async function main() { const jsonProvider = new providers.JsonRpcProvider( process.env.ETH_NODE_URI_MAINNET ) + const jsonProvider2 = new providers.JsonRpcProvider( + process.env.ETH_NODE_URI_BASE + ) const provider = new providers.FallbackProvider([jsonProvider]) + const provider2 = new providers.FallbackProvider([jsonProvider2]) let wallet = new Wallet(process.env.PRIVATE_KEY) wallet = wallet.connect(provider) @@ -40,17 +48,59 @@ async function main() { minAmount: amount, destinationChainId: destinationChainId, hasSourceSwaps: false, - hasDestinationCall: false, + hasDestinationCall: true, } // Bridge ERC20 lifiData.sendingAssetId = R_TOKEN_ADDRESS - const extraArgs = await lifi.encodeDestinationArgs(L2_GAS, false) + const extraArgs = await lifi.encodeDestinationArgs(0, false) + + // Swap Data + const uniswap = new Contract(UNISWAP_ADDRESS, [ + 'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)', + ]) + const path = [R_TOKEN_ADDRESS_BASE, USDC_TOKEN_ADDRESS_BASE] + const deadline = Math.floor(Date.now() / 1000) + 60 * 20 // 20 minutes from the current Unix time + + const amountOutMin = utils.parseEther('0.99') + const usdcAmountOutMin = utils.parseUnits('0.98', 6) + + const swapData = await uniswap.populateTransaction.swapExactTokensForTokens( + amountOutMin, + usdcAmountOutMin, + path, + CCIP_MSG_RECEIVER_ADDR, + deadline + ) + + const payload = utils.defaultAbiCoder.encode( + [ + 'bytes32', + 'tuple(address callTo, address approveTo, address sendingAssetId, address receivingAssetId, uint256 fromAmount, bytes callData, bool requiresDeposit)[]', + 'address', + ], + [ + lifiData.transactionId, + [ + { + callTo: swapData.to, + approveTo: swapData.to, + sendingAssetId: R_TOKEN_ADDRESS_BASE, + receivingAssetId: USDC_TOKEN_ADDRESS_BASE, + fromAmount: amountOutMin, + callData: swapData?.data, + requiresDeposit: true, + }, + ], + walletAddress, + ] + ) const bridgeData = { - callData: '0x', + callData: payload, extraArgs, + receiver: CCIP_MSG_RECEIVER_ADDR, } const fee = await lifi.quoteCCIPFee(lifiData, bridgeData) diff --git a/script/deploy/_targetState.json b/script/deploy/_targetState.json index 8d50a2663..e921c4dc5 100644 --- a/script/deploy/_targetState.json +++ b/script/deploy/_targetState.json @@ -315,6 +315,13 @@ "StargateFacet": "2.2.0", "HopFacetOptimized": "2.0.0" } + }, + "staging": { + "LiFiDiamond": { + "ERC20Proxy": "1.0.0", + "Executor": "2.0.0", + "CCIPMsgReceiver": "1.0.0" + } } }, "bsc": { diff --git a/script/deploy/facets/DeployCCIPMsgReceiver.s.sol b/script/deploy/facets/DeployCCIPMsgReceiver.s.sol new file mode 100644 index 000000000..99d6ccdfb --- /dev/null +++ b/script/deploy/facets/DeployCCIPMsgReceiver.s.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { CCIPMsgReceiver } from "lifi/Periphery/CCIPMsgReceiver.sol"; + +contract DeployScript is DeployScriptBase { + using stdJson for string; + + constructor() DeployScriptBase("CCIPMsgReceiver") {} + + function run() + public + returns (CCIPMsgReceiver deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + + deployed = CCIPMsgReceiver(deploy(type(CCIPMsgReceiver).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 + address refundWalletAddress = globalConfigJson.readAddress( + ".refundWallet" + ); + + // obtain address of Stargate router in current network from config file + string memory path = string.concat(root, "/config/ccip.json"); + string memory json = vm.readFile(path); + + address ccipRouter = json.readAddress( + string.concat(".routers.", network, ".router") + ); + + path = string.concat( + root, + "/deployments/", + network, + ".", + fileSuffix, + "json" + ); + json = vm.readFile(path); + address executor = json.readAddress(".Executor"); + + return abi.encode(refundWalletAddress, ccipRouter, executor, 100000); + } +} diff --git a/src/Facets/CCIPFacet.sol b/src/Facets/CCIPFacet.sol index b129e6b03..e5c8a590e 100644 --- a/src/Facets/CCIPFacet.sol +++ b/src/Facets/CCIPFacet.sol @@ -32,6 +32,7 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { struct CCIPData { bytes callData; bytes extraArgs; + address receiver; } /// @dev Local storage layout for CCIP @@ -148,7 +149,7 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(_bridgeData.receiver), + receiver: abi.encode(_ccipData.receiver), data: _ccipData.callData, tokenAmounts: amounts, feeToken: address(0), @@ -194,7 +195,7 @@ contract CCIPFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(_bridgeData.receiver), + receiver: abi.encode(_ccipData.receiver), data: _ccipData.callData, tokenAmounts: amounts, feeToken: address(0), diff --git a/src/Periphery/CCIPReceiver.sol b/src/Periphery/CCIPMsgReceiver.sol similarity index 97% rename from src/Periphery/CCIPReceiver.sol rename to src/Periphery/CCIPMsgReceiver.sol index 494f770ae..cafa7cc5c 100644 --- a/src/Periphery/CCIPReceiver.sol +++ b/src/Periphery/CCIPMsgReceiver.sol @@ -16,8 +16,8 @@ import { CCIPReceiver as CCIPRcvr } from "@chainlink/contracts-ccip/src/v0.8/cci /// @author LI.FI (https://li.fi) /// @notice Arbitrary execution contract used for cross-chain swaps // and message passing via Chainlink CCIP -/// @custom:version 2.0.2 -contract CCIPReceiver is +/// @custom:version 0.0.1 +contract CCIPMsgReceiver is CCIPRcvr, ILiFi, ReentrancyGuard, @@ -76,12 +76,8 @@ contract CCIPReceiver is ( bytes32 transactionId, LibSwap.SwapData[] memory swapData, - , address receiver - ) = abi.decode( - message.data, - (bytes32, LibSwap.SwapData[], address, address) - ); + ) = abi.decode(message.data, (bytes32, LibSwap.SwapData[], address)); _swapAndCompleteBridgeTokens( transactionId, swapData, diff --git a/test/solidity/Facets/CCIPFacet.t.sol b/test/solidity/Facets/CCIPFacet.t.sol index 72635eb8a..eeead3c4d 100644 --- a/test/solidity/Facets/CCIPFacet.t.sol +++ b/test/solidity/Facets/CCIPFacet.t.sol @@ -116,7 +116,7 @@ contract CCIPFacetTest is Test, DiamondTest { hasSourceSwaps: false, hasDestinationCall: false }); - validCCIPData = CCIPFacet.CCIPData("", ""); + validCCIPData = CCIPFacet.CCIPData("", "", address(this)); } function testRevertToBridgeTokensWhenSendingAmountIsZero() public { diff --git a/test/solidity/Periphery/CCIPReceiver.t.sol b/test/solidity/Periphery/CCIPMsgReceiver.t.sol similarity index 91% rename from test/solidity/Periphery/CCIPReceiver.t.sol rename to test/solidity/Periphery/CCIPMsgReceiver.t.sol index 95ace10ab..14b011ee3 100644 --- a/test/solidity/Periphery/CCIPReceiver.t.sol +++ b/test/solidity/Periphery/CCIPMsgReceiver.t.sol @@ -3,16 +3,16 @@ pragma solidity 0.8.17; import { Test, TestBase, LiFiDiamond, DSTest, ILiFi, LibSwap, LibAllowList, console, InvalidAmount, ERC20, UniswapV2Router02 } from "../utils/TestBase.sol"; import { OnlyContractOwner } from "src/Errors/GenericErrors.sol"; -import { CCIPReceiver } from "lifi/Periphery/CCIPReceiver.sol"; +import { CCIPMsgReceiver } from "lifi/Periphery/CCIPMsgReceiver.sol"; import { stdJson } from "forge-std/Script.sol"; import { ERC20Proxy } from "lifi/Periphery/ERC20Proxy.sol"; import { Executor } from "lifi/Periphery/Executor.sol"; import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; -contract CCIPReceiverTest is TestBase { +contract CCIPMsgReceiverTest is TestBase { using stdJson for string; - CCIPReceiver internal receiver; + CCIPMsgReceiver internal receiver; error UnAuthorized(); @@ -38,7 +38,7 @@ contract CCIPReceiverTest is TestBase { erc20Proxy = new ERC20Proxy(address(this)); executor = new Executor(address(erc20Proxy)); - receiver = new CCIPReceiver( + receiver = new CCIPMsgReceiver( address(this), ccipRouter, address(executor), @@ -103,14 +103,9 @@ contract CCIPReceiverTest is TestBase { }) ); - // create callData that will be sent to our CCIPReceiver + // create callData that will be sent to our CCIPMsgReceiver bytes32 txId = keccak256("txId"); - bytes memory payload = abi.encode( - txId, - swapData, - USER_RECEIVER, - USER_RECEIVER - ); + bytes memory payload = abi.encode(txId, swapData, USER_RECEIVER); // fund receiver with sufficient DAI to execute swap vm.startPrank(USER_DAI_WHALE); @@ -161,18 +156,13 @@ contract CCIPReceiverTest is TestBase { } function test_CCIP_EmitsCorrectEventOnRecovery() public { - // (mock) transfer "bridged funds" to CCIPReceiver.sol + // (mock) transfer "bridged funds" to CCIPMsgReceiver.sol bytes32 txId = keccak256("txId"); vm.startPrank(USER_SENDER); usdc.transfer(address(receiver), defaultUSDCAmount); vm.stopPrank(); - bytes memory payload = abi.encode( - txId, - swapData, - address(1), - address(1) - ); + bytes memory payload = abi.encode(txId, swapData, address(1)); vm.startPrank(ccipRouter); vm.expectEmit(true, true, true, true, address(receiver)); From 05d6a18d8e50f3782621be4740edc3e41a5b6042 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 30 Oct 2023 11:00:38 +0300 Subject: [PATCH 16/20] Remove unneeded payload args --- script/demoScripts/demoCCIP.ts | 9 +++++---- src/Periphery/CCIPMsgReceiver.sol | 9 +++------ test/solidity/Periphery/CCIPMsgReceiver.t.sol | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/script/demoScripts/demoCCIP.ts b/script/demoScripts/demoCCIP.ts index 972bdc951..448a7b550 100644 --- a/script/demoScripts/demoCCIP.ts +++ b/script/demoScripts/demoCCIP.ts @@ -43,7 +43,7 @@ async function main() { bridge: 'CCIP', integrator: 'ACME Devs', referrer: constants.AddressZero, - sendingAssetId: constants.AddressZero, + sendingAssetId: R_TOKEN_ADDRESS, receiver: walletAddress, minAmount: amount, destinationChainId: destinationChainId, @@ -54,17 +54,17 @@ async function main() { // Bridge ERC20 lifiData.sendingAssetId = R_TOKEN_ADDRESS - const extraArgs = await lifi.encodeDestinationArgs(0, false) + const extraArgs = await lifi.encodeDestinationArgs(1000000, false) // Swap Data const uniswap = new Contract(UNISWAP_ADDRESS, [ 'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)', ]) const path = [R_TOKEN_ADDRESS_BASE, USDC_TOKEN_ADDRESS_BASE] - const deadline = Math.floor(Date.now() / 1000) + 60 * 20 // 20 minutes from the current Unix time + const deadline = Math.floor(Date.now() / 1000) + 60 * 45 // 45 minutes from the current Unix time const amountOutMin = utils.parseEther('0.99') - const usdcAmountOutMin = utils.parseUnits('0.98', 6) + const usdcAmountOutMin = utils.parseUnits('0.95', 6) const swapData = await uniswap.populateTransaction.swapExactTokensForTokens( amountOutMin, @@ -119,6 +119,7 @@ async function main() { }) msg('TX Sent. Waiting for receipt...') await tx.wait() + msg('TX Hash: ' + tx.hash) msg('Done!') } diff --git a/src/Periphery/CCIPMsgReceiver.sol b/src/Periphery/CCIPMsgReceiver.sol index cafa7cc5c..21753b95f 100644 --- a/src/Periphery/CCIPMsgReceiver.sol +++ b/src/Periphery/CCIPMsgReceiver.sol @@ -73,16 +73,13 @@ contract CCIPMsgReceiver is Client.Any2EVMMessage memory message ) internal override { // Extract swap data from message - ( - bytes32 transactionId, - LibSwap.SwapData[] memory swapData, - address receiver - ) = abi.decode(message.data, (bytes32, LibSwap.SwapData[], address)); + (bytes32 transactionId, LibSwap.SwapData[] memory swapData) = abi + .decode(message.data, (bytes32, LibSwap.SwapData[])); _swapAndCompleteBridgeTokens( transactionId, swapData, message.destTokenAmounts[0].token, - payable(receiver), + payable(msg.sender), message.destTokenAmounts[0].amount ); } diff --git a/test/solidity/Periphery/CCIPMsgReceiver.t.sol b/test/solidity/Periphery/CCIPMsgReceiver.t.sol index 14b011ee3..ba02a0f92 100644 --- a/test/solidity/Periphery/CCIPMsgReceiver.t.sol +++ b/test/solidity/Periphery/CCIPMsgReceiver.t.sol @@ -105,7 +105,7 @@ contract CCIPMsgReceiverTest is TestBase { // create callData that will be sent to our CCIPMsgReceiver bytes32 txId = keccak256("txId"); - bytes memory payload = abi.encode(txId, swapData, USER_RECEIVER); + bytes memory payload = abi.encode(txId, swapData); // fund receiver with sufficient DAI to execute swap vm.startPrank(USER_DAI_WHALE); @@ -162,14 +162,14 @@ contract CCIPMsgReceiverTest is TestBase { usdc.transfer(address(receiver), defaultUSDCAmount); vm.stopPrank(); - bytes memory payload = abi.encode(txId, swapData, address(1)); + bytes memory payload = abi.encode(txId, swapData); vm.startPrank(ccipRouter); vm.expectEmit(true, true, true, true, address(receiver)); emit LiFiTransferRecovered( txId, ADDRESS_USDC, - address(1), + address(receiver), defaultUSDCAmount, block.timestamp ); From 650ae7ad48cb19f546c77e49357e10b78d30ed50 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 30 Oct 2023 11:07:56 +0300 Subject: [PATCH 17/20] Update demo script --- script/demoScripts/demoCCIP.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/script/demoScripts/demoCCIP.ts b/script/demoScripts/demoCCIP.ts index 448a7b550..c68d21f4d 100644 --- a/script/demoScripts/demoCCIP.ts +++ b/script/demoScripts/demoCCIP.ts @@ -78,7 +78,6 @@ async function main() { [ 'bytes32', 'tuple(address callTo, address approveTo, address sendingAssetId, address receivingAssetId, uint256 fromAmount, bytes callData, bool requiresDeposit)[]', - 'address', ], [ lifiData.transactionId, @@ -93,7 +92,6 @@ async function main() { requiresDeposit: true, }, ], - walletAddress, ] ) From c3470e48b9f18264a159ede7ad3b368ae4a333aa Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 30 Oct 2023 11:15:18 +0300 Subject: [PATCH 18/20] Redeploy to staging --- deployments/_deployments_log_file.json | 4 ++-- deployments/base.diamond.staging.json | 2 +- deployments/base.staging.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 79c082edf..153cda0c0 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -14520,9 +14520,9 @@ "staging": { "0.0.1": [ { - "ADDRESS": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976", + "ADDRESS": "0x409b24418bBF1F293B5EaF33b8aC99147A01626b", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-27 15:56:47", + "TIMESTAMP": "2023-10-30 11:14:40", "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f000000000000000000000000673aa85efd75080031d44fca061575d1da427a28000000000000000000000000eced7eb0ff5c63de883f7dee66af24f58fac417b00000000000000000000000000000000000000000000000000000000000186a0", "SALT": "", "VERIFIED": "true" diff --git a/deployments/base.diamond.staging.json b/deployments/base.diamond.staging.json index d9002d8e4..8fed6302c 100644 --- a/deployments/base.diamond.staging.json +++ b/deployments/base.diamond.staging.json @@ -47,7 +47,7 @@ } }, "Periphery": { - "CCIPMsgReceiver": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976", + "CCIPMsgReceiver": "0x409b24418bBF1F293B5EaF33b8aC99147A01626b", "ERC20Proxy": "0x133E6EA10e1c70C17D957A3c36078bEc443ef7D5", "Executor": "0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b", "FeeCollector": "", diff --git a/deployments/base.staging.json b/deployments/base.staging.json index b13c1e4e8..198b368a6 100644 --- a/deployments/base.staging.json +++ b/deployments/base.staging.json @@ -13,5 +13,5 @@ "LIFuelFacet": "0x3A9e75FD1EDd3746572A3932FCf2d1084ECf9EC8", "GenericSwapFacet": "0x9BFADfb17157599a6ac0Cd3aADb4d5b06c5ec405", "Executor": "0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b", - "CCIPMsgReceiver": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976" + "CCIPMsgReceiver": "0x409b24418bBF1F293B5EaF33b8aC99147A01626b" } \ No newline at end of file From fa77981003f000f785279948da8f1aaa34caf35b Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 30 Oct 2023 14:54:50 +0300 Subject: [PATCH 19/20] Revert back to previous version --- deployments/_deployments_log_file.json | 6 +++--- deployments/base.diamond.staging.json | 2 +- deployments/base.staging.json | 2 +- script/demoScripts/demoCCIP.ts | 10 ++++++---- src/Periphery/CCIPMsgReceiver.sol | 9 ++++++--- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 153cda0c0..741ab1809 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -14520,12 +14520,12 @@ "staging": { "0.0.1": [ { - "ADDRESS": "0x409b24418bBF1F293B5EaF33b8aC99147A01626b", + "ADDRESS": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2023-10-30 11:14:40", + "TIMESTAMP": "2023-10-30 14:54:10", "CONSTRUCTOR_ARGS": "0x000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f000000000000000000000000673aa85efd75080031d44fca061575d1da427a28000000000000000000000000eced7eb0ff5c63de883f7dee66af24f58fac417b00000000000000000000000000000000000000000000000000000000000186a0", "SALT": "", - "VERIFIED": "true" + "VERIFIED": "false" } ] } diff --git a/deployments/base.diamond.staging.json b/deployments/base.diamond.staging.json index 8fed6302c..d9002d8e4 100644 --- a/deployments/base.diamond.staging.json +++ b/deployments/base.diamond.staging.json @@ -47,7 +47,7 @@ } }, "Periphery": { - "CCIPMsgReceiver": "0x409b24418bBF1F293B5EaF33b8aC99147A01626b", + "CCIPMsgReceiver": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976", "ERC20Proxy": "0x133E6EA10e1c70C17D957A3c36078bEc443ef7D5", "Executor": "0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b", "FeeCollector": "", diff --git a/deployments/base.staging.json b/deployments/base.staging.json index 198b368a6..b13c1e4e8 100644 --- a/deployments/base.staging.json +++ b/deployments/base.staging.json @@ -13,5 +13,5 @@ "LIFuelFacet": "0x3A9e75FD1EDd3746572A3932FCf2d1084ECf9EC8", "GenericSwapFacet": "0x9BFADfb17157599a6ac0Cd3aADb4d5b06c5ec405", "Executor": "0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b", - "CCIPMsgReceiver": "0x409b24418bBF1F293B5EaF33b8aC99147A01626b" + "CCIPMsgReceiver": "0x76ad5bC7B89E951915010b5d5d24d045dC15D976" } \ No newline at end of file diff --git a/script/demoScripts/demoCCIP.ts b/script/demoScripts/demoCCIP.ts index c68d21f4d..9ad0bb8b2 100644 --- a/script/demoScripts/demoCCIP.ts +++ b/script/demoScripts/demoCCIP.ts @@ -14,8 +14,8 @@ const R_TOKEN_ADDRESS = '0x183015a9ba6ff60230fdeadc3f43b3d788b13e21' const R_TOKEN_ADDRESS_BASE = '0xaFB2820316e7Bc5Ef78d295AB9b8Bb2257534576' const USDC_TOKEN_ADDRESS_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' const UNISWAP_ADDRESS = '0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43' -const CCIP_MSG_RECEIVER_ADDR = '0x867C971a7411eE369EA18d282Df06393236bAb77' -const L2_GAS = 20000 // L2 Gas, Don't need to change it. +const CCIP_MSG_RECEIVER_ADDR = '0x76ad5bC7B89E951915010b5d5d24d045dC15D976' +const EXECUTOR_ADDR = '0xECeD7eB0FF5c63DE883F7DEe66af24f58faC417b' const destinationChainId = 8453 // Base Chain async function main() { @@ -63,14 +63,14 @@ async function main() { const path = [R_TOKEN_ADDRESS_BASE, USDC_TOKEN_ADDRESS_BASE] const deadline = Math.floor(Date.now() / 1000) + 60 * 45 // 45 minutes from the current Unix time - const amountOutMin = utils.parseEther('0.99') + const amountOutMin = utils.parseEther('1') const usdcAmountOutMin = utils.parseUnits('0.95', 6) const swapData = await uniswap.populateTransaction.swapExactTokensForTokens( amountOutMin, usdcAmountOutMin, path, - CCIP_MSG_RECEIVER_ADDR, + EXECUTOR_ADDR, deadline ) @@ -78,6 +78,7 @@ async function main() { [ 'bytes32', 'tuple(address callTo, address approveTo, address sendingAssetId, address receivingAssetId, uint256 fromAmount, bytes callData, bool requiresDeposit)[]', + 'address', ], [ lifiData.transactionId, @@ -92,6 +93,7 @@ async function main() { requiresDeposit: true, }, ], + walletAddress, ] ) diff --git a/src/Periphery/CCIPMsgReceiver.sol b/src/Periphery/CCIPMsgReceiver.sol index 21753b95f..cafa7cc5c 100644 --- a/src/Periphery/CCIPMsgReceiver.sol +++ b/src/Periphery/CCIPMsgReceiver.sol @@ -73,13 +73,16 @@ contract CCIPMsgReceiver is Client.Any2EVMMessage memory message ) internal override { // Extract swap data from message - (bytes32 transactionId, LibSwap.SwapData[] memory swapData) = abi - .decode(message.data, (bytes32, LibSwap.SwapData[])); + ( + bytes32 transactionId, + LibSwap.SwapData[] memory swapData, + address receiver + ) = abi.decode(message.data, (bytes32, LibSwap.SwapData[], address)); _swapAndCompleteBridgeTokens( transactionId, swapData, message.destTokenAmounts[0].token, - payable(msg.sender), + payable(receiver), message.destTokenAmounts[0].amount ); } From f9211b6ab2de571c8b2ddafd96676d3807766f0f Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 30 Oct 2023 16:17:49 +0300 Subject: [PATCH 20/20] Update demo script --- script/demoScripts/demoCCIP.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/demoScripts/demoCCIP.ts b/script/demoScripts/demoCCIP.ts index 9ad0bb8b2..f025ccd0a 100644 --- a/script/demoScripts/demoCCIP.ts +++ b/script/demoScripts/demoCCIP.ts @@ -64,7 +64,7 @@ async function main() { const deadline = Math.floor(Date.now() / 1000) + 60 * 45 // 45 minutes from the current Unix time const amountOutMin = utils.parseEther('1') - const usdcAmountOutMin = utils.parseUnits('0.95', 6) + const usdcAmountOutMin = utils.parseUnits('0.80', 6) const swapData = await uniswap.populateTransaction.swapExactTokensForTokens( amountOutMin,