From 2e5597534e23bca91a65b291a9790888381d32fc Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 29 Oct 2024 10:21:05 +0300 Subject: [PATCH 01/45] Scaffold Relay Facet --- config/relay.json | 16 +++ docs/RelayFacet.md | 92 ++++++++++++++ script/deploy/facets/DeployRelayFacet.s.sol | 33 ++++++ script/deploy/facets/UpdateRelayFacet.s.sol | 24 ++++ src/Facets/RelayFacet.sol | 123 +++++++++++++++++++ templates/facetDeployScript.template.hbs | 2 +- test/solidity/Facets/RelayFacet.t.sol | 125 ++++++++++++++++++++ 7 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 config/relay.json create mode 100644 docs/RelayFacet.md create mode 100644 script/deploy/facets/DeployRelayFacet.s.sol create mode 100644 script/deploy/facets/UpdateRelayFacet.s.sol create mode 100644 src/Facets/RelayFacet.sol create mode 100644 test/solidity/Facets/RelayFacet.t.sol diff --git a/config/relay.json b/config/relay.json new file mode 100644 index 000000000..bcf06aa51 --- /dev/null +++ b/config/relay.json @@ -0,0 +1,16 @@ +{ + "mainnet": { + "example": "0x0000000000000000000000000000000000000000", + "exampleAllowedTokens": [ + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ] + }, + "arbitrum": { + "example": "0x0000000000000000000000000000000000000000", + "exampleAllowedTokens": [ + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000" + ] + } +} diff --git a/docs/RelayFacet.md b/docs/RelayFacet.md new file mode 100644 index 000000000..7d42cfdf5 --- /dev/null +++ b/docs/RelayFacet.md @@ -0,0 +1,92 @@ +# Relay Facet + +## How it works + +The Relay Facet works by ... + +```mermaid +graph LR; + D{LiFiDiamond}-- DELEGATECALL -->RelayFacet; + RelayFacet -- CALL --> C(Relay) +``` + +## Public Methods + +- `function startBridgeTokensViaRelay(BridgeData calldata _bridgeData, RelayData calldata _relayData)` + - Simply bridges tokens using relay +- `swapAndStartBridgeTokensViarelay(BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, relayData memory _relayData)` + - Performs swap(s) before bridging tokens using relay + +## relay Specific Parameters + +The methods listed above take a variable labeled `_relayData`. This data is specific to relay and is represented as the following struct type: + +```solidity +/// @param example Example parameter. +struct relayData { + 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 various 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: 'relay', // 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=relay&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=relay&fromAddress={YOUR_WALLET_ADDRESS}' +``` diff --git a/script/deploy/facets/DeployRelayFacet.s.sol b/script/deploy/facets/DeployRelayFacet.s.sol new file mode 100644 index 000000000..5ca9c47a4 --- /dev/null +++ b/script/deploy/facets/DeployRelayFacet.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { DeployScriptBase } from "./utils/DeployScriptBase.sol"; +import { stdJson } from "forge-std/Script.sol"; +import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; + +contract DeployScript is DeployScriptBase { + using stdJson for string; + + constructor() DeployScriptBase("RelayFacet") {} + + function run() + public + returns (RelayFacet deployed, bytes memory constructorArgs) + { + constructorArgs = getConstructorArgs(); + + deployed = RelayFacet(deploy(type(RelayFacet).creationCode)); + } + + function getConstructorArgs() internal override returns (bytes memory) { + // If you don't have a constructor or it doesn't take any arguments, you can remove this function + string memory path = string.concat(root, "/config/relay.json"); + string memory json = vm.readFile(path); + + address example = json.readAddress( + string.concat(".", network, ".example") + ); + + return abi.encode(example); + } +} diff --git a/script/deploy/facets/UpdateRelayFacet.s.sol b/script/deploy/facets/UpdateRelayFacet.s.sol new file mode 100644 index 000000000..d6703d58a --- /dev/null +++ b/script/deploy/facets/UpdateRelayFacet.s.sol @@ -0,0 +1,24 @@ +// 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 { RelayFacet } from "lifi/Facets/RelayFacet.sol"; + +contract DeployScript is UpdateScriptBase { + using stdJson for string; + + struct Config { + uint256 a; + bool b; + address c; + } + + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("RelayFacet"); + } +} diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol new file mode 100644 index 000000000..c2e331946 --- /dev/null +++ b/src/Facets/RelayFacet.sol @@ -0,0 +1,123 @@ +// 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 Relay Facet +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for bridging through Relay Protocol +/// @custom:version 1.0.0 +contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { + /// Storage /// + + // Receiver for native transfers + address public immutable relayReceiver; + address public immutable relaySolver; + + /// Types /// + + /// @dev Optional bridge specific struct + /// @param exampleParam Example parameter + struct RelayData { + bytes32 requestId; + address receivingAssetId; + bytes signature; + } + + /// Modifiers /// + modifier isValidQuote( + ILiFi.BridgeData calldata _bridgeData, + RelayData calldata _relayData + ) { + // TODO: Verify the following + // requestId bytes32 + // originChainId uint256 + // sender bytes32(address) + // sendingAssetId bytes32(address) + // dstChainId uint256 + // receiver bytes32(address) + // receivingAssetId bytes32(address) + _; + } + + /// Constructor /// + + constructor(address _relayReceiver, address _relaySolver) { + relayReceiver = _relayReceiver; + relaySolver = _relaySolver; + } + + /// External Methods /// + + /// @notice Bridges tokens via Relay + /// @param _bridgeData The core information needed for bridging + /// @param _relayData Data specific to Relay + function startBridgeTokensViaRelay( + ILiFi.BridgeData memory _bridgeData, + RelayData calldata _relayData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + validateBridgeData(_bridgeData) + doesNotContainSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + { + LibAsset.depositAsset( + _bridgeData.sendingAssetId, + _bridgeData.minAmount + ); + _startBridge(_bridgeData, _relayData); + } + + /// @notice Performs a swap before bridging via Relay + /// @param _bridgeData The core information needed for bridging + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _relayData Data specific to Relay + function swapAndStartBridgeTokensViaRelay( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + RelayData calldata _relayData + ) + 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, _relayData); + } + + /// Internal Methods /// + + /// @dev Contains the business logic for the bridge via Relay + /// @param _bridgeData The core information needed for bridging + /// @param _relayData Data specific to Relay + function _startBridge( + ILiFi.BridgeData memory _bridgeData, + RelayData calldata _relayData + ) internal { + // check if sendingAsset is native or ERC20 + if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) { + // Native + } else { + // ERC20 + } + emit LiFiTransferStarted(_bridgeData); + } +} diff --git a/templates/facetDeployScript.template.hbs b/templates/facetDeployScript.template.hbs index 6e130576a..840e533a2 100644 --- a/templates/facetDeployScript.template.hbs +++ b/templates/facetDeployScript.template.hbs @@ -24,7 +24,7 @@ contract DeployScript is DeployScriptBase { string memory path = string.concat(root, "/config/{{camelCase name}}.json"); string memory json = vm.readFile(path); - address acrossSpokePool = json.readAddress( + address example = json.readAddress( string.concat(".", network, ".example") ); diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol new file mode 100644 index 000000000..54586f7ba --- /dev/null +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.17; + +import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFacet.sol"; +import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; + +// Stub RelayFacet Contract +contract TestRelayFacet is RelayFacet { + constructor(address _example) RelayFacet(_example, _example) {} + + function addDex(address _dex) external { + LibAllowList.addAllowedContract(_dex); + } + + function setFunctionApprovalBySignature(bytes4 _signature) external { + LibAllowList.addAllowedSelector(_signature); + } +} + +contract RelayFacetTest is TestBaseFacet { + RelayFacet.RelayData internal validRelayData; + TestRelayFacet internal relayFacet; + 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); + + relayFacet = new TestRelayFacet(EXAMPLE_PARAM); + // relayFacet.initRelay(EXAMPLE_ALLOWED_TOKENS); + bytes4[] memory functionSelectors = new bytes4[](4); + functionSelectors[0] = relayFacet.startBridgeTokensViaRelay.selector; + functionSelectors[1] = relayFacet + .swapAndStartBridgeTokensViaRelay + .selector; + functionSelectors[2] = relayFacet.addDex.selector; + functionSelectors[3] = relayFacet + .setFunctionApprovalBySignature + .selector; + + addFacet(diamond, address(relayFacet), functionSelectors); + relayFacet = TestRelayFacet(address(diamond)); + relayFacet.addDex(ADDRESS_UNISWAP); + relayFacet.setFunctionApprovalBySignature( + uniswap.swapExactTokensForTokens.selector + ); + relayFacet.setFunctionApprovalBySignature( + uniswap.swapTokensForExactETH.selector + ); + relayFacet.setFunctionApprovalBySignature( + uniswap.swapETHForExactTokens.selector + ); + + setFacetAddressInTestBase(address(relayFacet), "RelayFacet"); + + // adjust bridgeData + bridgeData.bridge = "relay"; + bridgeData.destinationChainId = 137; + + // produce valid RelayData + // validRelayData = RelayFacet.RelayData({ 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) { + relayFacet.startBridgeTokensViaRelay{ + value: bridgeData.minAmount + }(bridgeData, validRelayData); + } else { + relayFacet.startBridgeTokensViaRelay(bridgeData, validRelayData); + } + } + + function initiateSwapAndBridgeTxWithFacet( + bool isNative + ) internal override { + if (isNative) { + relayFacet.swapAndStartBridgeTokensViaRelay{ + value: swapData[0].fromAmount + }(bridgeData, swapData, validRelayData); + } else { + relayFacet.swapAndStartBridgeTokensViaRelay( + bridgeData, + swapData, + validRelayData + ); + } + } +} From 37e76b02802805a4ac56576c056baf36a7e125e3 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 29 Oct 2024 11:37:55 +0300 Subject: [PATCH 02/45] Add verification modifier --- src/Facets/RelayFacet.sol | 40 ++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index c2e331946..07db9260f 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -8,6 +8,7 @@ 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 { ECDSA } from "solady/utils/ECDSA.sol"; /// @title Relay Facet /// @author LI.FI (https://li.fi) @@ -30,19 +31,34 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { bytes signature; } + /// Errors /// + + error InvalidQuote(); + /// Modifiers /// - modifier isValidQuote( - ILiFi.BridgeData calldata _bridgeData, + + modifier onlyValidQuote( + ILiFi.BridgeData memory _bridgeData, RelayData calldata _relayData ) { - // TODO: Verify the following - // requestId bytes32 - // originChainId uint256 - // sender bytes32(address) - // sendingAssetId bytes32(address) - // dstChainId uint256 - // receiver bytes32(address) - // receivingAssetId bytes32(address) + // Verify that the bridging quote has been signed by the Relay solver + // as attested using the attestaion API + // API URL: https://api.relay.link/requests/{requestId}/signature/v2 + bytes32 hash = keccak256( + abi.encodePacked( + _relayData.requestId, + block.chainid, + bytes32(uint256(uint160(address(this)))), + bytes32(uint256(uint160(_bridgeData.sendingAssetId))), + _bridgeData.destinationChainId, + bytes32(uint256(uint160(_bridgeData.receiver))), + bytes32(uint256(uint160(_relayData.receivingAssetId))) + ) + ); + address signer = ECDSA.recover(hash, _relayData.signature); + if (signer != relaySolver) { + revert InvalidQuote(); + } _; } @@ -59,12 +75,13 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// @param _bridgeData The core information needed for bridging /// @param _relayData Data specific to Relay function startBridgeTokensViaRelay( - ILiFi.BridgeData memory _bridgeData, + ILiFi.BridgeData calldata _bridgeData, RelayData calldata _relayData ) external payable nonReentrant + onlyValidQuote(_bridgeData, _relayData) refundExcessNative(payable(msg.sender)) validateBridgeData(_bridgeData) doesNotContainSourceSwaps(_bridgeData) @@ -89,6 +106,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { external payable nonReentrant + onlyValidQuote(_bridgeData, _relayData) refundExcessNative(payable(msg.sender)) containsSourceSwaps(_bridgeData) doesNotContainDestinationCalls(_bridgeData) From c97020269ceff877302c272a740e29b1246ddd82 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 29 Oct 2024 11:55:14 +0300 Subject: [PATCH 03/45] Add calldata --- src/Facets/RelayFacet.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 07db9260f..faa8e3159 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -28,6 +28,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { struct RelayData { bytes32 requestId; address receivingAssetId; + bytes calldata; bytes signature; } @@ -133,6 +134,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { // check if sendingAsset is native or ERC20 if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) { // Native + // TODO: need to call the relayReceiver } else { // ERC20 } From 9c85cf611da951d69819335adf32c0d0050d1938 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 29 Oct 2024 12:10:03 +0300 Subject: [PATCH 04/45] Implement ERC20 --- src/Facets/RelayFacet.sol | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index faa8e3159..2e7ec0ab1 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -5,6 +5,7 @@ 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 { LibUtil } from "../Libraries/LibUtil.sol"; import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; import { SwapperV2 } from "../Helpers/SwapperV2.sol"; import { Validatable } from "../Helpers/Validatable.sol"; @@ -28,7 +29,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { struct RelayData { bytes32 requestId; address receivingAssetId; - bytes calldata; + bytes callData; bytes signature; } @@ -131,12 +132,30 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { ILiFi.BridgeData memory _bridgeData, RelayData calldata _relayData ) internal { + bytes memory quoteId = _relayData.callData[68:]; // check if sendingAsset is native or ERC20 if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) { // Native // TODO: need to call the relayReceiver } else { // ERC20 + + // We build the calldata from scratch to ensure that we can only + // send to the solver address + bytes memory transferCallData = bytes.concat( + abi.encodeWithSignature( + "transfer(address,uint256)", + relaySolver, + _bridgeData.minAmount + ), + quoteId + ); + (bool success, bytes memory reason) = address( + _bridgeData.sendingAssetId + ).call(transferCallData); + if (!success) { + revert(LibUtil.getRevertMsg(reason)); + } } emit LiFiTransferStarted(_bridgeData); } From 119b8b5d9f2b43e6a9efb3aa9845cf0da4a27f15 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 29 Oct 2024 18:00:12 +0300 Subject: [PATCH 05/45] Implement tests --- config/relay.json | 52 +++++++++++---- src/Facets/RelayFacet.sol | 11 +++- test/solidity/Facets/RelayFacet.t.sol | 93 ++++++++++++++------------- 3 files changed, 100 insertions(+), 56 deletions(-) diff --git a/config/relay.json b/config/relay.json index bcf06aa51..71334e1fe 100644 --- a/config/relay.json +++ b/config/relay.json @@ -1,16 +1,46 @@ { "mainnet": { - "example": "0x0000000000000000000000000000000000000000", - "exampleAllowedTokens": [ - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ] + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "optimism": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "polygon": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "boba": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" }, "arbitrum": { - "example": "0x0000000000000000000000000000000000000000", - "exampleAllowedTokens": [ - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000" - ] + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "avalanche": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "blast": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "mode": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "linea": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "taiko": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" + }, + "scroll": { + "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" } -} +} \ No newline at end of file diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 2e7ec0ab1..2a3298dca 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -132,12 +132,19 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { ILiFi.BridgeData memory _bridgeData, RelayData calldata _relayData ) internal { - bytes memory quoteId = _relayData.callData[68:]; // check if sendingAsset is native or ERC20 if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) { // Native - // TODO: need to call the relayReceiver + + // Send Native to relayReceiver along with requestId as extra data + (bool success, bytes memory reason) = relayReceiver.call{ + value: _bridgeData.minAmount + }(abi.encode(_relayData.requestId)); + if (!success) { + revert(LibUtil.getRevertMsg(reason)); + } } else { + bytes memory quoteId = _relayData.callData[68:]; // ERC20 // We build the calldata from scratch to ensure that we can only diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 54586f7ba..25f56e815 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -3,10 +3,14 @@ pragma solidity 0.8.17; import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFacet.sol"; import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; +import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; // Stub RelayFacet Contract contract TestRelayFacet is RelayFacet { - constructor(address _example) RelayFacet(_example, _example) {} + constructor( + address _relayReceiver, + address _relaySolver + ) RelayFacet(_relayReceiver, _relaySolver) {} function addDex(address _dex) external { LibAllowList.addAllowedContract(_dex); @@ -20,17 +24,15 @@ contract TestRelayFacet is RelayFacet { contract RelayFacetTest is TestBaseFacet { RelayFacet.RelayData internal validRelayData; TestRelayFacet internal relayFacet; - address internal EXAMPLE_PARAM = address(0xb33f); + address internal RELAY_RECEIVER = + 0xa5F565650890fBA1824Ee0F21EbBbF660a179934; + uint256 internal PRIVATE_KEY = 0x1234567890; + address RELAY_SOLVER = vm.addr(PRIVATE_KEY); function setUp() public { - customBlockNumberForForking = 17130542; + customBlockNumberForForking = 19767662; initTestBase(); - - address[] memory EXAMPLE_ALLOWED_TOKENS = new address[](2); - EXAMPLE_ALLOWED_TOKENS[0] = address(1); - EXAMPLE_ALLOWED_TOKENS[1] = address(2); - - relayFacet = new TestRelayFacet(EXAMPLE_PARAM); + relayFacet = new TestRelayFacet(RELAY_RECEIVER, RELAY_SOLVER); // relayFacet.initRelay(EXAMPLE_ALLOWED_TOKENS); bytes4[] memory functionSelectors = new bytes4[](4); functionSelectors[0] = relayFacet.startBridgeTokensViaRelay.selector; @@ -61,48 +63,26 @@ contract RelayFacetTest is TestBaseFacet { bridgeData.bridge = "relay"; bridgeData.destinationChainId = 137; - // produce valid RelayData - // validRelayData = RelayFacet.RelayData({ exampleParam: "foo bar baz" }); + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + receivingAssetId: 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174, // Polygon USDC + callData: "", + signature: "" + }); } - // 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 { + validRelayData.signature = signData(bridgeData, validRelayData); if (isNative) { relayFacet.startBridgeTokensViaRelay{ value: bridgeData.minAmount }(bridgeData, validRelayData); } else { + validRelayData.callData = abi.encodeWithSignature( + "transfer(address,uint256)", + RELAY_SOLVER, + bridgeData.minAmount + ); relayFacet.startBridgeTokensViaRelay(bridgeData, validRelayData); } } @@ -110,11 +90,17 @@ contract RelayFacetTest is TestBaseFacet { function initiateSwapAndBridgeTxWithFacet( bool isNative ) internal override { + validRelayData.signature = signData(bridgeData, validRelayData); if (isNative) { relayFacet.swapAndStartBridgeTokensViaRelay{ value: swapData[0].fromAmount }(bridgeData, swapData, validRelayData); } else { + validRelayData.callData = abi.encodeWithSignature( + "transfer(address,uint256)", + RELAY_SOLVER, + bridgeData.minAmount + ); relayFacet.swapAndStartBridgeTokensViaRelay( bridgeData, swapData, @@ -122,4 +108,25 @@ contract RelayFacetTest is TestBaseFacet { ); } } + + function signData( + ILiFi.BridgeData memory _bridgeData, + RelayFacet.RelayData memory _relayData + ) internal view returns (bytes memory) { + bytes32 hash = keccak256( + abi.encodePacked( + _relayData.requestId, + block.chainid, + bytes32(uint256(uint160(address(relayFacet)))), + bytes32(uint256(uint160(_bridgeData.sendingAssetId))), + _bridgeData.destinationChainId, + bytes32(uint256(uint160(_bridgeData.receiver))), + bytes32(uint256(uint160(_relayData.receivingAssetId))) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(PRIVATE_KEY, hash); + bytes memory signature = abi.encodePacked(r, s, v); + return signature; + } } From d669597d2cacef7555c174de333c338f3ba0db59 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 30 Oct 2024 11:27:32 +0300 Subject: [PATCH 06/45] Deploy to staging --- deployments/_deployments_log_file.json | 30 +++++++++++++++++++++ deployments/arbitrum.diamond.staging.json | 8 +++--- deployments/arbitrum.staging.json | 5 ++-- deployments/polygon.diamond.staging.json | 6 ++++- deployments/polygon.staging.json | 5 ++-- script/deploy/facets/DeployRelayFacet.s.sol | 10 ++++--- script/deploy/facets/UpdateRelayFacet.s.sol | 6 ----- 7 files changed, 52 insertions(+), 18 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index bba139377..672e53822 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -24344,5 +24344,35 @@ ] } } + }, + "RelayFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-10-30 11:11:53", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "polygon": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-10-30 11:17:41", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", + "SALT": "", + "VERIFIED": "true" + } + ] + } + } } } diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index 5080f6d14..8a91dba90 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -125,9 +125,9 @@ "Name": "", "Version": "" }, - "0x2b64B62cbCfB38560222eBcfbbc4e65eC34c8Ce8": { - "Name": "", - "Version": "" + "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a": { + "Name": "RelayFacet", + "Version": "1.0.0" }, "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95": { "Name": "AcrossFacetV3", @@ -148,8 +148,8 @@ "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", "LiFiDEXAggregator": "", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", - "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", + "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverStargateV2": "", "RelayerCelerIM": "0xa1Ed8783AC96385482092b82eb952153998e9b70", "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 69b369d95..bfc512cc5 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -48,5 +48,6 @@ "EmergencyPauseFacet": "0x17Bb203F42d8e404ac7E8dB6ff972B7E8473850b", "AcrossFacetV3": "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", - "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b" -} + "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", + "RelayFacet": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a" +} \ No newline at end of file diff --git a/deployments/polygon.diamond.staging.json b/deployments/polygon.diamond.staging.json index 83daeacfc..58755fd04 100644 --- a/deployments/polygon.diamond.staging.json +++ b/deployments/polygon.diamond.staging.json @@ -120,6 +120,10 @@ "0xE15C7585636e62b88bA47A40621287086E0c2E33": { "Name": "", "Version": "" + }, + "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a": { + "Name": "RelayFacet", + "Version": "1.0.0" } }, "Periphery": { @@ -128,8 +132,8 @@ "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", "LiFiDEXAggregator": "", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", - "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverAcrossV3": "", + "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverStargateV2": "", "RelayerCelerIM": "0xa1Ed8783AC96385482092b82eb952153998e9b70", "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" diff --git a/deployments/polygon.staging.json b/deployments/polygon.staging.json index 79d77d88d..5683a0133 100644 --- a/deployments/polygon.staging.json +++ b/deployments/polygon.staging.json @@ -46,5 +46,6 @@ "CelerCircleBridgeFacet": "0x371E073f6A09DCBEE1D2Ac56E940F878a0Ba9DAE", "HopFacetOptimized": "0xf82135385765f1324257ffF74489F16382EBBb8A", "SymbiosisFacet": "0x21571D628B0bCBeb954D5933A604eCac35bAF2c7", - "AcrossFacetV3": "0xe2e5428F972d9C0a5Ba433e0c402752b472dB248" -} + "AcrossFacetV3": "0xe2e5428F972d9C0a5Ba433e0c402752b472dB248", + "RelayFacet": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a" +} \ No newline at end of file diff --git a/script/deploy/facets/DeployRelayFacet.s.sol b/script/deploy/facets/DeployRelayFacet.s.sol index 5ca9c47a4..9fa6d4054 100644 --- a/script/deploy/facets/DeployRelayFacet.s.sol +++ b/script/deploy/facets/DeployRelayFacet.s.sol @@ -24,10 +24,14 @@ contract DeployScript is DeployScriptBase { string memory path = string.concat(root, "/config/relay.json"); string memory json = vm.readFile(path); - address example = json.readAddress( - string.concat(".", network, ".example") + address relayReceiver = json.readAddress( + string.concat(".", network, ".relayReceiver") ); - return abi.encode(example); + address relaySolver = json.readAddress( + string.concat(".", network, ".relaySolver") + ); + + return abi.encode(relayReceiver, relaySolver); } } diff --git a/script/deploy/facets/UpdateRelayFacet.s.sol b/script/deploy/facets/UpdateRelayFacet.s.sol index d6703d58a..f2ca8b4d4 100644 --- a/script/deploy/facets/UpdateRelayFacet.s.sol +++ b/script/deploy/facets/UpdateRelayFacet.s.sol @@ -9,12 +9,6 @@ import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; contract DeployScript is UpdateScriptBase { using stdJson for string; - struct Config { - uint256 a; - bool b; - address c; - } - function run() public returns (address[] memory facets, bytes memory cutData) From 7622a004768c1937b9a23542aa10840a95d4531e Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 30 Oct 2024 11:30:26 +0300 Subject: [PATCH 07/45] Add deployment requirements --- script/deploy/resources/deployRequirements.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/script/deploy/resources/deployRequirements.json b/script/deploy/resources/deployRequirements.json index 4d512122d..7159a583e 100644 --- a/script/deploy/resources/deployRequirements.json +++ b/script/deploy/resources/deployRequirements.json @@ -523,5 +523,19 @@ "allowToDeployWithZeroAddress": "false" } } + }, + "Relay": { + "configData": { + "_relayReceiver": { + "configFileName": "relay.json", + "keyInConfigFile": "..relayReceiver", + "allowToDeployWithZeroAddress": "false" + }, + "_relaySolver": { + "configFileName": "relay.json", + "keyInConfigFile": "..relaySolver", + "allowToDeployWithZeroAddress": "false" + } + } } -} +} \ No newline at end of file From 7d1411753fdbb76b0dc188d6bb3cbf1eb988f183 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 10:59:16 +0300 Subject: [PATCH 08/45] Implement demo for Relay --- config/relay.json | 4 +- deployments/_deployments_log_file.json | 4 +- deployments/arbitrum.diamond.staging.json | 2 +- deployments/arbitrum.staging.json | 2 +- script/demoScripts/demoRelay.ts | 173 ++++++++++++++++++++++ src/Facets/RelayFacet.sol | 23 +-- test/solidity/Facets/RelayFacet.t.sol | 23 +-- 7 files changed, 207 insertions(+), 24 deletions(-) create mode 100644 script/demoScripts/demoRelay.ts diff --git a/config/relay.json b/config/relay.json index 71334e1fe..6b68645c4 100644 --- a/config/relay.json +++ b/config/relay.json @@ -12,7 +12,7 @@ "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" }, "boba": { - "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relayReceiver": "0xa06e1351e2fd2d45b5d35633ca7ecf328684a109", "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" }, "arbitrum": { @@ -32,7 +32,7 @@ "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" }, "linea": { - "relayReceiver": "0xa5f565650890fba1824ee0f21ebbbf660a179934", + "relayReceiver": "0x00000000aa467eba42a3d604b3d74d63b2b6c6cb", "relaySolver": "0xf70da97812CB96acDF810712Aa562db8dfA3dbEF" }, "taiko": { diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 672e53822..c3a9f3a33 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -24350,9 +24350,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a", + "ADDRESS": "0x74763722d92832d247cFa92825b06098cf72BAA2", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-10-30 11:11:53", + "TIMESTAMP": "2024-10-31 10:21:46", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" diff --git a/deployments/arbitrum.diamond.staging.json b/deployments/arbitrum.diamond.staging.json index 8a91dba90..c5befa25d 100644 --- a/deployments/arbitrum.diamond.staging.json +++ b/deployments/arbitrum.diamond.staging.json @@ -125,7 +125,7 @@ "Name": "", "Version": "" }, - "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a": { + "0x74763722d92832d247cFa92825b06098cf72BAA2": { "Name": "RelayFacet", "Version": "1.0.0" }, diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index bfc512cc5..db8438c21 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -49,5 +49,5 @@ "AcrossFacetV3": "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", - "RelayFacet": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a" + "RelayFacet": "0x74763722d92832d247cFa92825b06098cf72BAA2" } \ No newline at end of file diff --git a/script/demoScripts/demoRelay.ts b/script/demoScripts/demoRelay.ts new file mode 100644 index 000000000..51715548e --- /dev/null +++ b/script/demoScripts/demoRelay.ts @@ -0,0 +1,173 @@ +import deployments from '../../deployments/arbitrum.staging.json' +import { + RelayFacet__factory, + ILiFi, + type RelayFacet, + ERC20__factory, +} from '../../typechain' +import { ethers, utils } from 'ethers' +import dotenv from 'dotenv' +dotenv.config() + +const main = async () => { + const RPC_URL = process.env.ETH_NODE_URI_ARBITRUM + const PRIVATE_KEY = process.env.PRIVATE_KEY + const LIFI_ADDRESS = deployments.LiFiDiamond + + const provider = new ethers.providers.JsonRpcProvider(RPC_URL) + const signer = new ethers.Wallet(PRIVATE_KEY as string, provider) + const relay = RelayFacet__factory.connect(LIFI_ADDRESS, provider) + + const address = await signer.getAddress() + + let tx + + // Bridge ETH + + let params = { + user: deployments.LiFiDiamond, + originChainId: 42161, + destinationChainId: 137, + originCurrency: '0x0000000000000000000000000000000000000000', + destinationCurrency: '0x0000000000000000000000000000000000000000', + recipient: address, + tradeType: 'EXACT_INPUT', + amount: '1000000000000000', + referrer: 'relay.link/swap', + useExternalLiquidity: false, + } + + // Bridge 0.001 ETH from ARB on POL + let resp = await fetch('https://api.relay.link/quote', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }) + let quote = await resp.json() + let requestId = quote.steps[0].requestId + console.log(quote) + + console.log(requestId) + let sigResp = await fetch( + `https://api.relay.link/requests/${requestId}/signature/v2`, + { headers: { 'Content-Type': 'application/json' } } + ) + let sigData = await sigResp.json() + console.log(sigData) + + let bridgeData: ILiFi.BridgeDataStruct = { + transactionId: utils.randomBytes(32), + bridge: 'Relay', + integrator: 'ACME Devs', + referrer: '0x0000000000000000000000000000000000000000', + sendingAssetId: '0x0000000000000000000000000000000000000000', + receiver: address, + minAmount: ethers.utils.parseEther('0.001'), + destinationChainId: 137, + hasSourceSwaps: false, + hasDestinationCall: false, + } + + let relayData: RelayFacet.RelayDataStruct = { + requestId, + receivingAssetId: '0x0000000000000000000000000000000000000000', + callData: '0x', + signature: sigData.signature, + } + + console.info('Dev Wallet Address: ', address) + console.info('Bridging ETH...') + tx = await relay + .connect(signer) + .startBridgeTokensViaRelay(bridgeData, relayData, { + value: ethers.utils.parseEther('0.001'), + }) + await tx.wait() + console.info('Bridged ETH') + + // Bridge USDC + + params = { + user: deployments.LiFiDiamond, + originChainId: 42161, + destinationChainId: 10, + originCurrency: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + destinationCurrency: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', + recipient: address, + tradeType: 'EXACT_INPUT', + amount: '5000000', + referrer: 'relay.link/swap', + useExternalLiquidity: false, + } + + // Bridge 0.001 ETH from ARB on POL + + resp = await fetch('https://api.relay.link/quote', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }) + quote = await resp.json() + requestId = quote.steps[0].requestId + console.log(quote) + + console.log(requestId) + sigResp = await fetch( + `https://api.relay.link/requests/${requestId}/signature/v2`, + { headers: { 'Content-Type': 'application/json' } } + ) + sigData = await sigResp.json() + console.log(sigData) + + const token = ERC20__factory.connect( + '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + provider + ) + + bridgeData = { + transactionId: utils.randomBytes(32), + bridge: 'Relay', + integrator: 'ACME Devs', + referrer: '0x0000000000000000000000000000000000000000', + sendingAssetId: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + receiver: address, + minAmount: '5000000', + destinationChainId: 10, + hasSourceSwaps: false, + hasDestinationCall: false, + } + + relayData = { + requestId, + receivingAssetId: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', + callData: quote.steps[0].items[0].data.data, + signature: sigData.signature, + } + + console.info('Dev Wallet Address: ', address) + console.info('Approving USDC...') + tx = await token.connect(signer).approve(LIFI_ADDRESS, '5000000') + await tx.wait() + console.info('Approved USDC') + console.info('Bridging USDC...') + tx = await relay + .connect(signer) + .startBridgeTokensViaRelay(bridgeData, relayData) + await tx.wait() + console.info('Bridged USDC') +} + +main() + .then(() => { + console.log('Success') + process.exit(0) + }) + .catch((error) => { + console.error('error') + console.error(error) + process.exit(1) + }) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 2a3298dca..b8bfc13b5 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -46,18 +46,23 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { // Verify that the bridging quote has been signed by the Relay solver // as attested using the attestaion API // API URL: https://api.relay.link/requests/{requestId}/signature/v2 - bytes32 hash = keccak256( + bytes32 message = keccak256( abi.encodePacked( - _relayData.requestId, - block.chainid, - bytes32(uint256(uint160(address(this)))), - bytes32(uint256(uint160(_bridgeData.sendingAssetId))), - _bridgeData.destinationChainId, - bytes32(uint256(uint160(_bridgeData.receiver))), - bytes32(uint256(uint160(_relayData.receivingAssetId))) + "\x19Ethereum Signed Message:\n32", + keccak256( + abi.encodePacked( + _relayData.requestId, + block.chainid, + bytes32(uint256(uint160(address(this)))), + bytes32(uint256(uint160(_bridgeData.sendingAssetId))), + _bridgeData.destinationChainId, + bytes32(uint256(uint160(_bridgeData.receiver))), + bytes32(uint256(uint160(_relayData.receivingAssetId))) + ) + ) ) ); - address signer = ECDSA.recover(hash, _relayData.signature); + address signer = ECDSA.recover(message, _relayData.signature); if (signer != relaySolver) { revert InvalidQuote(); } diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 25f56e815..e4014d770 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -113,19 +113,24 @@ contract RelayFacetTest is TestBaseFacet { ILiFi.BridgeData memory _bridgeData, RelayFacet.RelayData memory _relayData ) internal view returns (bytes memory) { - bytes32 hash = keccak256( + bytes32 message = keccak256( abi.encodePacked( - _relayData.requestId, - block.chainid, - bytes32(uint256(uint160(address(relayFacet)))), - bytes32(uint256(uint160(_bridgeData.sendingAssetId))), - _bridgeData.destinationChainId, - bytes32(uint256(uint160(_bridgeData.receiver))), - bytes32(uint256(uint160(_relayData.receivingAssetId))) + "\x19Ethereum Signed Message:\n32", + keccak256( + abi.encodePacked( + _relayData.requestId, + block.chainid, + bytes32(uint256(uint160(address(relayFacet)))), + bytes32(uint256(uint160(_bridgeData.sendingAssetId))), + _bridgeData.destinationChainId, + bytes32(uint256(uint160(_bridgeData.receiver))), + bytes32(uint256(uint160(_relayData.receivingAssetId))) + ) + ) ) ); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PRIVATE_KEY, hash); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(PRIVATE_KEY, message); bytes memory signature = abi.encodePacked(r, s, v); return signature; } From 8695c5e5ba6b96df13665352bdd2be3871e9ef9f Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 11:45:57 +0300 Subject: [PATCH 09/45] Update documentation --- docs/RelayFacet.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/RelayFacet.md b/docs/RelayFacet.md index 7d42cfdf5..0a84e5ee3 100644 --- a/docs/RelayFacet.md +++ b/docs/RelayFacet.md @@ -1,8 +1,11 @@ # Relay Facet +Relay is a cross-chain payments system enabling instant, low-cost bridging and cross-chain execution using relayers as financial agents. + ## How it works -The Relay Facet works by ... +The Relay Facet works by sending funds directly to the RelayReceiver contract in the case of Native tokens or sending tokens directly +to the official Relay solver EOA along with extra calldata bytes that reference a prefecthed quote id ```mermaid graph LR; @@ -22,9 +25,14 @@ graph LR; The methods listed above take a variable labeled `_relayData`. This data is specific to relay and is represented as the following struct type: ```solidity -/// @param example Example parameter. -struct relayData { - string example; +/// @param requestId the id of the quote request. +/// @param receivingAssetId +/// @param callData the bridging calldata generated by the relay api +struct RelayData { + bytes32 requestId; + address receivingAssetId; + bytes callData; + bytes signature; } ``` From 0a0fa7ee4cc95e0044fd9daf15418fcde795e716 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 11:51:58 +0300 Subject: [PATCH 10/45] Update doc link --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index e13a483ff..a035266aa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,6 +28,7 @@ - [Optimism Bridge Facet](./OptimismBridgeFacet.md) - [Periphery Registry Facet](./PeripheryRegistryFacet.md) - [Polygon Bridge Facet](./PolygonBridgeFacet.md) +- [Relay Facet](./RelayFacet.md) - [Ronin Bridge Facet](./RoninBridgeFacet.md) - [Squid Facet](./SquidFacet.md) - [Standardized Call Facet](./StandardizedCallFacet.md) From e0d7f1c5d0c6470ffe2560463c6d13a722f923ea Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 12:05:34 +0300 Subject: [PATCH 11/45] Deploy to POL and OP staging --- deployments/_deployments_log_file.json | 18 ++++++++++++++++-- deployments/optimism.diamond.staging.json | 6 +++++- deployments/optimism.staging.json | 5 +++-- deployments/polygon.diamond.staging.json | 2 +- deployments/polygon.staging.json | 2 +- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index c3a9f3a33..dc4fc0c65 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -24364,9 +24364,23 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a", + "ADDRESS": "0x74763722d92832d247cFa92825b06098cf72BAA2", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2024-10-31 11:59:26", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x74763722d92832d247cFa92825b06098cf72BAA2", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-10-30 11:17:41", + "TIMESTAMP": "2024-10-31 12:02:46", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" diff --git a/deployments/optimism.diamond.staging.json b/deployments/optimism.diamond.staging.json index 5c2ab5ea7..6c672a5b7 100644 --- a/deployments/optimism.diamond.staging.json +++ b/deployments/optimism.diamond.staging.json @@ -124,6 +124,10 @@ "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b": { "Name": "AcrossFacetPackedV3", "Version": "1.0.0" + }, + "0x74763722d92832d247cFa92825b06098cf72BAA2": { + "Name": "RelayFacet", + "Version": "1.0.0" } }, "Periphery": { @@ -132,8 +136,8 @@ "FeeCollector": "0x7F8E9bEBd1Dea263A36a6916B99bd84405B9654a", "LiFiDEXAggregator": "", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", - "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", + "Receiver": "0x36E9B2E8A627474683eF3b1E9Df26D2bF04396f3", "ReceiverStargateV2": "", "RelayerCelerIM": "0xa1Ed8783AC96385482092b82eb952153998e9b70", "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" diff --git a/deployments/optimism.staging.json b/deployments/optimism.staging.json index bfc530438..1b15df344 100644 --- a/deployments/optimism.staging.json +++ b/deployments/optimism.staging.json @@ -39,5 +39,6 @@ "EmergencyPauseFacet": "0x17Bb203F42d8e404ac7E8dB6ff972B7E8473850b", "AcrossFacetV3": "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", - "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b" -} + "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", + "RelayFacet": "0x74763722d92832d247cFa92825b06098cf72BAA2" +} \ No newline at end of file diff --git a/deployments/polygon.diamond.staging.json b/deployments/polygon.diamond.staging.json index 58755fd04..01e5b0224 100644 --- a/deployments/polygon.diamond.staging.json +++ b/deployments/polygon.diamond.staging.json @@ -121,7 +121,7 @@ "Name": "", "Version": "" }, - "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a": { + "0x74763722d92832d247cFa92825b06098cf72BAA2": { "Name": "RelayFacet", "Version": "1.0.0" } diff --git a/deployments/polygon.staging.json b/deployments/polygon.staging.json index 5683a0133..12b8bae15 100644 --- a/deployments/polygon.staging.json +++ b/deployments/polygon.staging.json @@ -47,5 +47,5 @@ "HopFacetOptimized": "0xf82135385765f1324257ffF74489F16382EBBb8A", "SymbiosisFacet": "0x21571D628B0bCBeb954D5933A604eCac35bAF2c7", "AcrossFacetV3": "0xe2e5428F972d9C0a5Ba433e0c402752b472dB248", - "RelayFacet": "0xA49EEC3DF7C2f69c656Ab06804f7122826bba63a" + "RelayFacet": "0x74763722d92832d247cFa92825b06098cf72BAA2" } \ No newline at end of file From 458674feac23872794405d999ce2da609442663e Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 12:06:36 +0300 Subject: [PATCH 12/45] Remove unneeded comment --- script/deploy/facets/DeployRelayFacet.s.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/script/deploy/facets/DeployRelayFacet.s.sol b/script/deploy/facets/DeployRelayFacet.s.sol index 9fa6d4054..f1382e220 100644 --- a/script/deploy/facets/DeployRelayFacet.s.sol +++ b/script/deploy/facets/DeployRelayFacet.s.sol @@ -20,7 +20,6 @@ contract DeployScript is DeployScriptBase { } function getConstructorArgs() internal override returns (bytes memory) { - // If you don't have a constructor or it doesn't take any arguments, you can remove this function string memory path = string.concat(root, "/config/relay.json"); string memory json = vm.readFile(path); From eb6d35fabc9d593ca9902e31e8876bf6747f222e Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 12:13:29 +0300 Subject: [PATCH 13/45] Fixes --- docs/RelayFacet.md | 2 +- templates/facetDoc.template.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/RelayFacet.md b/docs/RelayFacet.md index 0a84e5ee3..bbc72da7d 100644 --- a/docs/RelayFacet.md +++ b/docs/RelayFacet.md @@ -17,7 +17,7 @@ graph LR; - `function startBridgeTokensViaRelay(BridgeData calldata _bridgeData, RelayData calldata _relayData)` - Simply bridges tokens using relay -- `swapAndStartBridgeTokensViarelay(BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, relayData memory _relayData)` +- `swapAndStartBridgeTokensViaRelay(BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, relayData memory _relayData)` - Performs swap(s) before bridging tokens using relay ## relay Specific Parameters diff --git a/templates/facetDoc.template.hbs b/templates/facetDoc.template.hbs index 4ae472edc..a825fcd29 100644 --- a/templates/facetDoc.template.hbs +++ b/templates/facetDoc.template.hbs @@ -14,7 +14,7 @@ graph LR; - `function startBridgeTokensVia{{titleCase name}}(BridgeData calldata _bridgeData, {{titleCase name}}Data calldata _{{camelCase name}}Data)` - Simply bridges tokens using {{camelCase name}} -- `swapAndStartBridgeTokensVia{{camelCase name}}(BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, {{camelCase name}}Data memory _{{camelCase name}}Data)` +- `swapAndStartBridgeTokensVia{{titleCase name}}(BridgeData memory _bridgeData, LibSwap.SwapData[] calldata _swapData, {{camelCase name}}Data memory _{{camelCase name}}Data)` - Performs swap(s) before bridging tokens using {{camelCase name}} ## {{camelCase name}} Specific Parameters From 674e253a7749b25cf5db8dedb04f9c6504876b1e Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 12:14:18 +0300 Subject: [PATCH 14/45] Fixes --- test/solidity/Facets/RelayFacet.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index e4014d770..a742cad04 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -33,7 +33,6 @@ contract RelayFacetTest is TestBaseFacet { customBlockNumberForForking = 19767662; initTestBase(); relayFacet = new TestRelayFacet(RELAY_RECEIVER, RELAY_SOLVER); - // relayFacet.initRelay(EXAMPLE_ALLOWED_TOKENS); bytes4[] memory functionSelectors = new bytes4[](4); functionSelectors[0] = relayFacet.startBridgeTokensViaRelay.selector; functionSelectors[1] = relayFacet From 7049a82fdccf0d99e99f5ca588ed3d2fb331a833 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 31 Oct 2024 16:29:35 +0300 Subject: [PATCH 15/45] Add swap and bridge TX to demo --- script/demoScripts/demoRelay.ts | 88 +++++++++++++++++++ script/demoScripts/utils/demoScriptHelpers.ts | 57 ++++++++++++ 2 files changed, 145 insertions(+) diff --git a/script/demoScripts/demoRelay.ts b/script/demoScripts/demoRelay.ts index 51715548e..ac5314dd2 100644 --- a/script/demoScripts/demoRelay.ts +++ b/script/demoScripts/demoRelay.ts @@ -7,6 +7,14 @@ import { } from '../../typechain' import { ethers, utils } from 'ethers' import dotenv from 'dotenv' +import { + ADDRESS_UNISWAP_ARB, + ADDRESS_USDC_ARB, + ADDRESS_WETH_ARB, + getUniswapSwapDataERC20ToERC20, + getUniswapSwapDataERC20ToETH, +} from './utils/demoScriptHelpers' +import { _100 } from '@uniswap/sdk/dist/constants' dotenv.config() const main = async () => { @@ -159,6 +167,86 @@ const main = async () => { .startBridgeTokensViaRelay(bridgeData, relayData) await tx.wait() console.info('Bridged USDC') + + // Swap USDC and Bridge ETH + + params = { + user: deployments.LiFiDiamond, + originChainId: 42161, + destinationChainId: 137, + originCurrency: '0x0000000000000000000000000000000000000000', + destinationCurrency: '0x0000000000000000000000000000000000000000', + recipient: address, + tradeType: 'EXACT_INPUT', + amount: '1000000000000000', + referrer: 'relay.link/swap', + useExternalLiquidity: false, + } + + // Bridge 0.001 ETH from ARB on POL + resp = await fetch('https://api.relay.link/quote', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }) + quote = await resp.json() + requestId = quote.steps[0].requestId + console.log(quote) + + console.log(requestId) + sigResp = await fetch( + `https://api.relay.link/requests/${requestId}/signature/v2`, + { headers: { 'Content-Type': 'application/json' } } + ) + sigData = await sigResp.json() + console.log(sigData) + + bridgeData = { + transactionId: utils.randomBytes(32), + bridge: 'Relay', + integrator: 'ACME Devs', + referrer: '0x0000000000000000000000000000000000000000', + sendingAssetId: '0x0000000000000000000000000000000000000000', + receiver: address, + minAmount: ethers.utils.parseEther('0.001'), + destinationChainId: 137, + hasSourceSwaps: true, + hasDestinationCall: false, + } + + const swapData = [] + + const uniswapAddress = ADDRESS_UNISWAP_ARB + swapData[0] = await getUniswapSwapDataERC20ToETH( + uniswapAddress, + 42161, + ADDRESS_USDC_ARB, + ADDRESS_WETH_ARB, + ethers.utils.parseUnits('4', 6), + LIFI_ADDRESS, + true + ) + + relayData = { + requestId, + receivingAssetId: '0x0000000000000000000000000000000000000000', + callData: '0x', + signature: sigData.signature, + } + + console.info('Dev Wallet Address: ', address) + console.info('Approving USDC...') + tx = await token.connect(signer).approve(LIFI_ADDRESS, '4000000') + await tx.wait() + console.info('Approved USDC') + console.info('Bridging USDC -> ETH...') + tx = await relay + .connect(signer) + .swapAndStartBridgeTokensViaRelay(bridgeData, swapData, relayData) + await tx.wait() + console.info('Bridged ETH') } main() diff --git a/script/demoScripts/utils/demoScriptHelpers.ts b/script/demoScripts/utils/demoScriptHelpers.ts index ee2a0ab3a..3cacf9630 100644 --- a/script/demoScripts/utils/demoScriptHelpers.ts +++ b/script/demoScripts/utils/demoScriptHelpers.ts @@ -195,6 +195,63 @@ export const getUniswapSwapDataERC20ToERC20 = async ( return swapData } +export const getUniswapSwapDataERC20ToETH = async ( + uniswapAddress: string, + chainId: number, + sendingAssetId: string, + receivingAssetId: string, + fromAmount: BigNumber, + receiverAddress: string, + requiresDeposit = true, + minAmountOut = 0, + deadline = Math.floor(Date.now() / 1000) + 60 * 60 +) => { + // prepare destSwap callData + const uniswap = new Contract(uniswapAddress, [ + 'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)', + ]) + const path = [sendingAssetId, receivingAssetId] + + // get minAmountOut from Uniswap router + console.log(`finalFromAmount : ${fromAmount}`) + + const finalMinAmountOut = + minAmountOut == 0 + ? await getAmountsOutUniswap( + uniswapAddress, + chainId, + [sendingAssetId, receivingAssetId], + fromAmount + ) + : minAmountOut + console.log(`finalMinAmountOut: ${finalMinAmountOut}`) + + const uniswapCalldata = ( + await uniswap.populateTransaction.swapExactTokensForETH( + fromAmount, // amountIn + finalMinAmountOut, + path, + receiverAddress, + deadline + ) + ).data + + if (!uniswapCalldata) throw Error('Could not create Uniswap calldata') + + // construct LibSwap.SwapData + const swapData: LibSwap.SwapDataStruct = { + callTo: uniswapAddress, + approveTo: uniswapAddress, + sendingAssetId, + receivingAssetId: '0x0000000000000000000000000000000000000000', + fromAmount, + callData: uniswapCalldata, + requiresDeposit, + } + + return swapData +} + export const getAmountsOutUniswap = async ( uniswapAddress: string, chainId: number, From 2bae7764fa9c55f7a9a8b478b035f89f61a4e980 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 1 Nov 2024 15:48:34 +0300 Subject: [PATCH 16/45] Add params needed to handle non EVM addresses --- src/Facets/RelayFacet.sol | 12 +++++++++--- test/solidity/Facets/RelayFacet.t.sol | 13 ++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index b8bfc13b5..a305047bf 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -18,6 +18,9 @@ import { ECDSA } from "solady/utils/ECDSA.sol"; contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Storage /// + address internal constant NON_EVM_ADDRESS = + 0x11f111f111f111F111f111f111F111f111f111F1; + // Receiver for native transfers address public immutable relayReceiver; address public immutable relaySolver; @@ -28,7 +31,8 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// @param exampleParam Example parameter struct RelayData { bytes32 requestId; - address receivingAssetId; + bytes32 nonEVMReceiver; + bytes32 receivingAssetId; bytes callData; bytes signature; } @@ -56,8 +60,10 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { bytes32(uint256(uint160(address(this)))), bytes32(uint256(uint160(_bridgeData.sendingAssetId))), _bridgeData.destinationChainId, - bytes32(uint256(uint160(_bridgeData.receiver))), - bytes32(uint256(uint160(_relayData.receivingAssetId))) + _bridgeData.receiver == NON_EVM_ADDRESS + ? _relayData.nonEVMReceiver + : bytes32(uint256(uint160(_bridgeData.receiver))), + _relayData.receivingAssetId ) ) ) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index a742cad04..82e39dfbc 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -28,6 +28,8 @@ contract RelayFacetTest is TestBaseFacet { 0xa5F565650890fBA1824Ee0F21EbBbF660a179934; uint256 internal PRIVATE_KEY = 0x1234567890; address RELAY_SOLVER = vm.addr(PRIVATE_KEY); + address internal constant NON_EVM_ADDRESS = + 0x11f111f111f111F111f111f111F111f111f111F1; function setUp() public { customBlockNumberForForking = 19767662; @@ -64,7 +66,10 @@ contract RelayFacetTest is TestBaseFacet { validRelayData = RelayFacet.RelayData({ requestId: bytes32("1234"), - receivingAssetId: 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174, // Polygon USDC + nonEVMReceiver: "", + receivingAssetId: bytes32( + uint256(uint160(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174)) + ), // Polygon USDC callData: "", signature: "" }); @@ -122,8 +127,10 @@ contract RelayFacetTest is TestBaseFacet { bytes32(uint256(uint160(address(relayFacet)))), bytes32(uint256(uint160(_bridgeData.sendingAssetId))), _bridgeData.destinationChainId, - bytes32(uint256(uint160(_bridgeData.receiver))), - bytes32(uint256(uint160(_relayData.receivingAssetId))) + _bridgeData.receiver == NON_EVM_ADDRESS + ? _relayData.nonEVMReceiver + : bytes32(uint256(uint160(_bridgeData.receiver))), + _relayData.receivingAssetId ) ) ) From ecc49ce61b7e9f2fd9c6c3d0179840a864548bd8 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 1 Nov 2024 17:44:38 +0300 Subject: [PATCH 17/45] Update tests to handle non-evm --- src/Facets/RelayFacet.sol | 33 +++++++++++++- test/solidity/Facets/RelayFacet.t.sol | 65 ++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index a305047bf..9e2ba29f3 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -37,6 +37,14 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { bytes signature; } + /// Events /// + + event BridgeToNonEVMChain( + bytes32 indexed transactionId, + uint256 indexed destinationChainId, + bytes32 receiver + ); + /// Errors /// error InvalidQuote(); @@ -59,7 +67,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { block.chainid, bytes32(uint256(uint160(address(this)))), bytes32(uint256(uint160(_bridgeData.sendingAssetId))), - _bridgeData.destinationChainId, + _getMappedChainId(_bridgeData.destinationChainId), _bridgeData.receiver == NON_EVM_ADDRESS ? _relayData.nonEVMReceiver : bytes32(uint256(uint160(_bridgeData.receiver))), @@ -175,6 +183,29 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { revert(LibUtil.getRevertMsg(reason)); } } + + if (_bridgeData.receiver == NON_EVM_ADDRESS) { + emit BridgeToNonEVMChain( + _bridgeData.transactionId, + _bridgeData.destinationChainId, + _relayData.nonEVMReceiver + ); + } + emit LiFiTransferStarted(_bridgeData); } + + function _getMappedChainId( + uint256 chainId + ) internal pure returns (uint256) { + if (chainId == 20000000000001) { + return 8253038; + } + + if (chainId == 1151111081099710) { + return 792703809; + } + + return chainId; + } } diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 82e39dfbc..1154272ff 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -5,6 +5,14 @@ import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFa import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; +// TODO: Upgrade forge-std lib +// This is a hack to be able to use newer capabilities of forge without having +// to update the forge-std lib as this will break some tests at the moment +interface VmWithUnixTime { + /// Returns the time since unix epoch in milliseconds. + function unixTime() external returns (uint256 milliseconds); +} + // Stub RelayFacet Contract contract TestRelayFacet is RelayFacet { constructor( @@ -64,15 +72,38 @@ contract RelayFacetTest is TestBaseFacet { bridgeData.bridge = "relay"; bridgeData.destinationChainId = 137; - validRelayData = RelayFacet.RelayData({ - requestId: bytes32("1234"), - nonEVMReceiver: "", - receivingAssetId: bytes32( - uint256(uint160(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174)) - ), // Polygon USDC - callData: "", - signature: "" - }); + // This will randomly setup bridging to EVM or non-EVM + if (VmWithUnixTime(address(vm)).unixTime() % 2 == 0) { + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: "", + receivingAssetId: bytes32( + uint256( + uint160(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ) + ), // Polygon USDC + callData: "", + signature: "" + }); + } else { + bridgeData.receiver = NON_EVM_ADDRESS; + bridgeData.destinationChainId = 792703809; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked( + "EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb" + ) + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ) + ), // Solana USDC + callData: "", + signature: "" + }); + } } function initiateBridgeTxWithFacet(bool isNative) internal override { @@ -126,7 +157,7 @@ contract RelayFacetTest is TestBaseFacet { block.chainid, bytes32(uint256(uint160(address(relayFacet)))), bytes32(uint256(uint160(_bridgeData.sendingAssetId))), - _bridgeData.destinationChainId, + _getMappedChainId(_bridgeData.destinationChainId), _bridgeData.receiver == NON_EVM_ADDRESS ? _relayData.nonEVMReceiver : bytes32(uint256(uint160(_bridgeData.receiver))), @@ -140,4 +171,18 @@ contract RelayFacetTest is TestBaseFacet { bytes memory signature = abi.encodePacked(r, s, v); return signature; } + + function _getMappedChainId( + uint256 chainId + ) internal pure returns (uint256) { + if (chainId == 20000000000001) { + return 8253038; + } + + if (chainId == 1151111081099710) { + return 792703809; + } + + return chainId; + } } From ad145c080d87611708950a696468b4ee5e0bc46c Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 4 Nov 2024 11:59:56 +0300 Subject: [PATCH 18/45] Redeploy to staging --- deployments/_deployments_log_file.json | 14 +++++++------- deployments/arbitrum.staging.json | 2 +- deployments/optimism.staging.json | 2 +- deployments/polygon.staging.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index eadd847b1..82fd088a8 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -24364,9 +24364,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x74763722d92832d247cFa92825b06098cf72BAA2", + "ADDRESS": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-10-31 10:21:46", + "TIMESTAMP": "2024-11-04 11:36:35", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" @@ -24378,9 +24378,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x74763722d92832d247cFa92825b06098cf72BAA2", + "ADDRESS": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-10-31 11:59:26", + "TIMESTAMP": "2024-11-04 11:49:16", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" @@ -24392,9 +24392,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0x74763722d92832d247cFa92825b06098cf72BAA2", + "ADDRESS": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-10-31 12:02:46", + "TIMESTAMP": "2024-11-04 11:55:36", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" @@ -24403,4 +24403,4 @@ } } } -} \ No newline at end of file +} diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index db8438c21..00c4e2c38 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -49,5 +49,5 @@ "AcrossFacetV3": "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", - "RelayFacet": "0x74763722d92832d247cFa92825b06098cf72BAA2" + "RelayFacet": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669" } \ No newline at end of file diff --git a/deployments/optimism.staging.json b/deployments/optimism.staging.json index 1b15df344..332d1bc03 100644 --- a/deployments/optimism.staging.json +++ b/deployments/optimism.staging.json @@ -40,5 +40,5 @@ "AcrossFacetV3": "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", - "RelayFacet": "0x74763722d92832d247cFa92825b06098cf72BAA2" + "RelayFacet": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669" } \ No newline at end of file diff --git a/deployments/polygon.staging.json b/deployments/polygon.staging.json index 12b8bae15..2de0f5139 100644 --- a/deployments/polygon.staging.json +++ b/deployments/polygon.staging.json @@ -47,5 +47,5 @@ "HopFacetOptimized": "0xf82135385765f1324257ffF74489F16382EBBb8A", "SymbiosisFacet": "0x21571D628B0bCBeb954D5933A604eCac35bAF2c7", "AcrossFacetV3": "0xe2e5428F972d9C0a5Ba433e0c402752b472dB248", - "RelayFacet": "0x74763722d92832d247cFa92825b06098cf72BAA2" + "RelayFacet": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669" } \ No newline at end of file From bc4295f50c795fac40985ecbc15f0d92456a8386 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 4 Nov 2024 12:42:50 +0300 Subject: [PATCH 19/45] Update Relay Demo --- package.json | 11 +++- script/demoScripts/demoRelay.ts | 99 ++++++++++++++++++++++++++++----- yarn.lock | 28 ++++++++++ 3 files changed, 121 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 03e210892..eed801663 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13", "@nomiclabs/hardhat-etherscan": "3.1.6", "@nomiclabs/hardhat-waffle": "2.0.5", + "@solana/web3.js": "^1.95.4", "@typechain/ethers-v5": "^10.2.0", "@typechain/hardhat": "^6.1.5", "@types/node": "^17.0.23", @@ -111,7 +112,13 @@ "zx": "^8.0.2" }, "lint-staged": { - "*.{ts,js}": ["prettier --write", "eslint --fix"], - "*.sol": ["prettier --write", "solhint --fix"] + "*.{ts,js}": [ + "prettier --write", + "eslint --fix" + ], + "*.sol": [ + "prettier --write", + "solhint --fix" + ] } } diff --git a/script/demoScripts/demoRelay.ts b/script/demoScripts/demoRelay.ts index ac5314dd2..20481a13a 100644 --- a/script/demoScripts/demoRelay.ts +++ b/script/demoScripts/demoRelay.ts @@ -15,6 +15,7 @@ import { getUniswapSwapDataERC20ToETH, } from './utils/demoScriptHelpers' import { _100 } from '@uniswap/sdk/dist/constants' +import { PublicKey } from '@solana/web3.js' dotenv.config() const main = async () => { @@ -45,7 +46,6 @@ const main = async () => { useExternalLiquidity: false, } - // Bridge 0.001 ETH from ARB on POL let resp = await fetch('https://api.relay.link/quote', { method: 'POST', headers: { @@ -55,15 +55,12 @@ const main = async () => { }) let quote = await resp.json() let requestId = quote.steps[0].requestId - console.log(quote) - console.log(requestId) let sigResp = await fetch( `https://api.relay.link/requests/${requestId}/signature/v2`, { headers: { 'Content-Type': 'application/json' } } ) let sigData = await sigResp.json() - console.log(sigData) let bridgeData: ILiFi.BridgeDataStruct = { transactionId: utils.randomBytes(32), @@ -80,7 +77,8 @@ const main = async () => { let relayData: RelayFacet.RelayDataStruct = { requestId, - receivingAssetId: '0x0000000000000000000000000000000000000000', + nonEVMReceiver: ethers.constants.HashZero, + receivingAssetId: ethers.constants.HashZero, callData: '0x', signature: sigData.signature, } @@ -110,8 +108,6 @@ const main = async () => { useExternalLiquidity: false, } - // Bridge 0.001 ETH from ARB on POL - resp = await fetch('https://api.relay.link/quote', { method: 'POST', headers: { @@ -121,15 +117,12 @@ const main = async () => { }) quote = await resp.json() requestId = quote.steps[0].requestId - console.log(quote) - console.log(requestId) sigResp = await fetch( `https://api.relay.link/requests/${requestId}/signature/v2`, { headers: { 'Content-Type': 'application/json' } } ) sigData = await sigResp.json() - console.log(sigData) const token = ERC20__factory.connect( '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', @@ -151,7 +144,11 @@ const main = async () => { relayData = { requestId, - receivingAssetId: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', + nonEVMReceiver: ethers.constants.HashZero, + receivingAssetId: ethers.utils.hexZeroPad( + '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', + 32 + ), callData: quote.steps[0].items[0].data.data, signature: sigData.signature, } @@ -183,7 +180,6 @@ const main = async () => { useExternalLiquidity: false, } - // Bridge 0.001 ETH from ARB on POL resp = await fetch('https://api.relay.link/quote', { method: 'POST', headers: { @@ -193,9 +189,7 @@ const main = async () => { }) quote = await resp.json() requestId = quote.steps[0].requestId - console.log(quote) - console.log(requestId) sigResp = await fetch( `https://api.relay.link/requests/${requestId}/signature/v2`, { headers: { 'Content-Type': 'application/json' } } @@ -231,7 +225,8 @@ const main = async () => { relayData = { requestId, - receivingAssetId: '0x0000000000000000000000000000000000000000', + nonEVMReceiver: ethers.constants.HashZero, + receivingAssetId: ethers.constants.HashZero, callData: '0x', signature: sigData.signature, } @@ -247,6 +242,80 @@ const main = async () => { .swapAndStartBridgeTokensViaRelay(bridgeData, swapData, relayData) await tx.wait() console.info('Bridged ETH') + + // Bridge USDC to Solana + + const solanaReceiver = 'EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb' + const solanaUSDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' + + params = { + user: deployments.LiFiDiamond, + originChainId: 42161, + destinationChainId: 792703809, + originCurrency: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + destinationCurrency: solanaUSDC, + recipient: solanaReceiver, + tradeType: 'EXACT_INPUT', + amount: '5000000', + referrer: 'relay.link/swap', + useExternalLiquidity: false, + } + + resp = await fetch('https://api.relay.link/quote', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }) + quote = await resp.json() + console.log(quote) + requestId = quote.steps[0].requestId + + console.log(requestId) + sigResp = await fetch( + `https://api.relay.link/requests/${requestId}/signature/v2`, + { headers: { 'Content-Type': 'application/json' } } + ) + sigData = await sigResp.json() + console.log(sigData) + + bridgeData = { + transactionId: utils.randomBytes(32), + bridge: 'Relay', + integrator: 'ACME Devs', + referrer: '0x0000000000000000000000000000000000000000', + sendingAssetId: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + receiver: '0x11f111f111f111F111f111f111F111f111f111F1', + minAmount: '5000000', + destinationChainId: 1151111081099710, + hasSourceSwaps: false, + hasDestinationCall: false, + } + + relayData = { + requestId, + nonEVMReceiver: `0x${new PublicKey(solanaReceiver) + .toBuffer() + .toString('hex')}`, + receivingAssetId: `0x${new PublicKey(solanaUSDC) + .toBuffer() + .toString('hex')}`, + callData: quote.steps[0].items[0].data.data, + signature: sigData.signature, + } + + console.info('Dev Wallet Address: ', address) + console.info('Approving USDC...') + tx = await token.connect(signer).approve(LIFI_ADDRESS, '5000000') + await tx.wait() + console.info('Approved USDC') + console.info('Bridging USDC...') + tx = await relay + .connect(signer) + .startBridgeTokensViaRelay(bridgeData, relayData) + await tx.wait() + console.info('Bridged USDC') } main() diff --git a/yarn.lock b/yarn.lock index a153c799c..d66b1d16d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57,6 +57,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.25.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@balena/dockerignore@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" @@ -1601,6 +1608,27 @@ rpc-websockets "^7.11.1" superstruct "^1.0.4" +"@solana/web3.js@^1.95.4": + version "1.95.4" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.4.tgz#771603f60d75cf7556ad867e1fd2efae32f9ad09" + integrity sha512-sdewnNEA42ZSMxqkzdwEWi6fDgzwtJHaQa5ndUGEJYtoOnM6X5cvPmjoTUp7/k7bRrVAxfBgDnvQQHD6yhlLYw== + dependencies: + "@babel/runtime" "^7.25.0" + "@noble/curves" "^1.4.2" + "@noble/hashes" "^1.4.0" + "@solana/buffer-layout" "^4.0.1" + agentkeepalive "^4.5.0" + bigint-buffer "^1.1.5" + bn.js "^5.2.1" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.3" + fast-stable-stringify "^1.0.0" + jayson "^4.1.1" + node-fetch "^2.7.0" + rpc-websockets "^9.0.2" + superstruct "^2.0.2" + "@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1", "@solidity-parser/parser@^0.14.5": version "0.14.5" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" From a91b20471d088f7b902b2e9a3bd84412d2f5b9bc Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 5 Nov 2024 15:36:00 +0300 Subject: [PATCH 20/45] add revert case --- docs/RelayFacet.md | 12 ++++++++---- src/Facets/RelayFacet.sol | 14 ++++++++++++-- test/solidity/Facets/RelayFacet.t.sol | 15 +++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/docs/RelayFacet.md b/docs/RelayFacet.md index bbc72da7d..1bb34b3e9 100644 --- a/docs/RelayFacet.md +++ b/docs/RelayFacet.md @@ -25,12 +25,16 @@ graph LR; The methods listed above take a variable labeled `_relayData`. This data is specific to relay and is represented as the following struct type: ```solidity -/// @param requestId the id of the quote request. -/// @param receivingAssetId -/// @param callData the bridging calldata generated by the relay api +/// @dev Relay specific parameters +/// @param requestId Realy API request ID +/// @param nonEVMReceiver set only if bridging to non-EVM chain +/// @params receivingAssetId address of receiving asset +/// @params callData calldata provided by Relay API +/// @params signature attestation signature provided by the Relay solver struct RelayData { bytes32 requestId; - address receivingAssetId; + bytes32 nonEVMReceiver; + bytes32 receivingAssetId; bytes callData; bytes signature; } diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 9e2ba29f3..317781796 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -23,12 +23,17 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { // Receiver for native transfers address public immutable relayReceiver; + // Relayer wallet for ERC20 transfers address public immutable relaySolver; /// Types /// - /// @dev Optional bridge specific struct - /// @param exampleParam Example parameter + /// @dev Relay specific parameters + /// @param requestId Realy API request ID + /// @param nonEVMReceiver set only if bridging to non-EVM chain + /// @params receivingAssetId address of receiving asset + /// @params callData calldata provided by Relay API + /// @params signature attestation signature provided by the Relay solver struct RelayData { bytes32 requestId; bytes32 nonEVMReceiver; @@ -51,6 +56,8 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Modifiers /// + /// @param _bridgeData The core information needed for bridging + /// @param _relayData Data specific to Relay modifier onlyValidQuote( ILiFi.BridgeData memory _bridgeData, RelayData calldata _relayData @@ -184,6 +191,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { } } + // Emit special event if bridging to non-EVM chain if (_bridgeData.receiver == NON_EVM_ADDRESS) { emit BridgeToNonEVMChain( _bridgeData.transactionId, @@ -195,6 +203,8 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { emit LiFiTransferStarted(_bridgeData); } + /// @notice get Relay specific chain id for non-EVM chains + /// @param chainId LIFI specific chain id function _getMappedChainId( uint256 chainId ) internal pure returns (uint256) { diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 1154272ff..04b13cb4d 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -39,6 +39,8 @@ contract RelayFacetTest is TestBaseFacet { address internal constant NON_EVM_ADDRESS = 0x11f111f111f111F111f111f111F111f111f111F1; + error InvalidQuote(); + function setUp() public { customBlockNumberForForking = 19767662; initTestBase(); @@ -144,6 +146,19 @@ contract RelayFacetTest is TestBaseFacet { } } + function testRevert_BridgeWithInvalidSignature() public virtual { + vm.startPrank(USER_SENDER); + + // approval + usdc.approve(_facetTestContractAddress, bridgeData.minAmount); + + PRIVATE_KEY = 0x0987654321; + + vm.expectRevert(InvalidQuote.selector); + initiateBridgeTxWithFacet(false); + vm.stopPrank(); + } + function signData( ILiFi.BridgeData memory _bridgeData, RelayFacet.RelayData memory _relayData From c09b84d5f68dc1f523b70254b114791d5bd76e81 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 5 Nov 2024 15:41:35 +0300 Subject: [PATCH 21/45] use standardized method for creating hash --- src/Facets/RelayFacet.sol | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 317781796..8115ba5e9 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -65,21 +65,18 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { // Verify that the bridging quote has been signed by the Relay solver // as attested using the attestaion API // API URL: https://api.relay.link/requests/{requestId}/signature/v2 - bytes32 message = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encodePacked( - _relayData.requestId, - block.chainid, - bytes32(uint256(uint160(address(this)))), - bytes32(uint256(uint160(_bridgeData.sendingAssetId))), - _getMappedChainId(_bridgeData.destinationChainId), - _bridgeData.receiver == NON_EVM_ADDRESS - ? _relayData.nonEVMReceiver - : bytes32(uint256(uint160(_bridgeData.receiver))), - _relayData.receivingAssetId - ) + bytes32 message = ECDSA.toEthSignedMessageHash( + keccak256( + abi.encodePacked( + _relayData.requestId, + block.chainid, + bytes32(uint256(uint160(address(this)))), + bytes32(uint256(uint160(_bridgeData.sendingAssetId))), + _getMappedChainId(_bridgeData.destinationChainId), + _bridgeData.receiver == NON_EVM_ADDRESS + ? _relayData.nonEVMReceiver + : bytes32(uint256(uint160(_bridgeData.receiver))), + _relayData.receivingAssetId ) ) ); From 0da22f7723f5dc7e14db4872ab8dfc04b9b31c92 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 5 Nov 2024 15:42:39 +0300 Subject: [PATCH 22/45] add comments for clarity --- src/Facets/RelayFacet.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 8115ba5e9..f79ccdc8e 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -205,10 +205,12 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { function _getMappedChainId( uint256 chainId ) internal pure returns (uint256) { + // Bitcoin if (chainId == 20000000000001) { return 8253038; } + // Solana if (chainId == 1151111081099710) { return 792703809; } From 1f100496a4a6912854b90cb3f9b654cdcfaa9bdf Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 5 Nov 2024 15:47:46 +0300 Subject: [PATCH 23/45] add URL for listing chain ids --- lib/openzeppelin-contracts | 2 +- src/Facets/RelayFacet.sol | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 54b3f1434..e50c24f58 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 54b3f14346da01ba0d159114b399197fea8b7cda +Subproject commit e50c24f5839db17f46991478384bfda14acfb830 diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index f79ccdc8e..e1845b169 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -201,6 +201,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { } /// @notice get Relay specific chain id for non-EVM chains + /// IDs found here https://li.quest/v1/chains?chainTypes=UTXO,SVM /// @param chainId LIFI specific chain id function _getMappedChainId( uint256 chainId From e104a624bbac3cff7992ae16b15d60dfcd9958f7 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 5 Nov 2024 16:30:05 +0300 Subject: [PATCH 24/45] fix typo --- src/Facets/RelayFacet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index e1845b169..2cc4e01b1 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -63,7 +63,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { RelayData calldata _relayData ) { // Verify that the bridging quote has been signed by the Relay solver - // as attested using the attestaion API + // as attested using the attestation API // API URL: https://api.relay.link/requests/{requestId}/signature/v2 bytes32 message = ECDSA.toEthSignedMessageHash( keccak256( From 634a261439741b9c5ccc3dcce52594a40852de5b Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 7 Nov 2024 10:02:09 +0300 Subject: [PATCH 25/45] add explicit test for non-evm chains --- test/solidity/Facets/RelayFacet.t.sol | 92 +++++++++++++++------------ 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 04b13cb4d..e5bb697dc 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -5,14 +5,6 @@ import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFa import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; -// TODO: Upgrade forge-std lib -// This is a hack to be able to use newer capabilities of forge without having -// to update the forge-std lib as this will break some tests at the moment -interface VmWithUnixTime { - /// Returns the time since unix epoch in milliseconds. - function unixTime() external returns (uint256 milliseconds); -} - // Stub RelayFacet Contract contract TestRelayFacet is RelayFacet { constructor( @@ -74,38 +66,15 @@ contract RelayFacetTest is TestBaseFacet { bridgeData.bridge = "relay"; bridgeData.destinationChainId = 137; - // This will randomly setup bridging to EVM or non-EVM - if (VmWithUnixTime(address(vm)).unixTime() % 2 == 0) { - validRelayData = RelayFacet.RelayData({ - requestId: bytes32("1234"), - nonEVMReceiver: "", - receivingAssetId: bytes32( - uint256( - uint160(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) - ) - ), // Polygon USDC - callData: "", - signature: "" - }); - } else { - bridgeData.receiver = NON_EVM_ADDRESS; - bridgeData.destinationChainId = 792703809; - validRelayData = RelayFacet.RelayData({ - requestId: bytes32("1234"), - nonEVMReceiver: bytes32( - abi.encodePacked( - "EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb" - ) - ), // DEV Wallet - receivingAssetId: bytes32( - abi.encodePacked( - "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" - ) - ), // Solana USDC - callData: "", - signature: "" - }); - } + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: "", + receivingAssetId: bytes32( + uint256(uint160(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174)) + ), // Polygon USDC + callData: "", + signature: "" + }); } function initiateBridgeTxWithFacet(bool isNative) internal override { @@ -159,6 +128,49 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } + function testBase_CanBridgeTokensToNonEVMChain() + public + virtual + assertBalanceChange( + ADDRESS_USDC, + USER_SENDER, + -int256(defaultUSDCAmount) + ) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_DAI, USER_SENDER, 0) + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + { + bridgeData.receiver = NON_EVM_ADDRESS; + bridgeData.destinationChainId = 792703809; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked( + "EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb" + ) + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ) + ), // Solana USDC + callData: "", + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // approval + usdc.approve(_facetTestContractAddress, bridgeData.minAmount); + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + initiateBridgeTxWithFacet(false); + vm.stopPrank(); + } + function signData( ILiFi.BridgeData memory _bridgeData, RelayFacet.RelayData memory _relayData From 4c1100f254b80765c3fca0a6f3df43656eb98e07 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 7 Nov 2024 10:11:46 +0300 Subject: [PATCH 26/45] add comments fix typo --- src/Facets/RelayFacet.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 2cc4e01b1..b07574b5a 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -29,7 +29,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Types /// /// @dev Relay specific parameters - /// @param requestId Realy API request ID + /// @param requestId Relay API request ID /// @param nonEVMReceiver set only if bridging to non-EVM chain /// @params receivingAssetId address of receiving asset /// @params callData calldata provided by Relay API @@ -89,6 +89,8 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Constructor /// + /// @param _relayReceiver The receiver for native transfers + /// @param _relaySolver Ther relayer wallet for ERC20 transfers constructor(address _relayReceiver, address _relaySolver) { relayReceiver = _relayReceiver; relaySolver = _relaySolver; From 250469bffcea4dfbd14188d9d566204cc5a98cd6 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 7 Nov 2024 10:19:48 +0300 Subject: [PATCH 27/45] add NON_EVM_ADDRESS constant to LibAsset --- src/Facets/RelayFacet.sol | 9 ++------- src/Libraries/LibAsset.sol | 3 +++ test/solidity/Facets/RelayFacet.t.sol | 8 +++----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index b07574b5a..74ccbeea0 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -16,11 +16,6 @@ import { ECDSA } from "solady/utils/ECDSA.sol"; /// @notice Provides functionality for bridging through Relay Protocol /// @custom:version 1.0.0 contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { - /// Storage /// - - address internal constant NON_EVM_ADDRESS = - 0x11f111f111f111F111f111f111F111f111f111F1; - // Receiver for native transfers address public immutable relayReceiver; // Relayer wallet for ERC20 transfers @@ -73,7 +68,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { bytes32(uint256(uint160(address(this)))), bytes32(uint256(uint160(_bridgeData.sendingAssetId))), _getMappedChainId(_bridgeData.destinationChainId), - _bridgeData.receiver == NON_EVM_ADDRESS + _bridgeData.receiver == LibAsset.NON_EVM_ADDRESS ? _relayData.nonEVMReceiver : bytes32(uint256(uint160(_bridgeData.receiver))), _relayData.receivingAssetId @@ -191,7 +186,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { } // Emit special event if bridging to non-EVM chain - if (_bridgeData.receiver == NON_EVM_ADDRESS) { + if (_bridgeData.receiver == LibAsset.NON_EVM_ADDRESS) { emit BridgeToNonEVMChain( _bridgeData.transactionId, _bridgeData.destinationChainId, diff --git a/src/Libraries/LibAsset.sol b/src/Libraries/LibAsset.sol index c2dd7dad0..71511f59f 100644 --- a/src/Libraries/LibAsset.sol +++ b/src/Libraries/LibAsset.sol @@ -15,6 +15,9 @@ library LibAsset { address internal constant NULL_ADDRESS = address(0); + address internal constant NON_EVM_ADDRESS = + 0x11f111f111f111F111f111f111F111f111f111F1; + /// @dev All native assets use the empty address for their asset id /// by convention diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index e5bb697dc..aa6737809 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { LibAllowList, TestBaseFacet, console, ERC20 } from "../utils/TestBaseFacet.sol"; +import { LibAllowList, TestBaseFacet, console, ERC20, LibAsset } from "../utils/TestBaseFacet.sol"; import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; @@ -28,8 +28,6 @@ contract RelayFacetTest is TestBaseFacet { 0xa5F565650890fBA1824Ee0F21EbBbF660a179934; uint256 internal PRIVATE_KEY = 0x1234567890; address RELAY_SOLVER = vm.addr(PRIVATE_KEY); - address internal constant NON_EVM_ADDRESS = - 0x11f111f111f111F111f111f111F111f111f111F1; error InvalidQuote(); @@ -140,7 +138,7 @@ contract RelayFacetTest is TestBaseFacet { assertBalanceChange(ADDRESS_DAI, USER_SENDER, 0) assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) { - bridgeData.receiver = NON_EVM_ADDRESS; + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; bridgeData.destinationChainId = 792703809; validRelayData = RelayFacet.RelayData({ requestId: bytes32("1234"), @@ -185,7 +183,7 @@ contract RelayFacetTest is TestBaseFacet { bytes32(uint256(uint160(address(relayFacet)))), bytes32(uint256(uint160(_bridgeData.sendingAssetId))), _getMappedChainId(_bridgeData.destinationChainId), - _bridgeData.receiver == NON_EVM_ADDRESS + _bridgeData.receiver == LibAsset.NON_EVM_ADDRESS ? _relayData.nonEVMReceiver : bytes32(uint256(uint160(_bridgeData.receiver))), _relayData.receivingAssetId From 03f1711b7b2fd7b6fa6c573d6505faf00b09ba23 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 7 Nov 2024 10:21:50 +0300 Subject: [PATCH 28/45] update name --- script/deploy/resources/deployRequirements.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/deploy/resources/deployRequirements.json b/script/deploy/resources/deployRequirements.json index 7159a583e..dc022377f 100644 --- a/script/deploy/resources/deployRequirements.json +++ b/script/deploy/resources/deployRequirements.json @@ -524,7 +524,7 @@ } } }, - "Relay": { + "RelayFacet": { "configData": { "_relayReceiver": { "configFileName": "relay.json", From 26c1ee75226267a0f8846a0b6825a26570849abf Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 7 Nov 2024 10:34:57 +0300 Subject: [PATCH 29/45] fix typo --- src/Facets/RelayFacet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 74ccbeea0..d48b4f70c 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -85,7 +85,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// Constructor /// /// @param _relayReceiver The receiver for native transfers - /// @param _relaySolver Ther relayer wallet for ERC20 transfers + /// @param _relaySolver The relayer wallet for ERC20 transfers constructor(address _relayReceiver, address _relaySolver) { relayReceiver = _relayReceiver; relaySolver = _relaySolver; From 4e9b7d436006bca753613432d191f1981ed4dffc Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 7 Nov 2024 10:36:07 +0300 Subject: [PATCH 30/45] remove unused imports --- script/deploy/facets/UpdateRelayFacet.s.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/script/deploy/facets/UpdateRelayFacet.s.sol b/script/deploy/facets/UpdateRelayFacet.s.sol index f2ca8b4d4..54655e4f8 100644 --- a/script/deploy/facets/UpdateRelayFacet.s.sol +++ b/script/deploy/facets/UpdateRelayFacet.s.sol @@ -2,13 +2,8 @@ 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 { RelayFacet } from "lifi/Facets/RelayFacet.sol"; contract DeployScript is UpdateScriptBase { - using stdJson for string; - function run() public returns (address[] memory facets, bytes memory cutData) From 54463f185174b9fceec8095c1d098e4cc12575df Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 7 Nov 2024 14:54:10 +0300 Subject: [PATCH 31/45] Update and redeploy to staging --- deployments/_deployments_log_file.json | 12 ++++++------ deployments/arbitrum.diamond.json | 2 +- deployments/arbitrum.staging.json | 2 +- deployments/optimism.diamond.json | 2 +- deployments/optimism.staging.json | 2 +- deployments/polygon.diamond.json | 2 +- deployments/polygon.staging.json | 2 +- script/demoScripts/demoRelay.ts | 4 ---- src/Facets/RelayFacet.sol | 5 +---- test/solidity/Facets/RelayFacet.t.sol | 12 ------------ 10 files changed, 13 insertions(+), 32 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 82777d2f8..d8c1a4a03 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -24492,9 +24492,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669", + "ADDRESS": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-11-04 11:36:35", + "TIMESTAMP": "2024-11-07 14:15:11", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" @@ -24506,9 +24506,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669", + "ADDRESS": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-11-04 11:49:16", + "TIMESTAMP": "2024-11-07 14:16:01", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" @@ -24520,9 +24520,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669", + "ADDRESS": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-11-04 11:55:36", + "TIMESTAMP": "2024-11-07 14:17:16", "CONSTRUCTOR_ARGS": "0x000000000000000000000000a5f565650890fba1824ee0f21ebbbf660a179934000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", "SALT": "", "VERIFIED": "true" diff --git a/deployments/arbitrum.diamond.json b/deployments/arbitrum.diamond.json index f3dd714ee..50fbefe25 100644 --- a/deployments/arbitrum.diamond.json +++ b/deployments/arbitrum.diamond.json @@ -152,8 +152,8 @@ "FeeCollector": "0xB0210dE78E28e2633Ca200609D9f528c13c26cD9", "LiFiDEXAggregator": "0x6140b987d6B51Fd75b66C3B07733Beb5167c42fc", "LiFuelFeeCollector": "0xc02FFcdD914DbA646704439c6090BAbaD521d04C", - "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "ReceiverAcrossV3": "0xB9CEc304899037E661F49DdFa7f64943b5920072", + "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", "RelayerCelerIM": "0x6a8b11bF29C0546991DEcD6E0Db8cC7Fda22bA97", "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d" diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 00c4e2c38..9897cdb97 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -49,5 +49,5 @@ "AcrossFacetV3": "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", - "RelayFacet": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669" + "RelayFacet": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5" } \ No newline at end of file diff --git a/deployments/optimism.diamond.json b/deployments/optimism.diamond.json index 66cbfe053..b9447d035 100644 --- a/deployments/optimism.diamond.json +++ b/deployments/optimism.diamond.json @@ -152,8 +152,8 @@ "FeeCollector": "0xbD6C7B0d2f68c2b7805d88388319cfB6EcB50eA9", "LiFiDEXAggregator": "0x6140b987d6B51Fd75b66C3B07733Beb5167c42fc", "LiFuelFeeCollector": "0xc02FFcdD914DbA646704439c6090BAbaD521d04C", - "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "ReceiverAcrossV3": "0xB9CEc304899037E661F49DdFa7f64943b5920072", + "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", "RelayerCelerIM": "0x6a8b11bF29C0546991DEcD6E0Db8cC7Fda22bA97", "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d" diff --git a/deployments/optimism.staging.json b/deployments/optimism.staging.json index 332d1bc03..91bbb5e5e 100644 --- a/deployments/optimism.staging.json +++ b/deployments/optimism.staging.json @@ -40,5 +40,5 @@ "AcrossFacetV3": "0x6124C65B6264bE13f059b7C3A891a5b77DA8Bd95", "ReceiverAcrossV3": "0x3877f47B560819E96BBD7e7700a02dfACe36D696", "AcrossFacetPackedV3": "0x4352459F6BE1C7D1278F8c34Bb598b0feeB50f8b", - "RelayFacet": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669" + "RelayFacet": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5" } \ No newline at end of file diff --git a/deployments/polygon.diamond.json b/deployments/polygon.diamond.json index bd7c4285e..6549a587a 100644 --- a/deployments/polygon.diamond.json +++ b/deployments/polygon.diamond.json @@ -152,8 +152,8 @@ "FeeCollector": "0xbD6C7B0d2f68c2b7805d88388319cfB6EcB50eA9", "LiFiDEXAggregator": "0x6140b987d6B51Fd75b66C3B07733Beb5167c42fc", "LiFuelFeeCollector": "0xc02FFcdD914DbA646704439c6090BAbaD521d04C", - "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "ReceiverAcrossV3": "0xB9CEc304899037E661F49DdFa7f64943b5920072", + "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", "RelayerCelerIM": "0x6a8b11bF29C0546991DEcD6E0Db8cC7Fda22bA97", "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d" diff --git a/deployments/polygon.staging.json b/deployments/polygon.staging.json index 2de0f5139..cdbbb5a31 100644 --- a/deployments/polygon.staging.json +++ b/deployments/polygon.staging.json @@ -47,5 +47,5 @@ "HopFacetOptimized": "0xf82135385765f1324257ffF74489F16382EBBb8A", "SymbiosisFacet": "0x21571D628B0bCBeb954D5933A604eCac35bAF2c7", "AcrossFacetV3": "0xe2e5428F972d9C0a5Ba433e0c402752b472dB248", - "RelayFacet": "0xca7e5EE5aC72A21796652D8d52B88E2fC728a669" + "RelayFacet": "0x3cf7dE0e31e13C93c8Aada774ADF1C7eD58157f5" } \ No newline at end of file diff --git a/script/demoScripts/demoRelay.ts b/script/demoScripts/demoRelay.ts index 20481a13a..9340fc6d7 100644 --- a/script/demoScripts/demoRelay.ts +++ b/script/demoScripts/demoRelay.ts @@ -79,7 +79,6 @@ const main = async () => { requestId, nonEVMReceiver: ethers.constants.HashZero, receivingAssetId: ethers.constants.HashZero, - callData: '0x', signature: sigData.signature, } @@ -149,7 +148,6 @@ const main = async () => { '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', 32 ), - callData: quote.steps[0].items[0].data.data, signature: sigData.signature, } @@ -227,7 +225,6 @@ const main = async () => { requestId, nonEVMReceiver: ethers.constants.HashZero, receivingAssetId: ethers.constants.HashZero, - callData: '0x', signature: sigData.signature, } @@ -301,7 +298,6 @@ const main = async () => { receivingAssetId: `0x${new PublicKey(solanaUSDC) .toBuffer() .toString('hex')}`, - callData: quote.steps[0].items[0].data.data, signature: sigData.signature, } diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index d48b4f70c..769585ee0 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -27,13 +27,11 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { /// @param requestId Relay API request ID /// @param nonEVMReceiver set only if bridging to non-EVM chain /// @params receivingAssetId address of receiving asset - /// @params callData calldata provided by Relay API /// @params signature attestation signature provided by the Relay solver struct RelayData { bytes32 requestId; bytes32 nonEVMReceiver; bytes32 receivingAssetId; - bytes callData; bytes signature; } @@ -164,7 +162,6 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { revert(LibUtil.getRevertMsg(reason)); } } else { - bytes memory quoteId = _relayData.callData[68:]; // ERC20 // We build the calldata from scratch to ensure that we can only @@ -175,7 +172,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { relaySolver, _bridgeData.minAmount ), - quoteId + abi.encode(_relayData.requestId) ); (bool success, bytes memory reason) = address( _bridgeData.sendingAssetId diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index aa6737809..a54e4d4c2 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -70,7 +70,6 @@ contract RelayFacetTest is TestBaseFacet { receivingAssetId: bytes32( uint256(uint160(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174)) ), // Polygon USDC - callData: "", signature: "" }); } @@ -82,11 +81,6 @@ contract RelayFacetTest is TestBaseFacet { value: bridgeData.minAmount }(bridgeData, validRelayData); } else { - validRelayData.callData = abi.encodeWithSignature( - "transfer(address,uint256)", - RELAY_SOLVER, - bridgeData.minAmount - ); relayFacet.startBridgeTokensViaRelay(bridgeData, validRelayData); } } @@ -100,11 +94,6 @@ contract RelayFacetTest is TestBaseFacet { value: swapData[0].fromAmount }(bridgeData, swapData, validRelayData); } else { - validRelayData.callData = abi.encodeWithSignature( - "transfer(address,uint256)", - RELAY_SOLVER, - bridgeData.minAmount - ); relayFacet.swapAndStartBridgeTokensViaRelay( bridgeData, swapData, @@ -152,7 +141,6 @@ contract RelayFacetTest is TestBaseFacet { "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ) ), // Solana USDC - callData: "", signature: "" }); From d4bee97429e388b78b9ab2b42fd2cb119c742b6a Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 11 Nov 2024 10:19:49 +0300 Subject: [PATCH 32/45] Use floating pragma --- src/Facets/RelayFacet.sol | 2 +- templates/facet.template.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 769585ee0..08d1eaa73 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { LibDiamond } from "../Libraries/LibDiamond.sol"; diff --git a/templates/facet.template.hbs b/templates/facet.template.hbs index 831a1ee10..dafaa9fc5 100644 --- a/templates/facet.template.hbs +++ b/templates/facet.template.hbs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity ^0.8.17; import { ILiFi } from "../Interfaces/ILiFi.sol"; import { LibDiamond } from "../Libraries/LibDiamond.sol"; From aa4d0e6ee843c3b7ffa1f6023ec2686ac0e587a2 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 11 Nov 2024 11:33:02 +0300 Subject: [PATCH 33/45] Update version --- src/Libraries/LibAsset.sol | 2 +- test/solidity/Facets/RelayFacet.t.sol | 61 ++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/Libraries/LibAsset.sol b/src/Libraries/LibAsset.sol index 71511f59f..f4e9d7d55 100644 --- a/src/Libraries/LibAsset.sol +++ b/src/Libraries/LibAsset.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: UNLICENSED -/// @custom:version 1.0.0 pragma solidity ^0.8.17; import { InsufficientBalance, NullAddrIsNotAnERC20Token, NullAddrIsNotAValidSpender, NoTransferToNullAddress, InvalidAmount, NativeAssetTransferFailed } from "../Errors/GenericErrors.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -7,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { LibSwap } from "./LibSwap.sol"; /// @title LibAsset +/// @custom:version 1.0.1 /// @notice This library contains helpers for dealing with onchain transfers /// of assets, including accounting for the native asset `assetId` /// conventions and any noncompliant ERC20 transfers diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index a54e4d4c2..b3eda36c7 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -115,7 +115,7 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } - function testBase_CanBridgeTokensToNonEVMChain() + function test_CanBridgeTokensToNonEVMChain() public virtual assertBalanceChange( @@ -157,6 +157,65 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } + function test_CanSwapAndBridgeTokensToNonEVMChain() + public + virtual + assertBalanceChange( + ADDRESS_DAI, + USER_SENDER, + -int256(swapData[0].fromAmount) + ) + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_SENDER, 0) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 792703809; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked( + "EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb" + ) + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ) + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // prepare bridgeData + bridgeData.hasSourceSwaps = true; + + // reset swap data + setDefaultSwapDataSingleDAItoUSDC(); + + // approval + dai.approve(_facetTestContractAddress, swapData[0].fromAmount); + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit AssetSwapped( + bridgeData.transactionId, + ADDRESS_UNISWAP, + ADDRESS_DAI, + ADDRESS_USDC, + swapData[0].fromAmount, + bridgeData.minAmount, + block.timestamp + ); + + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + // execute call in child contract + initiateSwapAndBridgeTxWithFacet(false); + } + function signData( ILiFi.BridgeData memory _bridgeData, RelayFacet.RelayData memory _relayData From 5c18202b4dd457776f5a5fde7a6cebd5e6937ab9 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 11 Nov 2024 12:52:12 +0300 Subject: [PATCH 34/45] Add more tests --- test/solidity/Facets/RelayFacet.t.sol | 371 +++++++++++++++++++++++++- 1 file changed, 366 insertions(+), 5 deletions(-) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index b3eda36c7..ed8fe3715 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Unlicense pragma solidity 0.8.17; -import { LibAllowList, TestBaseFacet, console, ERC20, LibAsset } from "../utils/TestBaseFacet.sol"; +import { LibAllowList, TestBaseFacet, console, ERC20, LibAsset, LibSwap } from "../utils/TestBaseFacet.sol"; import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; @@ -115,7 +115,7 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } - function test_CanBridgeTokensToNonEVMChain() + function test_CanBridgeTokensToSolana() public virtual assertBalanceChange( @@ -128,7 +128,7 @@ contract RelayFacetTest is TestBaseFacet { assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) { bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; - bridgeData.destinationChainId = 792703809; + bridgeData.destinationChainId = 1151111081099710; validRelayData = RelayFacet.RelayData({ requestId: bytes32("1234"), nonEVMReceiver: bytes32( @@ -157,7 +157,50 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } - function test_CanSwapAndBridgeTokensToNonEVMChain() + function test_CanBridgeNativeTokensToSolana() + public + virtual + assertBalanceChange( + address(0), + USER_SENDER, + -int256((defaultNativeAmount + addToMessageValue)) + ) + assertBalanceChange(address(0), USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_SENDER, 0) + assertBalanceChange(ADDRESS_DAI, USER_SENDER, 0) + { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 1151111081099710; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked( + "EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb" + ) + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ) + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // customize bridgeData + bridgeData.sendingAssetId = address(0); + bridgeData.minAmount = defaultNativeAmount; + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + initiateBridgeTxWithFacet(true); + vm.stopPrank(); + } + + function test_CanSwapAndBridgeTokensToSolana() public virtual assertBalanceChange( @@ -170,7 +213,7 @@ contract RelayFacetTest is TestBaseFacet { assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) { bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; - bridgeData.destinationChainId = 792703809; + bridgeData.destinationChainId = 1151111081099710; validRelayData = RelayFacet.RelayData({ requestId: bytes32("1234"), nonEVMReceiver: bytes32( @@ -216,6 +259,324 @@ contract RelayFacetTest is TestBaseFacet { initiateSwapAndBridgeTxWithFacet(false); } + function test_CanSwapAndBridgeNativeTokensToSolana() + public + virtual + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 1151111081099710; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked( + "EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb" + ) + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ) + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + // store initial balances + uint256 initialUSDCBalance = usdc.balanceOf(USER_SENDER); + + // prepare bridgeData + bridgeData.hasSourceSwaps = true; + bridgeData.sendingAssetId = address(0); + + // prepare swap data + address[] memory path = new address[](2); + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_WRAPPED_NATIVE; + + uint256 amountOut = defaultNativeAmount; + + // Calculate USDC input amount + uint256[] memory amounts = uniswap.getAmountsIn(amountOut, path); + uint256 amountIn = amounts[0]; + + bridgeData.minAmount = amountOut; + + delete swapData; + swapData.push( + LibSwap.SwapData({ + callTo: address(uniswap), + approveTo: address(uniswap), + sendingAssetId: ADDRESS_USDC, + receivingAssetId: address(0), + fromAmount: amountIn, + callData: abi.encodeWithSelector( + uniswap.swapTokensForExactETH.selector, + amountOut, + amountIn, + path, + _facetTestContractAddress, + block.timestamp + 20 minutes + ), + requiresDeposit: true + }) + ); + + // approval + usdc.approve(_facetTestContractAddress, amountIn); + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit AssetSwapped( + bridgeData.transactionId, + ADDRESS_UNISWAP, + ADDRESS_USDC, + address(0), + swapData[0].fromAmount, + bridgeData.minAmount, + block.timestamp + ); + + //@dev the bridged amount will be higher than bridgeData.minAmount since the code will + // deposit all remaining ETH to the bridge. We cannot access that value (minAmount + remaining gas) + // therefore the test is designed to only check if an event was emitted but not match the parameters + vm.expectEmit(false, false, false, false, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + // execute call in child contract + initiateSwapAndBridgeTxWithFacet(false); + + // check balances after call + assertEq( + usdc.balanceOf(USER_SENDER), + initialUSDCBalance - swapData[0].fromAmount + ); + } + + function test_CanBridgeTokensToBitcoin() + public + virtual + assertBalanceChange( + ADDRESS_USDC, + USER_SENDER, + -int256(defaultUSDCAmount) + ) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_DAI, USER_SENDER, 0) + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 20000000000001; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked("bc1q6l08rtj6j907r2een0jqs6l7qnruwyxfshmf8a") + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked("bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8") + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // approval + usdc.approve(_facetTestContractAddress, bridgeData.minAmount); + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + initiateBridgeTxWithFacet(false); + vm.stopPrank(); + } + + function test_CanBridgeNativeTokensToBitcoin() + public + virtual + assertBalanceChange( + address(0), + USER_SENDER, + -int256((defaultNativeAmount + addToMessageValue)) + ) + assertBalanceChange(address(0), USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_SENDER, 0) + assertBalanceChange(ADDRESS_DAI, USER_SENDER, 0) + { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 20000000000001; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked("bc1q6l08rtj6j907r2een0jqs6l7qnruwyxfshmf8a") + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked("bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8") + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // customize bridgeData + bridgeData.sendingAssetId = address(0); + bridgeData.minAmount = defaultNativeAmount; + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + initiateBridgeTxWithFacet(true); + vm.stopPrank(); + } + + function test_CanSwapAndBridgeTokensToBitcoin() + public + virtual + assertBalanceChange( + ADDRESS_DAI, + USER_SENDER, + -int256(swapData[0].fromAmount) + ) + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_SENDER, 0) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 20000000000001; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked("bc1q6l08rtj6j907r2een0jqs6l7qnruwyxfshmf8a") + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked("bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8") + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // prepare bridgeData + bridgeData.hasSourceSwaps = true; + + // reset swap data + setDefaultSwapDataSingleDAItoUSDC(); + + // approval + dai.approve(_facetTestContractAddress, swapData[0].fromAmount); + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit AssetSwapped( + bridgeData.transactionId, + ADDRESS_UNISWAP, + ADDRESS_DAI, + ADDRESS_USDC, + swapData[0].fromAmount, + bridgeData.minAmount, + block.timestamp + ); + + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + // execute call in child contract + initiateSwapAndBridgeTxWithFacet(false); + } + + function test_CanSwapAndBridgeNativeTokensToBitcoin() + public + virtual + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 20000000000001; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked("bc1q6l08rtj6j907r2een0jqs6l7qnruwyxfshmf8a") + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked("bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8") + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + // store initial balances + uint256 initialUSDCBalance = usdc.balanceOf(USER_SENDER); + + // prepare bridgeData + bridgeData.hasSourceSwaps = true; + bridgeData.sendingAssetId = address(0); + + // prepare swap data + address[] memory path = new address[](2); + path[0] = ADDRESS_USDC; + path[1] = ADDRESS_WRAPPED_NATIVE; + + uint256 amountOut = defaultNativeAmount; + + // Calculate USDC input amount + uint256[] memory amounts = uniswap.getAmountsIn(amountOut, path); + uint256 amountIn = amounts[0]; + + bridgeData.minAmount = amountOut; + + delete swapData; + swapData.push( + LibSwap.SwapData({ + callTo: address(uniswap), + approveTo: address(uniswap), + sendingAssetId: ADDRESS_USDC, + receivingAssetId: address(0), + fromAmount: amountIn, + callData: abi.encodeWithSelector( + uniswap.swapTokensForExactETH.selector, + amountOut, + amountIn, + path, + _facetTestContractAddress, + block.timestamp + 20 minutes + ), + requiresDeposit: true + }) + ); + + // approval + usdc.approve(_facetTestContractAddress, amountIn); + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit AssetSwapped( + bridgeData.transactionId, + ADDRESS_UNISWAP, + ADDRESS_USDC, + address(0), + swapData[0].fromAmount, + bridgeData.minAmount, + block.timestamp + ); + + //@dev the bridged amount will be higher than bridgeData.minAmount since the code will + // deposit all remaining ETH to the bridge. We cannot access that value (minAmount + remaining gas) + // therefore the test is designed to only check if an event was emitted but not match the parameters + vm.expectEmit(false, false, false, false, _facetTestContractAddress); + emit LiFiTransferStarted(bridgeData); + + // execute call in child contract + initiateSwapAndBridgeTxWithFacet(false); + + // check balances after call + assertEq( + usdc.balanceOf(USER_SENDER), + initialUSDCBalance - swapData[0].fromAmount + ); + } + function signData( ILiFi.BridgeData memory _bridgeData, RelayFacet.RelayData memory _relayData From 7f3f8d86b03250851c7ce1a52b0e544e18952de0 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 11 Nov 2024 13:19:05 +0300 Subject: [PATCH 35/45] Add even more tests --- test/solidity/Facets/RelayFacet.t.sol | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index ed8fe3715..e983dad55 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -5,6 +5,12 @@ import { LibAllowList, TestBaseFacet, console, ERC20, LibAsset, LibSwap } from " import { RelayFacet } from "lifi/Facets/RelayFacet.sol"; import { ILiFi } from "lifi/Interfaces/ILiFi.sol"; +contract Reverter { + fallback() external { + revert("I always revert"); + } +} + // Stub RelayFacet Contract contract TestRelayFacet is RelayFacet { constructor( @@ -577,6 +583,55 @@ contract RelayFacetTest is TestBaseFacet { ); } + function testFail_RevertIsBubbledWhenBridgingTokens() + public + virtual + assertBalanceChange( + ADDRESS_USDC, + USER_SENDER, + -int256(defaultUSDCAmount) + ) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_DAI, USER_SENDER, 0) + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + { + vm.startPrank(USER_SENDER); + + // approval + usdc.approve(_facetTestContractAddress, bridgeData.minAmount); + + _makeRevertable(ADDRESS_USDC); + + vm.expectRevert("I always revert"); + initiateBridgeTxWithFacet(false); + vm.stopPrank(); + } + + function testFail_RevertIsBubbledWhenBridgingNativeTokensFails() + public + virtual + assertBalanceChange( + address(0), + USER_SENDER, + -int256((defaultNativeAmount + addToMessageValue)) + ) + assertBalanceChange(address(0), USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_SENDER, 0) + assertBalanceChange(ADDRESS_DAI, USER_SENDER, 0) + { + vm.startPrank(USER_SENDER); + + // customize bridgeData + bridgeData.sendingAssetId = address(0); + bridgeData.minAmount = defaultNativeAmount; + + _makeRevertable(RELAY_RECEIVER); + + vm.expectRevert("I always revert"); + initiateBridgeTxWithFacet(true); + vm.stopPrank(); + } + function signData( ILiFi.BridgeData memory _bridgeData, RelayFacet.RelayData memory _relayData @@ -618,4 +673,10 @@ contract RelayFacetTest is TestBaseFacet { return chainId; } + + function _makeRevertable(address target) internal { + Reverter reverter = new Reverter(); + bytes memory code = address(reverter).code; + vm.etch(target, code); + } } From c8de86dbabf0e3185737fc7df938989c1ac1d01f Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 11 Nov 2024 14:39:58 +0300 Subject: [PATCH 36/45] Use mock to surgically make call fail --- test/solidity/Facets/RelayFacet.t.sol | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index e983dad55..18976203e 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -490,6 +490,7 @@ contract RelayFacetTest is TestBaseFacet { // execute call in child contract initiateSwapAndBridgeTxWithFacet(false); + vm.stopPrank(); } function test_CanSwapAndBridgeNativeTokensToBitcoin() @@ -581,9 +582,10 @@ contract RelayFacetTest is TestBaseFacet { usdc.balanceOf(USER_SENDER), initialUSDCBalance - swapData[0].fromAmount ); + vm.stopPrank(); } - function testFail_RevertIsBubbledWhenBridgingTokens() + function testFail_RevertIsBubbledWhenBridgingTokensFails() public virtual assertBalanceChange( @@ -600,7 +602,15 @@ contract RelayFacetTest is TestBaseFacet { // approval usdc.approve(_facetTestContractAddress, bridgeData.minAmount); - _makeRevertable(ADDRESS_USDC); + vm.mockCallRevert( + ADDRESS_USDC, + abi.encodeWithSignature( + "transfer(address,uint256)", + RELAY_SOLVER, + bridgeData.minAmount + ), + "I always revert" + ); vm.expectRevert("I always revert"); initiateBridgeTxWithFacet(false); From 291d0a78bc4174b3ec29bb2ce0b27c6b5d3e8ec8 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 12 Nov 2024 13:28:10 +0300 Subject: [PATCH 37/45] Add explicit test for _getMappedChainId() --- test/solidity/Facets/RelayFacet.t.sol | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index 18976203e..e58ee3068 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -25,6 +25,12 @@ contract TestRelayFacet is RelayFacet { function setFunctionApprovalBySignature(bytes4 _signature) external { LibAllowList.addAllowedSelector(_signature); } + + function getMappedChainId( + uint256 chainId + ) external pure returns (uint256) { + return _getMappedChainId(chainId); + } } contract RelayFacetTest is TestBaseFacet { @@ -41,7 +47,7 @@ contract RelayFacetTest is TestBaseFacet { customBlockNumberForForking = 19767662; initTestBase(); relayFacet = new TestRelayFacet(RELAY_RECEIVER, RELAY_SOLVER); - bytes4[] memory functionSelectors = new bytes4[](4); + bytes4[] memory functionSelectors = new bytes4[](5); functionSelectors[0] = relayFacet.startBridgeTokensViaRelay.selector; functionSelectors[1] = relayFacet .swapAndStartBridgeTokensViaRelay @@ -50,6 +56,7 @@ contract RelayFacetTest is TestBaseFacet { functionSelectors[3] = relayFacet .setFunctionApprovalBySignature .selector; + functionSelectors[4] = relayFacet.getMappedChainId.selector; addFacet(diamond, address(relayFacet), functionSelectors); relayFacet = TestRelayFacet(address(diamond)); @@ -642,6 +649,23 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } + function test_mapsCorrectChainId(uint256 chainId) public { + uint256 mapped = relayFacet.getMappedChainId(chainId); + // Bitcoin + if (chainId == 20000000000001) { + assertEq(mapped, 8253038); + return; + } + + // Solana + if (chainId == 1151111081099710) { + assertEq(mapped, 792703809); + return; + } + + assertEq(mapped, chainId); + } + function signData( ILiFi.BridgeData memory _bridgeData, RelayFacet.RelayData memory _relayData From 2efd99ef72c9b7c752d5dd3c16d7089828baaae3 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 26 Nov 2024 11:53:55 +0300 Subject: [PATCH 38/45] update event --- src/Facets/RelayFacet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 08d1eaa73..e3abbba5c 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -186,7 +186,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { if (_bridgeData.receiver == LibAsset.NON_EVM_ADDRESS) { emit BridgeToNonEVMChain( _bridgeData.transactionId, - _bridgeData.destinationChainId, + _getMappedChainId(_bridgeData.destinationChainId), _relayData.nonEVMReceiver ); } From 4b3535fbbd6a3345f9f82900668a8c575048d562 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 26 Nov 2024 12:40:56 +0300 Subject: [PATCH 39/45] prevent replay --- src/Facets/RelayFacet.sol | 11 ++++++++ test/solidity/Facets/RelayFacet.t.sol | 36 ++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index e3abbba5c..1b23ee53b 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -21,6 +21,10 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { // Relayer wallet for ERC20 transfers address public immutable relaySolver; + /// Storage /// + + mapping(bytes32 => bool) public consumedIds; + /// Types /// /// @dev Relay specific parameters @@ -55,6 +59,11 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { ILiFi.BridgeData memory _bridgeData, RelayData calldata _relayData ) { + // Ensure that the id isn't already consumed + if (consumedIds[_relayData.requestId]) { + revert InvalidQuote(); + } + // Verify that the bridging quote has been signed by the Relay solver // as attested using the attestation API // API URL: https://api.relay.link/requests/{requestId}/signature/v2 @@ -182,6 +191,8 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { } } + consumedIds[_relayData.requestId] = true; + // Emit special event if bridging to non-EVM chain if (_bridgeData.receiver == LibAsset.NON_EVM_ADDRESS) { emit BridgeToNonEVMChain( diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index e58ee3068..cfd81689b 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -31,6 +31,10 @@ contract TestRelayFacet is RelayFacet { ) external pure returns (uint256) { return _getMappedChainId(chainId); } + + function setConsumedId(bytes32 id) external { + consumedIds[id] = true; + } } contract RelayFacetTest is TestBaseFacet { @@ -47,7 +51,7 @@ contract RelayFacetTest is TestBaseFacet { customBlockNumberForForking = 19767662; initTestBase(); relayFacet = new TestRelayFacet(RELAY_RECEIVER, RELAY_SOLVER); - bytes4[] memory functionSelectors = new bytes4[](5); + bytes4[] memory functionSelectors = new bytes4[](6); functionSelectors[0] = relayFacet.startBridgeTokensViaRelay.selector; functionSelectors[1] = relayFacet .swapAndStartBridgeTokensViaRelay @@ -57,6 +61,7 @@ contract RelayFacetTest is TestBaseFacet { .setFunctionApprovalBySignature .selector; functionSelectors[4] = relayFacet.getMappedChainId.selector; + functionSelectors[5] = relayFacet.setConsumedId.selector; addFacet(diamond, address(relayFacet), functionSelectors); relayFacet = TestRelayFacet(address(diamond)); @@ -170,6 +175,35 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } + function testRevert_WhenReplayingTransactionIds() public virtual { + relayFacet.setConsumedId(validRelayData.requestId); + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 1151111081099710; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32( + abi.encodePacked( + "EoW7FWTdPdZKpd3WAhH98c2HMGHsdh5yhzzEtk1u68Bb" + ) + ), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ) + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // approval + usdc.approve(_facetTestContractAddress, bridgeData.minAmount); + + vm.expectRevert(InvalidQuote.selector); + initiateBridgeTxWithFacet(false); + vm.stopPrank(); + } + function test_CanBridgeNativeTokensToSolana() public virtual From f285130f63f4ef8b1d8c94605ee5eb069c7eca19 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 26 Nov 2024 12:57:02 +0300 Subject: [PATCH 40/45] prevent empty non-evm addresses --- src/Facets/RelayFacet.sol | 8 ++++++++ test/solidity/Facets/RelayFacet.t.sol | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 1b23ee53b..2ea512b53 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -64,6 +64,14 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { revert InvalidQuote(); } + // Ensure nonEVMAddress is not empty + if ( + _bridgeData.receiver == LibAsset.NON_EVM_ADDRESS && + _relayData.nonEVMReceiver == bytes32(0) + ) { + revert InvalidQuote(); + } + // Verify that the bridging quote has been signed by the Relay solver // as attested using the attestation API // API URL: https://api.relay.link/requests/{requestId}/signature/v2 diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index cfd81689b..c444ce387 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -175,6 +175,30 @@ contract RelayFacetTest is TestBaseFacet { vm.stopPrank(); } + function testRevert_WhenUsingEmptyNonEVMAddress() public virtual { + bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; + bridgeData.destinationChainId = 1151111081099710; + validRelayData = RelayFacet.RelayData({ + requestId: bytes32("1234"), + nonEVMReceiver: bytes32(0), // DEV Wallet + receivingAssetId: bytes32( + abi.encodePacked( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + ) + ), // Solana USDC + signature: "" + }); + + vm.startPrank(USER_SENDER); + + // approval + usdc.approve(_facetTestContractAddress, bridgeData.minAmount); + + vm.expectRevert(InvalidQuote.selector); + initiateBridgeTxWithFacet(false); + vm.stopPrank(); + } + function testRevert_WhenReplayingTransactionIds() public virtual { relayFacet.setConsumedId(validRelayData.requestId); bridgeData.receiver = LibAsset.NON_EVM_ADDRESS; From a1fcd9a1fd41f5b9e5ff4d886c6230b2af3cd86c Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 26 Nov 2024 12:57:57 +0300 Subject: [PATCH 41/45] remove unused import --- src/Facets/RelayFacet.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Facets/RelayFacet.sol b/src/Facets/RelayFacet.sol index 2ea512b53..27acfc735 100644 --- a/src/Facets/RelayFacet.sol +++ b/src/Facets/RelayFacet.sol @@ -2,7 +2,6 @@ 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 { LibUtil } from "../Libraries/LibUtil.sol"; From fb3c3830858df0864360b14ccd45751087ee818b Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 12 Dec 2024 12:54:07 +0300 Subject: [PATCH 42/45] Update audit log --- audit/auditLog.json | 42 ++++++++++++++---- .../reports/2024-12-02_RelayFacet(v1.0.0).pdf | Bin 0 -> 54528 bytes 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 audit/reports/2024-12-02_RelayFacet(v1.0.0).pdf diff --git a/audit/auditLog.json b/audit/auditLog.json index 3cecd5418..03a396447 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -34,27 +34,53 @@ "auditorGitHandle": "sujithsomraaj", "auditReportPath": "./audit/reports/2024.11.05_EmergencyPauseFacet_ReAudit.pdf", "auditCommitHash": "da61880ba3847c07c35b64a78b957ff845ec18ac" + }, + "audit20241202": { + "auditCompletedOn": "02.12.2024", + "auditedBy": "Sujith Somraaj (individual security researcher)", + "auditorGitHandle": "sujithsomraaj", + "auditReportPath": "./audit/reports/2024-12-02_RelayFacet(v1.0.0).pdf", + "auditCommitHash": "291d0a78bc4174b3ec29bb2ce0b27c6b5d3e8ec8" } }, "auditedContracts": { "EmergencyPauseFacet": { - "1.0.0": ["audit20240913"], - "1.0.1": ["audit20241105"] + "1.0.0": [ + "audit20240913" + ], + "1.0.1": [ + "audit20241105" + ] }, "AcrossFacetV3": { - "1.0.0": ["audit20241007"] + "1.0.0": [ + "audit20241007" + ] }, "AcrossFacetPackedV3": { - "1.0.0": ["audit20241007"] + "1.0.0": [ + "audit20241007" + ] }, "ReceiverAcrossV3": { - "1.0.0": ["audit20241007"] + "1.0.0": [ + "audit20241007" + ] + }, + "RelayFacet": { + "1.0.0": [ + "audit20241202" + ] }, "StargateFacetV2": { - "1.0.1": ["audit20240814"] + "1.0.1": [ + "audit20240814" + ] }, "WithdrawablePeriphery": { - "1.0.0": ["audit20241014"] + "1.0.0": [ + "audit20241014" + ] } } -} +} \ No newline at end of file diff --git a/audit/reports/2024-12-02_RelayFacet(v1.0.0).pdf b/audit/reports/2024-12-02_RelayFacet(v1.0.0).pdf new file mode 100644 index 0000000000000000000000000000000000000000..2061dacd31d60de3f92f2eb3eed4961f1581a508 GIT binary patch literal 54528 zcmc$`V{|3lwk;alT(MQrifuco*tTukHY!HNR>gKIs#NTXD>hzz_w0SozGtW1`6$DH647iMe0xja>QcH&uF>6Kd- zPBbI31JV8{YnRp8+UTW2U#GK(y@%yA84IK$IH7)(yhnTD=&5j%$OOzl=hFwrcW zZy;fE?0jjm8+T+Uo7UB7g2ypRcR z{P=^UZ_f=DCf^qt6{7W@o6m%iI7KF_ zg7pG`TZ1()=AcxA0{UsIW?1>uclM_AVr(L_3T7lXKN`5B4p8GjW6BAeKxZ#)X62x% zA$Vgt(pPjDRC6LSxTlmczPa#$#XZEC$6pRd0*L`d^ni)QCt#BP~=kzZu}g zdbOFZR2-%s<(W zqnljTY_D|yQTaX3U!!*Yjj<-rolBxrFp+Q7!IRQg!A@+pOLLBLyquP7sw@Mr&A&^@ zcZ)$`q`tv;T#drcm zOl~)SxoNh`FIW2PlLZEW@d}g?;<;A4E;Ysi$kF4}@5NNf%0|Xt4u|dMZ~f#fb1MOU zPJ?6Fff_$Zz#$Tix7mrw;IF0nD~6;QJ}E8Gf>%@>MC(%q#JI`=T&=U_ z3RG|$Eq_=6OPqs*@59wj&olsD;)_pYOLH07hnjZ6F@a$YDeudlooUrqNnP8iKmE+K z_)eAISyM*M>KWS~7pD^Wjjr^H$T^@W?^u*5iq1H4RSeUWEVS|3sd=-Z>*vxyYnlIIa{a{rY~b!<9QaWH#^mT2Y0k8u3~{tO>twr@C@9o?_wH zg?KvkQh0kM_ULUHQr9+ZpBW<>~cKbJ{%+= z{NItIgYwqjxS~SeQi^~uD8CW4+d8z|;=VDxHqbVd)TgiaHr{WLb`G>kO0X-B5ifEn zwQwcg%Z-$STd3-g@7~2;y(8(>RPx~luRgnNs1S7&yis#cJvC zuQDpZ5uZ$m@8j(nKs*)wjt4#qq>p0g+GH{GgZ#j$RN4W#s(WU6y;y(TIVB?GdTIKA zpqNpd@EBx+JkOp7kEod~ zcvWdm*ty-+3VbfcUv-=-%@hgHC?!uK^(Uu~`+bVP`FPa->(}NvL(K+N1^S>KMp=Qg z`)~4!^n=9Y&`50ZY2AGvm*7S23*SxOR#o3tt?-}2uf$E%E2@W``=2h>yDZduHeT^O zOy$q_Ei*`IH#Nf((-D=Z81e^_CHR3uN;q65X?;Ky9%qLjcS>+>U_3^TLY@w_ksLyEzri=N}!{q9XPXE z;!JT^+LwzQ;!stUdr^kP?d7O0izN>?URx9o&wlawB#(qHg!#?*pk{2jG4zH4^H%eI z#{+3o51EK3{5=9X+#ebx<>p)H+IH1b!|5sdglqG>4OI_|o_h#&z* zH?2R`_≺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�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 Date: Thu, 12 Dec 2024 13:52:43 +0300 Subject: [PATCH 43/45] Update audit log --- audit/auditLog.json | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/audit/auditLog.json b/audit/auditLog.json index d0ef689be..5cf1afaee 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -84,23 +84,29 @@ ] }, "GasZipFacet": { - "2.0.0": ["audit20241107"] + "2.0.0": [ + "audit20241107" + ] }, "GasZipPeriphery": { - "1.0.0": ["audit20241107"] + "1.0.0": [ + "audit20241107" + ] }, "IGasZip": { - "1.0.0": ["audit20241107"] - }, - "EmergencyPauseFacet": { - "1.0.0": ["audit20240913"], - "1.0.1": ["audit20241105"] + "1.0.0": [ + "audit20241107" + ] }, "LiFiDEXAggregator": { - "1.5.0": ["audit20241203"] + "1.5.0": [ + "audit20241203" + ] }, "Permit2Proxy": { - "1.0.0": ["audit20241122"] + "1.0.0": [ + "audit20241122" + ] }, "ReceiverAcrossV3": { "1.0.0": [ @@ -112,6 +118,11 @@ "audit20241202" ] }, + "LibAsset": { + "1.0.1": [ + "audit20241202" + ] + }, "StargateFacetV2": { "1.0.1": [ "audit20240814" From 816030a9e1e8a6549af28fb633238356484673e3 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 12 Dec 2024 17:15:37 +0300 Subject: [PATCH 44/45] Test constructor --- test/solidity/Facets/RelayFacet.t.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/solidity/Facets/RelayFacet.t.sol b/test/solidity/Facets/RelayFacet.t.sol index c444ce387..0ac283bfe 100644 --- a/test/solidity/Facets/RelayFacet.t.sol +++ b/test/solidity/Facets/RelayFacet.t.sol @@ -120,6 +120,10 @@ contract RelayFacetTest is TestBaseFacet { } } + function test_CanDeployFacet() public virtual { + new RelayFacet(RELAY_RECEIVER, RELAY_SOLVER); + } + function testRevert_BridgeWithInvalidSignature() public virtual { vm.startPrank(USER_SENDER); From 7968629c6f76cb543b0bf3f494f6e2a1c89a55fa Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 13 Dec 2024 10:11:26 +0300 Subject: [PATCH 45/45] sort audited contracts --- audit/auditLog.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/audit/auditLog.json b/audit/auditLog.json index 5cf1afaee..aebd08999 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -65,12 +65,9 @@ } }, "auditedContracts": { - "EmergencyPauseFacet": { + "AcrossFacetPackedV3": { "1.0.0": [ - "audit20240913" - ], - "1.0.1": [ - "audit20241105" + "audit20241007" ] }, "AcrossFacetV3": { @@ -78,9 +75,12 @@ "audit20241007" ] }, - "AcrossFacetPackedV3": { + "EmergencyPauseFacet": { "1.0.0": [ - "audit20241007" + "audit20240913" + ], + "1.0.1": [ + "audit20241105" ] }, "GasZipFacet": { @@ -98,6 +98,11 @@ "audit20241107" ] }, + "LibAsset": { + "1.0.1": [ + "audit20241202" + ] + }, "LiFiDEXAggregator": { "1.5.0": [ "audit20241203" @@ -118,11 +123,6 @@ "audit20241202" ] }, - "LibAsset": { - "1.0.1": [ - "audit20241202" - ] - }, "StargateFacetV2": { "1.0.1": [ "audit20240814"