Skip to content
Merged
4 changes: 2 additions & 2 deletions conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ Follow the folder structure to locate resources and generate or modify code in a
);
}
```
- When a non-EVM address is used, the `bridgeData.receiver` must contain `LibAsset.NON_EVM_ADDRESS`, ensuring proper handling:
- When a non-EVM address is used, the `bridgeData.receiver` must contain `NON_EVM_ADDRESS` (found in src/Helpers/LiFiData.sol), ensuring proper handling:
```
if (
bridgeData.receiver != LibAsset.NON_EVM_ADDRESS
bridgeData.receiver != NON_EVM_ADDRESS
) {
revert InvalidCallData();
}
Expand Down
2 changes: 1 addition & 1 deletion docs/ChainflipFacet.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct ChainflipData {
}
```

For non-EVM destinations (i.e. Solana, Bitcoin), set the `receiver` in `BridgeData` to `LibAsset.NON_EVM_ADDRESS` and provide the destination address in `nonEVMReceiver`.
For non-EVM destinations (i.e. Solana, Bitcoin), set the `receiver` in `BridgeData` to `NON_EVM_ADDRESS` (inherit from src/Helpers/LiFiData.sol) and provide the destination address in `nonEVMReceiver`.

## Supported Chains

Expand Down
1 change: 1 addition & 0 deletions src/Errors/GenericErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ error InvalidConfig();
error InvalidContract();
error InvalidDestinationChain();
error InvalidFallbackAddress();
error InvalidNonEVMReceiver();
error InvalidReceiver();
error InvalidSendingToken();
error NativeAssetNotSupported();
Expand Down
112 changes: 102 additions & 10 deletions src/Facets/AllBridgeFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,55 @@
import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol";
import { Validatable } from "../Helpers/Validatable.sol";
import { LibSwap } from "../Libraries/LibSwap.sol";
import { InvalidConfig, InvalidCallData, InvalidNonEVMReceiver, InvalidReceiver } from "../Errors/GenericErrors.sol";
import { LiFiData } from "../Helpers/LiFiData.sol";

/// @title Allbridge Facet
/// @author Li.Finance (https://li.finance)
/// @author LI.FI (https://li.fi)
/// @notice Provides functionality for bridging through AllBridge
/// @custom:version 2.0.0
contract AllBridgeFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {
/// @custom:version 2.1.0
contract AllBridgeFacet is
ILiFi,
ReentrancyGuard,
SwapperV2,
Validatable,
LiFiData
{
uint32 private constant ALLBRIDGE_ID_ETHEREUM = 1;
uint32 private constant ALLBRIDGE_ID_BSC = 2;
uint32 private constant ALLBRIDGE_ID_TRON = 3;
uint32 private constant ALLBRIDGE_ID_SOLANA = 4;
uint32 private constant ALLBRIDGE_ID_POLYGON = 5;
uint32 private constant ALLBRIDGE_ID_ARBITRUM = 6;
uint32 private constant ALLBRIDGE_ID_AVALANCHE = 8;
uint32 private constant ALLBRIDGE_ID_BASE = 9;
uint32 private constant ALLBRIDGE_ID_OPTIMISM = 10;
uint32 private constant ALLBRIDGE_ID_CELO = 11;
uint32 private constant ALLBRIDGE_ID_SUI = 13;
uint256 internal constant LIFI_CHAIN_ID_ARBITRUM = 42161;
uint256 internal constant LIFI_CHAIN_ID_AVALANCHE = 43114;
uint256 internal constant LIFI_CHAIN_ID_BASE = 8453;
uint256 internal constant LIFI_CHAIN_ID_BSC = 56;
uint256 internal constant LIFI_CHAIN_ID_CELO = 42220;
uint256 internal constant LIFI_CHAIN_ID_POLYGON = 137;

error UnsupportedAllBridgeChainId();

/// @notice The contract address of the AllBridge router on the source chain.
// solhint-disable-next-line immutable-vars-naming
IAllBridge private immutable allBridge;
IAllBridge private immutable ALLBRIDGE;

/// @notice The struct for the AllBridge data.
/// @param fees The amount of token to pay the messenger and the bridge
/// @param recipient The address of the token receiver after bridging.
/// @param fees The amount of token to pay the messenger and the bridge
/// @param destinationChainId The destination chain id.
/// @param receiveToken The token to receive on the destination chain.
/// @param nonce A random nonce to associate with the tx.
/// @param messenger The messenger protocol enum
/// @param payFeeWithSendingAsset Whether to pay the relayer fee with the sending asset or not
struct AllBridgeData {
uint256 fees;
bytes32 recipient;
uint256 fees;
uint256 destinationChainId;
bytes32 receiveToken;
uint256 nonce;
Expand All @@ -39,7 +67,9 @@
/// @notice Initializes the AllBridge contract
/// @param _allBridge The address of the AllBridge contract
constructor(IAllBridge _allBridge) {
allBridge = _allBridge;
if (address(_allBridge) == address(0)) revert InvalidConfig();

ALLBRIDGE = _allBridge;
}

/// @notice Bridge tokens to another chain via AllBridge
Expand Down Expand Up @@ -96,14 +126,38 @@
ILiFi.BridgeData memory _bridgeData,
AllBridgeData calldata _allBridgeData
) internal {
// make sure destinationChainId matches in bridgeData and allBridgeData
if (
_allBridgeData.destinationChainId !=
_getAllBridgeChainId(_bridgeData.destinationChainId)
) revert InvalidCallData();

// validate receiver address
if (_bridgeData.receiver == NON_EVM_ADDRESS) {
// destination chain is non-EVM
// make sure it's non-zero (we cannot validate further)
if (_allBridgeData.recipient == bytes32(0))
revert InvalidNonEVMReceiver();
} else {
// destination chain is EVM
// make sure that bridgeData and allBridgeData receiver addresses match
if (
_bridgeData.receiver !=
address(uint160(uint256(_allBridgeData.recipient)))
) revert InvalidReceiver();
}

// set max approval to allBridge, if current allowance is insufficient
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
address(allBridge),
address(ALLBRIDGE),
_bridgeData.minAmount
);

// check if bridge fee should be paid with sending or native asset
if (_allBridgeData.payFeeWithSendingAsset) {
allBridge.swapAndBridge(
// pay fee with sending asset
ALLBRIDGE.swapAndBridge(
bytes32(uint256(uint160(_bridgeData.sendingAssetId))),
_bridgeData.minAmount,
_allBridgeData.recipient,
Expand All @@ -114,7 +168,8 @@
_allBridgeData.fees
);
} else {
allBridge.swapAndBridge{ value: _allBridgeData.fees }(
// pay fee with native asset
ALLBRIDGE.swapAndBridge{ value: _allBridgeData.fees }(
bytes32(uint256(uint160(_bridgeData.sendingAssetId))),
_bridgeData.minAmount,
_allBridgeData.recipient,
Expand All @@ -128,4 +183,41 @@

emit LiFiTransferStarted(_bridgeData);
}

/// @notice Converts LiFi internal chain IDs to AllBridge chain IDs
/// @param _destinationChainId The LiFi chain ID to convert
/// @return The corresponding Chainflip chain ID
/// @dev Reverts if the destination chain is not supported
function _getAllBridgeChainId(
uint256 _destinationChainId
) internal pure returns (uint256) {
// split possible values in half for more efficient search/matching

// first try to match cases where chainId is the same and does not need to be mapped
if (
_destinationChainId == ALLBRIDGE_ID_ETHEREUM ||
_destinationChainId == ALLBRIDGE_ID_OPTIMISM
) return _destinationChainId;
// all others have custom chainIds
else if (_destinationChainId == LIFI_CHAIN_ID_BSC)
return ALLBRIDGE_ID_BSC;
else if (_destinationChainId == LIFI_CHAIN_ID_TRON)
return ALLBRIDGE_ID_TRON;
else if (_destinationChainId == LIFI_CHAIN_ID_SOLANA)
return ALLBRIDGE_ID_SOLANA;
else if (_destinationChainId == LIFI_CHAIN_ID_POLYGON)
return ALLBRIDGE_ID_POLYGON;
else if (_destinationChainId == LIFI_CHAIN_ID_ARBITRUM)
return ALLBRIDGE_ID_ARBITRUM;
else if (_destinationChainId == LIFI_CHAIN_ID_AVALANCHE)
return ALLBRIDGE_ID_AVALANCHE;
else if (_destinationChainId == LIFI_CHAIN_ID_BASE)
return ALLBRIDGE_ID_BASE;
else if (_destinationChainId == LIFI_CHAIN_ID_CELO)
return ALLBRIDGE_ID_CELO;
else if (_destinationChainId == LIFI_CHAIN_ID_SUI)
return ALLBRIDGE_ID_SUI;
// revert if no match found
else revert UnsupportedAllBridgeChainId();
}
}
15 changes: 11 additions & 4 deletions src/Facets/ChainflipFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ import { Validatable } from "../Helpers/Validatable.sol";
import { IChainflipVault } from "../Interfaces/IChainflip.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { InformationMismatch, InvalidConfig, InvalidReceiver } from "../Errors/GenericErrors.sol";
import { LiFiData } from "../Helpers/LiFiData.sol";

/// @title Chainflip Facet
/// @title ChainflipFacet
/// @author LI.FI (https://li.fi)
/// @notice Allows bridging assets via Chainflip
/// @custom:version 1.0.0
contract ChainflipFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {
/// @custom:version 1.0.1
contract ChainflipFacet is
ILiFi,
ReentrancyGuard,
SwapperV2,
Validatable,
LiFiData
{
/// Events ///
event BridgeToNonEVMChain(
bytes32 indexed transactionId,
Expand Down Expand Up @@ -134,7 +141,7 @@ contract ChainflipFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {

// Handle address encoding based on destination chain type
bytes memory encodedDstAddress;
if (_bridgeData.receiver == LibAsset.NON_EVM_ADDRESS) {
if (_bridgeData.receiver == NON_EVM_ADDRESS) {
if (_chainflipData.nonEVMReceiver.length == 0) {
revert EmptyNonEvmAddress();
}
Expand Down
19 changes: 13 additions & 6 deletions src/Facets/RelayFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ 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";
import { LiFiData } from "../Helpers/LiFiData.sol";

/// @title Relay Facet
/// @title RelayFacet
/// @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 {
/// @custom:version 1.0.1
contract RelayFacet is
ILiFi,
ReentrancyGuard,
SwapperV2,
Validatable,
LiFiData
{
// Receiver for native transfers
// solhint-disable-next-line immutable-vars-naming
address public immutable relayReceiver;
Expand Down Expand Up @@ -67,7 +74,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {

// Ensure nonEVMAddress is not empty
if (
_bridgeData.receiver == LibAsset.NON_EVM_ADDRESS &&
_bridgeData.receiver == NON_EVM_ADDRESS &&
_relayData.nonEVMReceiver == bytes32(0)
) {
revert InvalidQuote();
Expand All @@ -84,7 +91,7 @@ contract RelayFacet is ILiFi, ReentrancyGuard, SwapperV2, Validatable {
bytes32(uint256(uint160(address(this)))),
bytes32(uint256(uint160(_bridgeData.sendingAssetId))),
_getMappedChainId(_bridgeData.destinationChainId),
_bridgeData.receiver == LibAsset.NON_EVM_ADDRESS
_bridgeData.receiver == NON_EVM_ADDRESS
? _relayData.nonEVMReceiver
: bytes32(uint256(uint160(_bridgeData.receiver))),
_relayData.receivingAssetId
Expand Down Expand Up @@ -203,7 +210,7 @@ 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) {
if (_bridgeData.receiver == NON_EVM_ADDRESS) {
emit BridgeToNonEVMChain(
_bridgeData.transactionId,
_getMappedChainId(_bridgeData.destinationChainId),
Expand Down
21 changes: 21 additions & 0 deletions src/Helpers/LiFiData.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

/// @title LiFiData
/// @author LI.FI (https://li.fi)
/// @notice A storage for LI.FI-internal config data (addresses, chainIDs, etc.)
/// @custom:version 1.0.0
contract LiFiData {
address internal constant NON_EVM_ADDRESS =
0x11f111f111f111F111f111f111F111f111f111F1;

// LI.FI non-EVM Custom Chain IDs (IDs are made up by the LI.FI team)
uint256 internal constant LIFI_CHAIN_ID_APTOS = 9271000000000010;
uint256 internal constant LIFI_CHAIN_ID_BCH = 20000000000002;
uint256 internal constant LIFI_CHAIN_ID_BTC = 20000000000001;
uint256 internal constant LIFI_CHAIN_ID_DGE = 20000000000004;
uint256 internal constant LIFI_CHAIN_ID_LTC = 20000000000003;
uint256 internal constant LIFI_CHAIN_ID_SOLANA = 1151111081099710;
uint256 internal constant LIFI_CHAIN_ID_SUI = 9270000000000000;
uint256 internal constant LIFI_CHAIN_ID_TRON = 1885080386571452;
}
6 changes: 3 additions & 3 deletions src/Helpers/SwapperV2.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
/// @custom:version 1.0.0
pragma solidity ^0.8.17;

import { ILiFi } from "../Interfaces/ILiFi.sol";
Expand All @@ -8,9 +7,10 @@ import { LibAsset } from "../Libraries/LibAsset.sol";
import { LibAllowList } from "../Libraries/LibAllowList.sol";
import { ContractCallNotAllowed, NoSwapDataProvided, CumulativeSlippageTooHigh } from "../Errors/GenericErrors.sol";

/// @title Swapper
/// @title SwapperV2
/// @author LI.FI (https://li.fi)
/// @notice Abstract contract to provide swap functionality
/// @custom:version 1.0.1
contract SwapperV2 is ILiFi {
/// Types ///

Expand Down Expand Up @@ -119,7 +119,7 @@ contract SwapperV2 is ILiFi {

if (finalBalance > initialBalance) {
LibAsset.transferAsset(
LibAsset.NATIVE_ASSETID,
LibAsset.NULL_ADDRESS,
_refundReceiver,
finalBalance - initialBalance
);
Expand Down
12 changes: 3 additions & 9 deletions src/Libraries/LibAsset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,17 @@ import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { InvalidReceiver, NullAddrIsNotAValidSpender, InvalidAmount, NullAddrIsNotAnERC20Token } from "../Errors/GenericErrors.sol";

/// @title LibAsset
/// @custom:version 2.1.0
/// @custom:version 2.1.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
library LibAsset {
using SafeTransferLib for address;
using SafeTransferLib for address payable;

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

address internal constant NATIVE_ASSETID = NULL_ADDRESS;
address internal constant NULL_ADDRESS = address(0);

/// @dev EIP-7702 delegation designator prefix for Account Abstraction
bytes3 internal constant DELEGATION_DESIGNATOR = 0xef0100;
Expand Down Expand Up @@ -191,7 +185,7 @@ library LibAsset {
/// @param assetId The asset identifier to evaluate
/// @return Boolean indicating if the asset is the native asset
function isNativeAsset(address assetId) internal pure returns (bool) {
return assetId == NATIVE_ASSETID;
return assetId == NULL_ADDRESS;
}

/// @notice Checks if the given address is a contract
Expand Down
4 changes: 2 additions & 2 deletions src/Periphery/ReceiverChainflip.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { InvalidConfig } from "../Errors/GenericErrors.sol";
/// @title ReceiverChainflip
/// @author LI.FI (https://li.fi)
/// @notice Receiver contract for Chainflip cross-chain swaps and message passing
/// @custom:version 1.0.0
/// @custom:version 1.0.1
contract ReceiverChainflip is ILiFi, WithdrawablePeriphery {
using SafeTransferLib for address;
using SafeTransferLib for address payable;
Expand Down Expand Up @@ -114,7 +114,7 @@ contract ReceiverChainflip is ILiFi, WithdrawablePeriphery {
) private {
// Group address conversion and store in memory to avoid multiple storage reads
address actualAssetId = assetId == CHAINFLIP_NATIVE_ADDRESS
? LibAsset.NATIVE_ASSETID
? LibAsset.NULL_ADDRESS
: assetId;
bool isNative = LibAsset.isNativeAsset(actualAssetId);

Expand Down
Loading
Loading