diff --git a/archive/scripts/Deploy/UpdateDeBridgeDlnFacet.s.sol b/archive/scripts/Deploy/UpdateDeBridgeDlnFacet.s.sol deleted file mode 100644 index 8b2397d7b..000000000 --- a/archive/scripts/Deploy/UpdateDeBridgeDlnFacet.s.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; - -contract DeployScript is UpdateScriptBase { - function run() - public - returns (address[] memory facets, bytes memory cutData) - { - return update("DeBridgeDlnFacet"); - } -} diff --git a/audit/auditLog.json b/audit/auditLog.json index fb599468d..56bde21e9 100644 --- a/audit/auditLog.json +++ b/audit/auditLog.json @@ -63,6 +63,12 @@ "auditReportPath": "./audit/reports/2024.12.03_LiFiDexAggregator.pdf", "auditCommitHash": "8a34562c912b5b19c919bb95338655c944428af5" }, + "audit20241205": { + "auditCompletedOn": "05.12.2024", + "auditedBy": "Sujith Somraaj (individual security researcher)", + "auditorGitHandle": "sujithsomraaj", + "auditReportPath": "./audit/reports/2024.12.05_DeBridgeDlnFacet(v1.0.0).pdf", + "auditCommitHash": "d72cb05510acc5bb794bde05af550ba2ef85d06d" "audit20241206": { "auditCompletedOn": "06.12.2024", "auditedBy": "Sujith Somraaj (individual security researcher)", @@ -95,6 +101,11 @@ "audit20241206" ] }, + "DeBridgeDlnFacet": { + "1.0.0": [ + "audit20241205" + ] + }, "EmergencyPauseFacet": { "1.0.0": [ "audit20240913" diff --git a/audit/reports/2024.12.05_DeBridgeDlnFacet(v1.0.0).pdf b/audit/reports/2024.12.05_DeBridgeDlnFacet(v1.0.0).pdf new file mode 100644 index 000000000..18c85023d Binary files /dev/null and b/audit/reports/2024.12.05_DeBridgeDlnFacet(v1.0.0).pdf differ diff --git a/config/dln.json b/config/dln.json index b28e26e23..2e1a6c6d4 100644 --- a/config/dln.json +++ b/config/dln.json @@ -1,26 +1,83 @@ { - "arbitrum": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + "networks": { + "arbitrum": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "avalanche": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "bsc": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "mainnet": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "polygon": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "linea": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "base": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "optimism": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "gnosis": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "metis": { + "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" + }, + "solana": { + "dlnSource": "src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4" + } }, - "avalanche": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" - }, - "bsc": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" - }, - "mainnet": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" - }, - "polygon": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" - }, - "linea": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" - }, - "base": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" - }, - "optimism": { - "dlnSource": "0xeF4fB24aD0916217251F553c0596F8Edc630EB66" - } + "mappings": [ + { + "chainId": 42161, + "deBridgeChainId": 42161 + }, + { + "chainId": 43114, + "deBridgeChainId": 43114 + }, + { + "chainId": 56, + "deBridgeChainId": 56 + }, + { + "chainId": 1, + "deBridgeChainId": 1 + }, + { + "chainId": 137, + "deBridgeChainId": 137 + }, + { + "chainId": 59144, + "deBridgeChainId": 59144 + }, + { + "chainId": 8453, + "deBridgeChainId": 8453 + }, + { + "chainId": 10, + "deBridgeChainId": 10 + }, + { + "chainId": 100, + "deBridgeChainId": 100000002 + }, + { + "chainId": 1088, + "deBridgeChainId": 100000004 + }, + { + "chainId": 1151111081099710, + "deBridgeChainId": 7565164 + } + ] } diff --git a/config/networks.json b/config/networks.json index a21cbdfbf..4b78c3449 100644 --- a/config/networks.json +++ b/config/networks.json @@ -460,7 +460,7 @@ "rpcUrl": "https://opbnb.drpc.org", "verificationType": "etherscan", "explorerUrl": "https://opbnb.bscscan.com/", - "explorerApiUrl": "", + "explorerApiUrl": "https://api-opbnb.bscscan.com/api", "multicallAddress": "0xcA11bde05977b3631167028862bE2a173976CA11", "safeApiUrl": "https://safe-transaction-opbnb-mainnet.bnbchain.org/api", "safeAddress": "0xaAD94196680Edb94B421bb5B1E1A0B27db686C72", diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index ba921733b..ff79e59ef 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -21199,9 +21199,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", + "ADDRESS": "0xE15C7585636e62b88bA47A40621287086E0c2E33", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-02-22 14:29:31", + "TIMESTAMP": "2024-10-14 15:56:11", "CONSTRUCTOR_ARGS": "0x000000000000000000000000ef4fb24ad0916217251f553c0596f8edc630eb66", "SALT": "", "VERIFIED": "true" @@ -21213,9 +21213,9 @@ "staging": { "1.0.0": [ { - "ADDRESS": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", + "ADDRESS": "0xE15C7585636e62b88bA47A40621287086E0c2E33", "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2024-02-22 15:43:42", + "TIMESTAMP": "2024-10-15 09:42:45", "CONSTRUCTOR_ARGS": "0x000000000000000000000000ef4fb24ad0916217251f553c0596f8edc630eb66", "SALT": "", "VERIFIED": "true" diff --git a/deployments/arbitrum.staging.json b/deployments/arbitrum.staging.json index 14875c2e3..96b7f9fe6 100644 --- a/deployments/arbitrum.staging.json +++ b/deployments/arbitrum.staging.json @@ -32,7 +32,8 @@ "ServiceFeeCollector": "0x9cc3164f01ED3796Fdf7Da538484D634608D2203", "WormholeFacet": "0x7260Fd3F8D0bEb06fF5935C6eadE9f406107c270", "SymbiosisFacet": "0x21571D628B0bCBeb954D5933A604eCac35bAF2c7", - "DeBridgeDlnFacet": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", + "DeBridgeDlnFacet": "0xE15C7585636e62b88bA47A40621287086E0c2E33", + "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "StandardizedCallFacet": "0xA7ffe57ee70Ac4998e9E9fC6f17341173E081A8f", "MayanFacet": "0xd596C903d78870786c5DB0E448ce7F87A65A0daD", "GenericSwapFacetV3": "0xFf6Fa203573Baaaa4AE375EB7ac2819d539e16FF", diff --git a/deployments/polygon.diamond.staging.json b/deployments/polygon.diamond.staging.json index 03e420933..8b19a0da3 100644 --- a/deployments/polygon.diamond.staging.json +++ b/deployments/polygon.diamond.staging.json @@ -118,8 +118,8 @@ "Version": "1.0.0" }, "0xE15C7585636e62b88bA47A40621287086E0c2E33": { - "Name": "", - "Version": "" + "Name": "DeBridgeDlnFacet", + "Version": "1.0.0" }, "0x74763722d92832d247cFa92825b06098cf72BAA2": { "Name": "RelayFacet", @@ -140,4 +140,3 @@ "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70" } } -} diff --git a/deployments/polygon.staging.json b/deployments/polygon.staging.json index 7f01b447d..679c6ad0f 100644 --- a/deployments/polygon.staging.json +++ b/deployments/polygon.staging.json @@ -34,7 +34,7 @@ "ServiceFeeCollector": "0x9cc3164f01ED3796Fdf7Da538484D634608D2203", "LiFuelFeeCollector": "0x94EA56D8049e93E0308B9c7d1418Baf6A7C68280", "AcrossFacetPacked": "0x7A3770a9504924d99D38BBba4F0116B756393Eb3", - "DeBridgeDlnFacet": "0xE500dED7b9C9f1020870B7a6Db076Dbd892C0fea", + "DeBridgeDlnFacet": "0xE15C7585636e62b88bA47A40621287086E0c2E33", "AmarokFacetPacked": "0x0aB252E7b5167Be2aC7841Bdaf1689E1a475ceE7", "TokenWrapper": "0xF63b27AE2Dc887b88f82E2Cc597d07fBB2E78E70", "GasRebateDistributor": "0x3116B8F099D7eFA6e24f39F80146Aac423365EB9", diff --git a/archive/docs/DeBridgeDlnFacet.md b/docs/DeBridgeDlnFacet.md similarity index 100% rename from archive/docs/DeBridgeDlnFacet.md rename to docs/DeBridgeDlnFacet.md diff --git a/docs/README.md b/docs/README.md index cb4f60b94..404deb64f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ - [CBridge Facet](./CBridgeFacet.md) - [Celer Circle Bridge Facet](./CelerCircleBridgeFacet.md) - [Circle Bridge Facet](./CircleBridgeFacet.md) -- [DeBridge Facet](./DeBridgeFacet.md) +- [DeBridge DLN Facet](/docs/DeBridgeDlnFacet.md) - [DEX Manager Facet](./DexManagerFacet.md) - [DiamondCut Facet](./DiamondCutFacet.md) - [DiamondLoupe Facet](./DiamondLoupeFacet.md) @@ -21,9 +21,7 @@ - [Hop Facet Packed](./HopFacetPacked.md) - [Hyphen Facet](./HyphenFacet.md) - [LIFuel Facet](./LIFuelFacet.md) -- [MakerTeleport Facet](./MakerTeleportFacet.md) - [Mayan Facet](./MayanFacet.md) -- [Multichain Facet](./MultichainFacet.md) - [OmniBridge Facet](./OmniBridgeFacet.md) - [Optimism Bridge Facet](./OptimismBridgeFacet.md) - [Periphery Registry Facet](./PeripheryRegistryFacet.md) @@ -34,7 +32,6 @@ - [Standardized Call Facet](./StandardizedCallFacet.md) - [Stargate Facet](./StargateFacet.md) - [Stargate FacetV2](./StargateFacetV2.md) -- [Synapse Bridge Facet](./SynapseBridgeFacet.md) - [ThorSwap Facet](./ThorSwapFacet.md) - [Withdraw Facet](./WithdrawFacet.md) diff --git a/script/demoScripts/demoDLN.ts b/script/demoScripts/demoDLN.ts index 9c725c7bf..cb849f9b1 100644 --- a/script/demoScripts/demoDLN.ts +++ b/script/demoScripts/demoDLN.ts @@ -1,4 +1,4 @@ -import deployments from '../../deployments/mainnet.staging.json' +import deployments from '../../deployments/arbitrum.staging.json' import { DeBridgeDlnFacet__factory, ILiFi, @@ -24,7 +24,7 @@ const main = async () => { // Bridge 5 ARB from Polygon to USDC on Optimism const resp = await fetch( - 'https://api.dln.trade/v1.0/dln/order/quote?srcChainId=42161&srcChainTokenIn=0x912CE59144191C1204E64559FE8253a0e49E6548&srcChainTokenInAmount=5000000000000000000&dstChainId=10&dstChainTokenOut=0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85&prependOperatingExpenses=false' + 'https://api.dln.trade/v1.0/dln/order/quote?srcChainId=42161&srcChainTokenIn=0x912CE59144191C1204E64559FE8253a0e49E6548&srcChainTokenInAmount=95000000000000000000&dstChainId=100000002&dstChainTokenOut=0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d&prependOperatingExpenses=false' ) const quote = await resp.json() @@ -42,7 +42,7 @@ const main = async () => { sendingAssetId: srcChainTokenIn.address, receiver: address, minAmount: srcChainTokenIn.amount, - destinationChainId: 10, + destinationChainId: 100, hasSourceSwaps: false, hasDestinationCall: false, } @@ -50,6 +50,7 @@ const main = async () => { const dlnData: DeBridgeDlnFacet.DeBridgeDlnDataStruct = { receivingAssetId: dstChainTokenOut.address, receiver: ethers.utils.solidityPack(['address'], [address]), + orderAuthorityDst: ethers.utils.solidityPack(['address'], [address]), minAmountOut: dstChainTokenOut.recommendedAmount, } @@ -65,7 +66,7 @@ const main = async () => { value: quote.fixFee, }) await tx.wait() - console.info('Bridged USDC') + console.info('Bridged ARB') } main() diff --git a/archive/scripts/Deploy/DeployDeBridgeDlnFacet.s.sol b/script/deploy/facets/DeployDeBridgeDlnFacet.s.sol similarity index 93% rename from archive/scripts/Deploy/DeployDeBridgeDlnFacet.s.sol rename to script/deploy/facets/DeployDeBridgeDlnFacet.s.sol index a99a01a54..224a13c5d 100644 --- a/archive/scripts/Deploy/DeployDeBridgeDlnFacet.s.sol +++ b/script/deploy/facets/DeployDeBridgeDlnFacet.s.sol @@ -26,7 +26,7 @@ contract DeployScript is DeployScriptBase { string memory json = vm.readFile(path); address dlnSource = json.readAddress( - string.concat(".", network, ".dlnSource") + string.concat(".networks.", network, ".dlnSource") ); return abi.encode(dlnSource); diff --git a/script/deploy/facets/UpdateDeBridgeDlnFacet.s.sol b/script/deploy/facets/UpdateDeBridgeDlnFacet.s.sol new file mode 100644 index 000000000..9dd809ec3 --- /dev/null +++ b/script/deploy/facets/UpdateDeBridgeDlnFacet.s.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { UpdateScriptBase } from "./utils/UpdateScriptBase.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { DeBridgeDlnFacet } from "lifi/Facets/DeBridgeDlnFacet.sol"; + +contract DeployScript is UpdateScriptBase { + using stdJson for string; + + struct ChainIdConfig { + uint256 chainId; + uint256 deBridgeChainId; + } + + function run() + public + returns (address[] memory facets, bytes memory cutData) + { + return update("DeBridgeDlnFacet"); + } + + function getCallData() internal override returns (bytes memory) { + path = string.concat(root, "/config/dln.json"); + json = vm.readFile(path); + bytes memory rawChains = json.parseRaw(".mappings"); + ChainIdConfig[] memory cidCfg = abi.decode( + rawChains, + (ChainIdConfig[]) + ); + + bytes memory callData = abi.encodeWithSelector( + DeBridgeDlnFacet.initDeBridgeDln.selector, + cidCfg + ); + + return callData; + } +} diff --git a/src/Facets/DeBridgeDlnFacet.sol b/src/Facets/DeBridgeDlnFacet.sol new file mode 100644 index 000000000..8650bad72 --- /dev/null +++ b/src/Facets/DeBridgeDlnFacet.sol @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { ILiFi } from "../Interfaces/ILiFi.sol"; +import { LibDiamond } from "../Libraries/LibDiamond.sol"; +import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol"; +import { LibSwap } from "../Libraries/LibSwap.sol"; +import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol"; +import { SwapperV2 } from "../Helpers/SwapperV2.sol"; +import { Validatable } from "../Helpers/Validatable.sol"; +import { IDlnSource } from "../Interfaces/IDlnSource.sol"; +import { NotInitialized } from "../Errors/GenericErrors.sol"; + +/// @title DeBridgeDLN Facet +/// @author LI.FI (https://li.fi) +/// @notice Provides functionality for bridging through DeBridge DLN +/// @custom:version 1.0.0 +contract DeBridgeDlnFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable { + /// Storage /// + + bytes32 internal constant NAMESPACE = + keccak256("com.lifi.facets.debridgedln"); + uint32 internal constant REFERRAL_CODE = 30729; + address internal constant NON_EVM_ADDRESS = + 0x11f111f111f111F111f111f111F111f111f111F1; + IDlnSource public immutable dlnSource; + + /// Types /// + + /// @param receivingAssetId The address of the asset to receive + /// @param receiver The address of the receiver + /// @param minAmountOut The minimum amount to receive on the destination chain + struct DeBridgeDlnData { + bytes receivingAssetId; + bytes receiver; + bytes orderAuthorityDst; + uint256 minAmountOut; + } + + struct Storage { + mapping(uint256 => uint256) deBridgeChainId; + bool initialized; + } + + struct ChainIdConfig { + uint256 chainId; + uint256 deBridgeChainId; + } + + /// Errors /// + + error UnknownDeBridgeChain(); + error EmptyNonEVMAddress(); + error InvalidConfig(); + + /// Events /// + + event DeBridgeInitialized(ChainIdConfig[] chainIdConfigs); + + event DlnOrderCreated(bytes32 indexed orderId); + + event DeBridgeChainIdSet(uint256 indexed chainId, uint256 deBridgeChainId); + + event BridgeToNonEVMChain( + bytes32 indexed transactionId, + uint256 indexed destinationChainId, + bytes receiver + ); + + /// Modifiers /// + + modifier onlyValidReceiverAddress(DeBridgeDlnData calldata _deBridgeData) { + // Ensure nonEVMAddress is not empty + if (_deBridgeData.receiver.length == 0) { + revert EmptyNonEVMAddress(); + } + _; + } + + /// Constructor /// + + /// @notice Constructor for the contract. + /// @param _dlnSource The address of the DLN order creation contract + constructor(IDlnSource _dlnSource) { + dlnSource = _dlnSource; + } + + /// Init /// + + /// @notice Initialize local variables for the DeBridgeDln Facet + /// @param chainIdConfigs Chain Id configuration data + function initDeBridgeDln( + ChainIdConfig[] calldata chainIdConfigs + ) external { + if (chainIdConfigs.length == 0) revert InvalidConfig(); + LibDiamond.enforceIsContractOwner(); + + Storage storage sm = getStorage(); + + for (uint256 i = 0; i < chainIdConfigs.length; i++) { + sm.deBridgeChainId[chainIdConfigs[i].chainId] = chainIdConfigs[i] + .deBridgeChainId; + } + + sm.initialized = true; + emit DeBridgeInitialized(chainIdConfigs); + } + + /// External Methods /// + + /// @notice Bridges tokens via DeBridgeDLN + /// @param _bridgeData The core information needed for bridging + /// @param _deBridgeData Data specific to DeBridgeDLN + function startBridgeTokensViaDeBridgeDln( + ILiFi.BridgeData memory _bridgeData, + DeBridgeDlnData calldata _deBridgeData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + validateBridgeData(_bridgeData) + onlyValidReceiverAddress(_deBridgeData) + doesNotContainSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + { + LibAsset.depositAsset( + _bridgeData.sendingAssetId, + _bridgeData.minAmount + ); + _startBridge( + _bridgeData, + _deBridgeData, + dlnSource.globalFixedNativeFee() + ); + } + + /// @notice Performs a swap before bridging via DeBridgeDLN + /// @param _bridgeData The core information needed for bridging + /// @param _swapData An array of swap related data for performing swaps before bridging + /// @param _deBridgeData Data specific to DeBridgeDLN + function swapAndStartBridgeTokensViaDeBridgeDln( + ILiFi.BridgeData memory _bridgeData, + LibSwap.SwapData[] calldata _swapData, + DeBridgeDlnData calldata _deBridgeData + ) + external + payable + nonReentrant + refundExcessNative(payable(msg.sender)) + containsSourceSwaps(_bridgeData) + doesNotContainDestinationCalls(_bridgeData) + validateBridgeData(_bridgeData) + onlyValidReceiverAddress(_deBridgeData) + { + uint256 fee = dlnSource.globalFixedNativeFee(); + address assetId = _bridgeData.sendingAssetId; + _bridgeData.minAmount = _depositAndSwap( + _bridgeData.transactionId, + _bridgeData.minAmount, + _swapData, + payable(msg.sender), + LibAsset.isNativeAsset(assetId) ? 0 : fee + ); + _startBridge(_bridgeData, _deBridgeData, fee); + } + + /// Internal Methods /// + + /// @dev Contains the business logic for the bridge via DeBridgeDLN + /// @param _bridgeData The core information needed for bridging + /// @param _deBridgeData Data specific to DeBridgeDLN + function _startBridge( + ILiFi.BridgeData memory _bridgeData, + DeBridgeDlnData calldata _deBridgeData, + uint256 _fee + ) internal { + IDlnSource.OrderCreation memory orderCreation = IDlnSource + .OrderCreation({ + giveTokenAddress: _bridgeData.sendingAssetId, + giveAmount: _bridgeData.minAmount, + takeTokenAddress: _deBridgeData.receivingAssetId, + takeAmount: _deBridgeData.minAmountOut, + takeChainId: getDeBridgeChainId( + _bridgeData.destinationChainId + ), + receiverDst: _deBridgeData.receiver, + givePatchAuthoritySrc: msg.sender, + orderAuthorityAddressDst: _deBridgeData.orderAuthorityDst, + allowedTakerDst: "", + externalCall: "", + allowedCancelBeneficiarySrc: abi.encodePacked(msg.sender) + }); + + bytes32 orderId; + if (!LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) { + // Give the DLN Source approval to bridge tokens + LibAsset.maxApproveERC20( + IERC20(_bridgeData.sendingAssetId), + address(dlnSource), + _bridgeData.minAmount + ); + + orderId = dlnSource.createOrder{ value: _fee }( + orderCreation, + "", + REFERRAL_CODE, + "" + ); + } else { + orderCreation.giveAmount = orderCreation.giveAmount - _fee; + orderId = dlnSource.createOrder{ value: _bridgeData.minAmount }( + orderCreation, + "", + REFERRAL_CODE, + "" + ); + } + + emit DlnOrderCreated(orderId); + + if (_bridgeData.receiver == NON_EVM_ADDRESS) { + emit BridgeToNonEVMChain( + _bridgeData.transactionId, + _bridgeData.destinationChainId, + _deBridgeData.receiver + ); + } + + emit LiFiTransferStarted(_bridgeData); + } + + /// Mappings management /// + + /// @notice Sets the DeBridge chain ID for a given chain ID + /// @param _chainId uint256 of the chain ID + /// @param _deBridgeChainId uint256 of the DeBridge chain ID + /// @dev This is used to map a chain ID to its DeBridge chain ID + function setDeBridgeChainId( + uint256 _chainId, + uint256 _deBridgeChainId + ) external { + LibDiamond.enforceIsContractOwner(); + Storage storage sm = getStorage(); + + if (!sm.initialized) { + revert NotInitialized(); + } + + sm.deBridgeChainId[_chainId] = _deBridgeChainId; + emit DeBridgeChainIdSet(_chainId, _deBridgeChainId); + } + + /// @notice Gets the DeBridge chain ID for a given chain ID + /// @param _chainId uint256 of the chain ID + /// @return uint256 of the DeBridge chain ID + function getDeBridgeChainId( + uint256 _chainId + ) public view returns (uint256) { + Storage storage sm = getStorage(); + uint256 chainId = sm.deBridgeChainId[_chainId]; + if (chainId == 0) revert UnknownDeBridgeChain(); + return chainId; + } + + /// @dev fetch local storage + function getStorage() private pure returns (Storage storage s) { + bytes32 namespace = NAMESPACE; + // solhint-disable-next-line no-inline-assembly + assembly { + s.slot := namespace + } + } +} diff --git a/archive/test/DeBridgeDlnFacet.t.sol b/test/solidity/Facets/DeBridgeDlnFacet.t.sol similarity index 51% rename from archive/test/DeBridgeDlnFacet.t.sol rename to test/solidity/Facets/DeBridgeDlnFacet.t.sol index 8df35caa7..f8e4c9510 100644 --- a/archive/test/DeBridgeDlnFacet.t.sol +++ b/test/solidity/Facets/DeBridgeDlnFacet.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.17; import { LibAllowList, TestBaseFacet, console, ERC20, LibSwap } from "../utils/TestBaseFacet.sol"; import { DeBridgeDlnFacet } from "lifi/Facets/DeBridgeDlnFacet.sol"; import { IDlnSource } from "lifi/Interfaces/IDlnSource.sol"; +import { stdJson } from "forge-std/StdJson.sol"; // Stub DeBridgeDlnFacet Contract contract TestDeBridgeDlnFacet is DeBridgeDlnFacet { @@ -19,18 +20,23 @@ contract TestDeBridgeDlnFacet is DeBridgeDlnFacet { } contract DeBridgeDlnFacetTest is TestBaseFacet { + using stdJson for string; + DeBridgeDlnFacet.DeBridgeDlnData internal validDeBridgeDlnData; TestDeBridgeDlnFacet internal deBridgeDlnFacet; IDlnSource internal DLN_SOURCE = IDlnSource(0xeF4fB24aD0916217251F553c0596F8Edc630EB66); uint256 internal FIXED_FEE; + // Errors + error EmptyNonEVMAddress(); + function setUp() public { customBlockNumberForForking = 19279222; initTestBase(); deBridgeDlnFacet = new TestDeBridgeDlnFacet(DLN_SOURCE); - bytes4[] memory functionSelectors = new bytes4[](4); + bytes4[] memory functionSelectors = new bytes4[](5); functionSelectors[0] = deBridgeDlnFacet .startBridgeTokensViaDeBridgeDln .selector; @@ -41,6 +47,7 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { functionSelectors[3] = deBridgeDlnFacet .setFunctionApprovalBySignature .selector; + functionSelectors[4] = DeBridgeDlnFacet.initDeBridgeDln.selector; addFacet(diamond, address(deBridgeDlnFacet), functionSelectors); deBridgeDlnFacet = TestDeBridgeDlnFacet(address(diamond)); @@ -55,6 +62,20 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { uniswap.swapETHForExactTokens.selector ); + // Initialize + string memory path = string.concat( + vm.projectRoot(), + "/config/dln.json" + ); + string memory json = vm.readFile(path); + bytes memory rawChains = json.parseRaw(".mappings"); + DeBridgeDlnFacet.ChainIdConfig[] memory cidCfg = abi.decode( + rawChains, + (DeBridgeDlnFacet.ChainIdConfig[]) + ); + + deBridgeDlnFacet.initDeBridgeDln(cidCfg); + setFacetAddressInTestBase( address(deBridgeDlnFacet), "DeBridgeDlnFacet" @@ -70,6 +91,7 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 ), // Polygon USDC receiver: abi.encodePacked(USER_RECEIVER), + orderAuthorityDst: abi.encodePacked(USER_RECEIVER), minAmountOut: (defaultUSDCAmount * 95) / 100 }); @@ -176,4 +198,144 @@ contract DeBridgeDlnFacetTest is TestBaseFacet { initialETHBalance - swapData[0].fromAmount - FIXED_FEE ); } + + function test_CanBridgeToNonEVMAddress() + public + assertBalanceChange(ADDRESS_DAI, USER_RECEIVER, 0) + assertBalanceChange(ADDRESS_USDC, USER_RECEIVER, 0) + { + vm.startPrank(USER_SENDER); + // store initial balances + uint256 initialETHBalance = USER_SENDER.balance; + + // prepare bridgeData + bridgeData.hasSourceSwaps = true; + bridgeData.sendingAssetId = ADDRESS_USDC; + + // prepare swap data + address[] memory path = new address[](2); + + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; + + uint256 amountOut = defaultUSDCAmount; + + // 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(0), + receivingAssetId: ADDRESS_USDC, + fromAmount: amountIn, + callData: abi.encodeWithSelector( + uniswap.swapETHForExactTokens.selector, + amountOut, + path, + _facetTestContractAddress, + block.timestamp + 20 minutes + ), + requiresDeposit: true + }) + ); + + //prepare check for events + vm.expectEmit(true, true, true, true, _facetTestContractAddress); + emit AssetSwapped( + bridgeData.transactionId, + ADDRESS_UNISWAP, + address(0), + ADDRESS_USDC, + swapData[0].fromAmount, + bridgeData.minAmount, + block.timestamp + ); + + // This is just a random Solana Address for testing + validDeBridgeDlnData + .receiver = hex"e275bc4764effea023f35f3c60b2260c93248b74ec8d63ee215d40317e98b014"; // [pre-commit-checker: not a secret] + validDeBridgeDlnData + .orderAuthorityDst = hex"e275bc4764effea023f35f3c60b2260c93248b74ec8d63ee215d40317e98b014"; // [pre-commit-checker: not a secret] + // SOL Token + validDeBridgeDlnData + .receivingAssetId = hex"0000000000000000000000000000000000000000000000000000000000000000"; // [pre-commit-checker: not a secret] + + // Setup to bridge to Solana + bridgeData.destinationChainId = 1151111081099710; + bridgeData.receiver = 0x11f111f111f111F111f111f111F111f111f111F1; + + //@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(true); + + // check balances after call + assertEq( + USER_SENDER.balance, + initialETHBalance - swapData[0].fromAmount - FIXED_FEE + ); + } + + function testRevert_WhenBridgingToEmptyReceiverAddress() public { + vm.startPrank(USER_SENDER); + + // prepare bridgeData + bridgeData.hasSourceSwaps = true; + bridgeData.sendingAssetId = ADDRESS_USDC; + + // prepare swap data + address[] memory path = new address[](2); + + path[0] = ADDRESS_WRAPPED_NATIVE; + path[1] = ADDRESS_USDC; + + uint256 amountOut = defaultUSDCAmount; + + // 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(0), + receivingAssetId: ADDRESS_USDC, + fromAmount: amountIn, + callData: abi.encodeWithSelector( + uniswap.swapETHForExactTokens.selector, + amountOut, + path, + _facetTestContractAddress, + block.timestamp + 20 minutes + ), + requiresDeposit: true + }) + ); + + // This is just a random Solana Address for testing + validDeBridgeDlnData.receiver = ""; // [pre-commit-checker: not a secret] + // SOL Token + validDeBridgeDlnData + .receivingAssetId = hex"0000000000000000000000000000000000000000000000000000000000000000"; // [pre-commit-checker: not a secret] + + // Setup to bridge to Solana + bridgeData.destinationChainId = 137; + + vm.expectRevert(EmptyNonEVMAddress.selector); + initiateSwapAndBridgeTxWithFacet(true); + } }