Source Code
Overview
ETH Balance
0 ETH
ETH Value
$0.00| Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 5666210 | 251 days ago | Contract Creation | 0 ETH |
Cross-Chain Transactions
Loading...
Loading
Contract Source Code Verified (Exact Match)
Contract Name:
CalldataVerificationFacet
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 1000000 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { LibSwap } from "../Libraries/LibSwap.sol";
import { AcrossFacetV3 } from "./AcrossFacetV3.sol";
import { StargateFacetV2 } from "./StargateFacetV2.sol";
import { CelerIMFacetBase, CelerIM } from "lifi/Helpers/CelerIMFacetBase.sol";
import { LibBytes } from "../Libraries/LibBytes.sol";
import { GenericSwapFacetV3 } from "lifi/Facets/GenericSwapFacetV3.sol";
import { InvalidCallData } from "../Errors/GenericErrors.sol";
/// @title CalldataVerificationFacet
/// @author LI.FI (https://li.fi)
/// @notice Provides functionality for verifying calldata
/// @custom:version 1.3.0
contract CalldataVerificationFacet {
using LibBytes for bytes;
/// @notice Extracts the bridge data from the calldata
/// @param data The calldata to extract the bridge data from
/// @return bridgeData The bridge data extracted from the calldata
function extractBridgeData(
bytes calldata data
) external pure returns (ILiFi.BridgeData memory bridgeData) {
bridgeData = _extractBridgeData(data);
}
/// @notice Extracts the swap data from the calldata
/// @param data The calldata to extract the swap data from
/// @return swapData The swap data extracted from the calldata
function extractSwapData(
bytes calldata data
) external pure returns (LibSwap.SwapData[] memory swapData) {
swapData = _extractSwapData(data);
}
/// @notice Extracts the bridge data and swap data from the calldata
/// @param data The calldata to extract the bridge data and swap data from
/// @return bridgeData The bridge data extracted from the calldata
/// @return swapData The swap data extracted from the calldata
function extractData(
bytes calldata data
)
external
pure
returns (
ILiFi.BridgeData memory bridgeData,
LibSwap.SwapData[] memory swapData
)
{
bridgeData = _extractBridgeData(data);
if (bridgeData.hasSourceSwaps) {
swapData = _extractSwapData(data);
}
}
/// @notice Extracts the main parameters from the calldata
/// @param data The calldata to extract the main parameters from
/// @return bridge The bridge extracted from the calldata
/// @return sendingAssetId The sending asset id extracted from the calldata
/// @return receiver The receiver extracted from the calldata
/// @return amount The min amountfrom the calldata
/// @return destinationChainId The destination chain id extracted from the calldata
/// @return hasSourceSwaps Whether the calldata has source swaps
/// @return hasDestinationCall Whether the calldata has a destination call
function extractMainParameters(
bytes calldata data
)
external
pure
returns (
string memory bridge,
address sendingAssetId,
address receiver,
uint256 amount,
uint256 destinationChainId,
bool hasSourceSwaps,
bool hasDestinationCall
)
{
ILiFi.BridgeData memory bridgeData = _extractBridgeData(data);
if (bridgeData.hasSourceSwaps) {
LibSwap.SwapData[] memory swapData = _extractSwapData(data);
sendingAssetId = swapData[0].sendingAssetId;
amount = swapData[0].fromAmount;
} else {
sendingAssetId = bridgeData.sendingAssetId;
amount = bridgeData.minAmount;
}
return (
bridgeData.bridge,
sendingAssetId,
bridgeData.receiver,
amount,
bridgeData.destinationChainId,
bridgeData.hasSourceSwaps,
bridgeData.hasDestinationCall
);
}
/// @notice Extracts the non-EVM address from the calldata
/// @param data The calldata to extract the non-EVM address from
/// @return nonEVMAddress The non-EVM address extracted from the calldata
function extractNonEVMAddress(
bytes calldata data
) external pure returns (bytes32 nonEVMAddress) {
bytes memory callData = data;
// Non-EVM address is always the first parameter of bridge specific data
if (_extractBridgeData(data).hasSourceSwaps) {
assembly {
let offset := mload(add(callData, 0x64)) // Get the offset of the bridge specific data
nonEVMAddress := mload(add(callData, add(offset, 0x24))) // Get the non-EVM address
}
} else {
assembly {
let offset := mload(add(callData, 0x44)) // Get the offset of the bridge specific data
nonEVMAddress := mload(add(callData, add(offset, 0x24))) // Get the non-EVM address
}
}
}
/// @notice Extracts the generic swap parameters from the calldata
/// @param data The calldata to extract the generic swap parameters from
/// @return sendingAssetId The sending asset id extracted from the calldata
/// @return amount The amount extracted from the calldata
/// @return receiver The receiver extracted from the calldata
/// @return receivingAssetId The receiving asset id extracted from the calldata
/// @return receivingAmount The receiving amount extracted from the calldata
function extractGenericSwapParameters(
bytes calldata data
)
public
pure
returns (
address sendingAssetId,
uint256 amount,
address receiver,
address receivingAssetId,
uint256 receivingAmount
)
{
// valid callData for a genericSwap call should have at least 484 bytes:
// Function selector: 4 bytes
// _transactionId: 32 bytes
// _integrator: 64 bytes
// _referrer: 64 bytes
// _receiver: 32 bytes
// _minAmountOut: 32 bytes
// _swapData: 256 bytes
if (data.length <= 484) {
revert InvalidCallData();
}
LibSwap.SwapData[] memory swapData;
bytes4 functionSelector = bytes4(data[:4]);
if (
functionSelector ==
GenericSwapFacetV3.swapTokensSingleV3ERC20ToERC20.selector ||
functionSelector ==
GenericSwapFacetV3.swapTokensSingleV3ERC20ToNative.selector ||
functionSelector ==
GenericSwapFacetV3.swapTokensSingleV3NativeToERC20.selector
) {
// single swap
swapData = new LibSwap.SwapData[](1);
// extract parameters from calldata
(, , , receiver, receivingAmount, swapData[0]) = abi.decode(
data[4:],
(bytes32, string, string, address, uint256, LibSwap.SwapData)
);
} else {
// multi swap or GenericSwap V1 call
(, , , receiver, receivingAmount, swapData) = abi.decode(
data[4:],
(bytes32, string, string, address, uint256, LibSwap.SwapData[])
);
}
// extract missing return parameters from swapData
sendingAssetId = swapData[0].sendingAssetId;
amount = swapData[0].fromAmount;
receivingAssetId = swapData[swapData.length - 1].receivingAssetId;
}
/// @notice Validates the calldata
/// @param data The calldata to validate
/// @param bridge The bridge to validate or empty string to ignore
/// @param sendingAssetId The sending asset id to validate
/// or 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF to ignore
/// @param receiver The receiver to validate
/// or 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF to ignore
/// @param amount The amount to validate or type(uint256).max to ignore
/// @param destinationChainId The destination chain id to validate
/// or type(uint256).max to ignore
/// @param hasSourceSwaps Whether the calldata has source swaps
/// @param hasDestinationCall Whether the calldata has a destination call
/// @return isValid Returns true if the calldata is valid
function validateCalldata(
bytes calldata data,
string calldata bridge,
address sendingAssetId,
address receiver,
uint256 amount,
uint256 destinationChainId,
bool hasSourceSwaps,
bool hasDestinationCall
) external pure returns (bool isValid) {
ILiFi.BridgeData memory bridgeData = _extractBridgeData(data);
bytes32 bridgeNameHash = keccak256(abi.encodePacked(bridge));
return
// Check bridge
(bridgeNameHash == keccak256(abi.encodePacked("")) ||
keccak256(abi.encodePacked(bridgeData.bridge)) ==
bridgeNameHash) &&
// Check sendingAssetId
(sendingAssetId == 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF ||
bridgeData.sendingAssetId == sendingAssetId) &&
// Check receiver
(receiver == 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF ||
bridgeData.receiver == receiver) &&
// Check amount
(amount == type(uint256).max || bridgeData.minAmount == amount) &&
// Check destinationChainId
(destinationChainId == type(uint256).max ||
bridgeData.destinationChainId == destinationChainId) &&
// Check hasSourceSwaps
bridgeData.hasSourceSwaps == hasSourceSwaps &&
// Check hasDestinationCall
bridgeData.hasDestinationCall == hasDestinationCall;
}
/// @notice Validates the destination calldata
/// @param data The calldata to validate
/// @param callTo The callTo address to validate
/// @param dstCalldata The destination calldata to validate
/// @return isValid Returns true if the calldata is valid
function validateDestinationCalldata(
bytes calldata data,
bytes calldata callTo,
bytes calldata dstCalldata
) external pure returns (bool isValid) {
bytes4 selector = bytes4(data[:4]);
// ---------------------------------------
// Case: StargateV2
if (
selector == StargateFacetV2.startBridgeTokensViaStargate.selector
) {
(, StargateFacetV2.StargateData memory stargateDataV2) = abi
.decode(
data[4:],
(ILiFi.BridgeData, StargateFacetV2.StargateData)
);
return
keccak256(dstCalldata) ==
keccak256(stargateDataV2.sendParams.composeMsg) &&
_compareBytesToBytes32CallTo(
callTo,
stargateDataV2.sendParams.to
);
}
if (
selector ==
StargateFacetV2.swapAndStartBridgeTokensViaStargate.selector
) {
(, , StargateFacetV2.StargateData memory stargateDataV2) = abi
.decode(
data[4:],
(
ILiFi.BridgeData,
LibSwap.SwapData[],
StargateFacetV2.StargateData
)
);
return
keccak256(dstCalldata) ==
keccak256(stargateDataV2.sendParams.composeMsg) &&
_compareBytesToBytes32CallTo(
callTo,
stargateDataV2.sendParams.to
);
}
// ---------------------------------------
// Case: Celer
if (
selector == CelerIMFacetBase.startBridgeTokensViaCelerIM.selector
) {
(, CelerIM.CelerIMData memory celerIMData) = abi.decode(
data[4:],
(ILiFi.BridgeData, CelerIM.CelerIMData)
);
return
keccak256(dstCalldata) == keccak256(celerIMData.callData) &&
keccak256(callTo) == keccak256(celerIMData.callTo);
}
if (
selector ==
CelerIMFacetBase.swapAndStartBridgeTokensViaCelerIM.selector
) {
(, , CelerIM.CelerIMData memory celerIMData) = abi.decode(
data[4:],
(ILiFi.BridgeData, LibSwap.SwapData[], CelerIM.CelerIMData)
);
return
keccak256(dstCalldata) == keccak256(celerIMData.callData) &&
keccak256(callTo) == keccak256((celerIMData.callTo));
}
// Case: AcrossV3
if (selector == AcrossFacetV3.startBridgeTokensViaAcrossV3.selector) {
(, AcrossFacetV3.AcrossV3Data memory acrossV3Data) = abi.decode(
data[4:],
(ILiFi.BridgeData, AcrossFacetV3.AcrossV3Data)
);
return
keccak256(dstCalldata) == keccak256(acrossV3Data.message) &&
keccak256(callTo) ==
keccak256(abi.encode(acrossV3Data.receiverAddress));
}
if (
selector ==
AcrossFacetV3.swapAndStartBridgeTokensViaAcrossV3.selector
) {
(, , AcrossFacetV3.AcrossV3Data memory acrossV3Data) = abi.decode(
data[4:],
(
ILiFi.BridgeData,
LibSwap.SwapData[],
AcrossFacetV3.AcrossV3Data
)
);
return
keccak256(dstCalldata) == keccak256(acrossV3Data.message) &&
keccak256(callTo) ==
keccak256(abi.encode(acrossV3Data.receiverAddress));
}
// ---------------------------------------
// Case: AcrossV3
if (selector == AcrossFacetV3.startBridgeTokensViaAcrossV3.selector) {
(, AcrossFacetV3.AcrossV3Data memory acrossV3Data) = abi.decode(
data[4:],
(ILiFi.BridgeData, AcrossFacetV3.AcrossV3Data)
);
return
keccak256(dstCalldata) == keccak256(acrossV3Data.message) &&
keccak256(callTo) ==
keccak256(abi.encode(acrossV3Data.receiverAddress));
}
if (
selector ==
AcrossFacetV3.swapAndStartBridgeTokensViaAcrossV3.selector
) {
(, , AcrossFacetV3.AcrossV3Data memory acrossV3Data) = abi.decode(
data[4:],
(
ILiFi.BridgeData,
LibSwap.SwapData[],
AcrossFacetV3.AcrossV3Data
)
);
return
keccak256(dstCalldata) == keccak256(acrossV3Data.message) &&
keccak256(callTo) ==
keccak256(abi.encode(acrossV3Data.receiverAddress));
}
// All other cases
return false;
}
/// Internal Methods ///
/// @notice Extracts the bridge data from the calldata
/// @param data The calldata to extract the bridge data from
/// @return bridgeData The bridge data extracted from the calldata
function _extractBridgeData(
bytes calldata data
) internal pure returns (ILiFi.BridgeData memory bridgeData) {
bridgeData = abi.decode(data[4:], (ILiFi.BridgeData));
}
/// @notice Extracts the swap data from the calldata
/// @param data The calldata to extract the swap data from
/// @return swapData The swap data extracted from the calldata
function _extractSwapData(
bytes calldata data
) internal pure returns (LibSwap.SwapData[] memory swapData) {
(, swapData) = abi.decode(
data[4:],
(ILiFi.BridgeData, LibSwap.SwapData[])
);
}
function _compareBytesToBytes32CallTo(
bytes memory callTo,
bytes32 callToBytes32
) private pure returns (bool) {
// solhint-disable-next-line gas-custom-errors
require(
callTo.length >= 20,
"Invalid callTo length; expected at least 20 bytes"
);
// Convert bytes to address type from callTo
address callToAddress;
assembly {
callToAddress := mload(add(callTo, 32))
}
// Convert callToBytes32 to address type and compare them
address callToAddressFromBytes32 = address(
uint160(uint256(callToBytes32))
);
return callToAddress == callToAddressFromBytes32;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/// @title LIFI Interface
/// @author LI.FI (https://li.fi)
/// @custom:version 1.0.0
interface ILiFi {
/// Structs ///
struct BridgeData {
bytes32 transactionId;
string bridge;
string integrator;
address referrer;
address sendingAssetId;
address receiver;
uint256 minAmount;
uint256 destinationChainId;
bool hasSourceSwaps;
bool hasDestinationCall;
}
/// Events ///
event LiFiTransferStarted(ILiFi.BridgeData bridgeData);
event LiFiTransferCompleted(
bytes32 indexed transactionId,
address receivingAssetId,
address receiver,
uint256 amount,
uint256 timestamp
);
event LiFiTransferRecovered(
bytes32 indexed transactionId,
address receivingAssetId,
address receiver,
uint256 amount,
uint256 timestamp
);
event LiFiGenericSwapCompleted(
bytes32 indexed transactionId,
string integrator,
string referrer,
address receiver,
address fromAssetId,
address toAssetId,
uint256 fromAmount,
uint256 toAmount
);
// Deprecated but kept here to include in ABI to parse historic events
event LiFiSwappedGeneric(
bytes32 indexed transactionId,
string integrator,
string referrer,
address fromAssetId,
address toAssetId,
uint256 fromAmount,
uint256 toAmount
);
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import { LibAsset } from "./LibAsset.sol";
import { LibUtil } from "./LibUtil.sol";
import { InvalidContract, NoSwapFromZeroBalance, InsufficientBalance } from "../Errors/GenericErrors.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
library LibSwap {
struct SwapData {
address callTo;
address approveTo;
address sendingAssetId;
address receivingAssetId;
uint256 fromAmount;
bytes callData;
bool requiresDeposit;
}
event AssetSwapped(
bytes32 transactionId,
address dex,
address fromAssetId,
address toAssetId,
uint256 fromAmount,
uint256 toAmount,
uint256 timestamp
);
function swap(bytes32 transactionId, SwapData calldata _swap) internal {
if (!LibAsset.isContract(_swap.callTo)) revert InvalidContract();
uint256 fromAmount = _swap.fromAmount;
if (fromAmount == 0) revert NoSwapFromZeroBalance();
uint256 nativeValue = LibAsset.isNativeAsset(_swap.sendingAssetId)
? _swap.fromAmount
: 0;
uint256 initialSendingAssetBalance = LibAsset.getOwnBalance(
_swap.sendingAssetId
);
uint256 initialReceivingAssetBalance = LibAsset.getOwnBalance(
_swap.receivingAssetId
);
if (nativeValue == 0) {
LibAsset.maxApproveERC20(
IERC20(_swap.sendingAssetId),
_swap.approveTo,
_swap.fromAmount
);
}
if (initialSendingAssetBalance < _swap.fromAmount) {
revert InsufficientBalance(
_swap.fromAmount,
initialSendingAssetBalance
);
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory res) = _swap.callTo.call{
value: nativeValue
}(_swap.callData);
if (!success) {
LibUtil.revertWith(res);
}
uint256 newBalance = LibAsset.getOwnBalance(_swap.receivingAssetId);
emit AssetSwapped(
transactionId,
_swap.callTo,
_swap.sendingAssetId,
_swap.receivingAssetId,
_swap.fromAmount,
newBalance > initialReceivingAssetBalance
? newBalance - initialReceivingAssetBalance
: newBalance,
block.timestamp
);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { IAcrossSpokePool } from "../Interfaces/IAcrossSpokePool.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";
import { InformationMismatch } from "../Errors/GenericErrors.sol";
/// @title AcrossFacetV3
/// @author LI.FI (https://li.fi)
/// @notice Provides functionality for bridging through Across Protocol
/// @custom:version 1.1.0
contract AcrossFacetV3 is ILiFi, ReentrancyGuard, SwapperV2, Validatable {
/// Storage ///
/// @notice The contract address of the spoke pool on the source chain.
IAcrossSpokePool public immutable spokePool;
/// @notice The WETH address on the current chain.
address public immutable wrappedNative;
/// Types ///
/// @param receiverAddress The address that will receive the token on dst chain (our Receiver contract or the user-defined receiver address)
/// @param refundAddress The address that will be used for potential bridge refunds
/// @param receivingAssetId The address of the token to be received at destination chain
/// @param outputAmount The amount to be received at destination chain (after fees)
/// @param outputAmountPercent The percentage of the output amount with 18 decimal precision (0.7550e18 = 75.50%, 0.99e18 = 99.00%)
/// @param exclusiveRelayer This is the exclusive relayer who can fill the deposit before the exclusivity deadline.
/// @param quoteTimestamp The timestamp of the Across quote that was used for this transaction
/// @param fillDeadline The destination chain timestamp until which the order can be filled
/// @param exclusivityDeadline The timestamp on the destination chain after which any relayer can fill the deposit
/// @param message Arbitrary data that can be used to pass additional information to the recipient along with the tokens
struct AcrossV3Data {
address receiverAddress;
address refundAddress;
address receivingAssetId;
uint256 outputAmount;
uint64 outputAmountPercent;
address exclusiveRelayer;
uint32 quoteTimestamp;
uint32 fillDeadline;
uint32 exclusivityDeadline;
bytes message;
}
/// Constructor ///
/// @notice Initialize the contract.
/// @param _spokePool The contract address of the spoke pool on the source chain.
/// @param _wrappedNative The address of the wrapped native token on the source chain.
constructor(IAcrossSpokePool _spokePool, address _wrappedNative) {
spokePool = _spokePool;
wrappedNative = _wrappedNative;
}
/// External Methods ///
/// @notice Bridges tokens via Across
/// @param _bridgeData the core information needed for bridging
/// @param _acrossData data specific to Across
function startBridgeTokensViaAcrossV3(
ILiFi.BridgeData memory _bridgeData,
AcrossV3Data calldata _acrossData
)
external
payable
nonReentrant
refundExcessNative(payable(msg.sender))
validateBridgeData(_bridgeData)
doesNotContainSourceSwaps(_bridgeData)
{
LibAsset.depositAsset(
_bridgeData.sendingAssetId,
_bridgeData.minAmount
);
_startBridge(_bridgeData, _acrossData);
}
/// @notice Performs a swap before bridging via Across
/// @param _bridgeData the core information needed for bridging
/// @param _swapData an array of swap related data for performing swaps before bridging
/// @param _acrossData data specific to Across
function swapAndStartBridgeTokensViaAcrossV3(
ILiFi.BridgeData memory _bridgeData,
LibSwap.SwapData[] calldata _swapData,
AcrossV3Data calldata _acrossData
)
external
payable
nonReentrant
refundExcessNative(payable(msg.sender))
containsSourceSwaps(_bridgeData)
validateBridgeData(_bridgeData)
{
_bridgeData.minAmount = _depositAndSwap(
_bridgeData.transactionId,
_bridgeData.minAmount,
_swapData,
payable(msg.sender)
);
// Create modified across data with calculated output amount
AcrossV3Data memory modifiedAcrossData = _acrossData;
modifiedAcrossData.outputAmount =
(_bridgeData.minAmount * _acrossData.outputAmountPercent) /
1e18;
_startBridge(_bridgeData, modifiedAcrossData);
}
/// Internal Methods ///
/// @dev Contains the business logic for the bridge via Across
/// @param _bridgeData the core information needed for bridging
/// @param _acrossData data specific to Across
function _startBridge(
ILiFi.BridgeData memory _bridgeData,
AcrossV3Data memory _acrossData
) internal {
// validate destination call flag
if (_acrossData.message.length > 0 != _bridgeData.hasDestinationCall)
revert InformationMismatch();
// ensure that receiver addresses match in case of no destination call
if (
!_bridgeData.hasDestinationCall &&
(_bridgeData.receiver != _acrossData.receiverAddress)
) revert InformationMismatch();
// check if sendingAsset is native or ERC20
if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) {
// NATIVE
spokePool.depositV3{ value: _bridgeData.minAmount }(
_acrossData.refundAddress, // depositor (also acts as refund address in case release tx cannot be executed)
_acrossData.receiverAddress, // recipient (on dst)
wrappedNative, // inputToken
_acrossData.receivingAssetId, // outputToken
_bridgeData.minAmount, // inputAmount
_acrossData.outputAmount, // outputAmount
_bridgeData.destinationChainId,
_acrossData.exclusiveRelayer,
_acrossData.quoteTimestamp,
_acrossData.fillDeadline,
_acrossData.exclusivityDeadline,
_acrossData.message
);
} else {
// ERC20
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
address(spokePool),
_bridgeData.minAmount
);
spokePool.depositV3(
_acrossData.refundAddress, // depositor (also acts as refund address in case release tx cannot be executed)
_acrossData.receiverAddress, // recipient (on dst)
_bridgeData.sendingAssetId, // inputToken
_acrossData.receivingAssetId, // outputToken
_bridgeData.minAmount, // inputAmount
_acrossData.outputAmount, // outputAmount
_bridgeData.destinationChainId,
_acrossData.exclusiveRelayer,
_acrossData.quoteTimestamp,
_acrossData.fillDeadline,
_acrossData.exclusivityDeadline,
_acrossData.message
);
}
emit LiFiTransferStarted(_bridgeData);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { IStargate, ITokenMessaging } from "../Interfaces/IStargate.sol";
import { LibAsset } from "../Libraries/LibAsset.sol";
import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol";
import { InformationMismatch } from "../Errors/GenericErrors.sol";
import { SwapperV2, LibSwap } from "../Helpers/SwapperV2.sol";
import { Validatable } from "../Helpers/Validatable.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { ERC20 } from "solady/tokens/ERC20.sol";
/// @title StargateFacetV2
/// @author Li.Finance (https://li.finance)
/// @notice Provides functionality for bridging through Stargate (V2)
/// @custom:version 1.0.1
contract StargateFacetV2 is ILiFi, ReentrancyGuard, SwapperV2, Validatable {
using SafeTransferLib for address;
/// STORAGE ///
ITokenMessaging public immutable tokenMessaging;
/// @param assetId The Stargate-specific assetId for the token that should be bridged
/// @param sendParams Various parameters that describe what needs to be bridged, how to bridge it and what to do with it on dst
/// @param fee Information about the (native) LayerZero fee that needs to be sent with the tx
/// @param refundAddress the address that is used for potential refunds
struct StargateData {
uint16 assetId;
IStargate.SendParam sendParams;
IStargate.MessagingFee fee;
address payable refundAddress;
}
/// ERRORS ///
error InvalidAssetId(uint16 invalidAssetId);
/// CONSTRUCTOR ///
/// @param _tokenMessaging The address of the tokenMessaging contract (used to obtain pool addresses)
constructor(address _tokenMessaging) {
tokenMessaging = ITokenMessaging(_tokenMessaging);
}
/// EXTERNAL METHODS ///
/// @notice Bridges tokens via Stargate Bridge
/// @param _bridgeData Data used purely for tracking and analytics
/// @param _stargateData Data specific to Stargate Bridge
function startBridgeTokensViaStargate(
ILiFi.BridgeData calldata _bridgeData,
StargateData calldata _stargateData
)
external
payable
nonReentrant
refundExcessNative(payable(msg.sender))
doesNotContainSourceSwaps(_bridgeData)
validateBridgeData(_bridgeData)
{
LibAsset.depositAsset(
_bridgeData.sendingAssetId,
_bridgeData.minAmount
);
_startBridge(_bridgeData, _stargateData);
}
/// @notice Performs a swap before bridging via Stargate Bridge
/// @param _bridgeData Data used purely for tracking and analytics
/// @param _swapData An array of swap related data for performing swaps before bridging
/// @param _stargateData Data specific to Stargate Bridge
function swapAndStartBridgeTokensViaStargate(
ILiFi.BridgeData memory _bridgeData,
LibSwap.SwapData[] calldata _swapData,
StargateData calldata _stargateData
)
external
payable
nonReentrant
refundExcessNative(payable(msg.sender))
containsSourceSwaps(_bridgeData)
validateBridgeData(_bridgeData)
{
_bridgeData.minAmount = _depositAndSwap(
_bridgeData.transactionId,
_bridgeData.minAmount,
_swapData,
payable(msg.sender),
_stargateData.fee.nativeFee
);
_startBridge(_bridgeData, _stargateData);
}
/// PRIVATE METHODS ///
/// @dev Contains the business logic for the bridging via StargateV2
/// @param _bridgeData Data used purely for tracking and analytics
/// @param _stargateData Data specific to Stargate Bridge
function _startBridge(
ILiFi.BridgeData memory _bridgeData,
StargateData memory _stargateData
) private {
// validate destination call flag
if (
(_stargateData.sendParams.composeMsg.length > 0 !=
_bridgeData.hasDestinationCall) ||
(_bridgeData.hasDestinationCall &&
_stargateData.sendParams.oftCmd.length != 0)
) revert InformationMismatch();
// ensure that receiver addresses match in case of no destination call
if (
!_bridgeData.hasDestinationCall &&
(_bridgeData.receiver !=
address(uint160(uint256(_stargateData.sendParams.to))))
) revert InformationMismatch();
// get the router-/pool address through the TokenMessaging contract
address routerAddress = tokenMessaging.stargateImpls(
_stargateData.assetId
);
if (routerAddress == address(0))
revert InvalidAssetId(_stargateData.assetId);
// check if NATIVE or ERC20
uint256 msgValue = _stargateData.fee.nativeFee;
if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) {
// NATIVE
// add minAmount to msgValue
msgValue += _bridgeData.minAmount;
} else {
// ERC20
// check current allowance to router
address sendingAssetId = _bridgeData.sendingAssetId;
uint256 currentAllowance = ERC20(sendingAssetId).allowance(
address(this),
routerAddress
);
// check if allowance is sufficient
if (currentAllowance < _bridgeData.minAmount) {
// check if allowance is 0
if (currentAllowance != 0) {
sendingAssetId.safeApprove(routerAddress, 0);
}
// set allowance to uintMax
sendingAssetId.safeApprove(routerAddress, type(uint256).max);
}
}
// update amount in sendParams
_stargateData.sendParams.amountLD = _bridgeData.minAmount;
// execute call to Stargate router
IStargate(routerAddress).sendToken{ value: msgValue }(
_stargateData.sendParams,
_stargateData.fee,
_stargateData.refundAddress
);
emit LiFiTransferStarted(_bridgeData);
}
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { LibAsset, IERC20 } from "../Libraries/LibAsset.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { ReentrancyGuard } from "../Helpers/ReentrancyGuard.sol";
import { SwapperV2, LibSwap } from "../Helpers/SwapperV2.sol";
import { InvalidAmount, InformationMismatch } from "../Errors/GenericErrors.sol";
import { Validatable } from "../Helpers/Validatable.sol";
import { MessageSenderLib, MsgDataTypes, IMessageBus } from "celer-network/contracts/message/libraries/MessageSenderLib.sol";
import { RelayerCelerIM } from "lifi/Periphery/RelayerCelerIM.sol";
interface CelerToken {
function canonical() external returns (address);
}
interface CelerIM {
/// @param maxSlippage The max slippage accepted, given as percentage in point (pip).
/// @param nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
/// @param callTo The address of the contract to be called at destination.
/// @param callData The encoded calldata with below data
/// bytes32 transactionId,
/// LibSwap.SwapData[] memory swapData,
/// address receiver,
/// address refundAddress
/// @param messageBusFee The fee to be paid to CBridge message bus for relaying the message
/// @param bridgeType Defines the bridge operation type (must be one of the values of CBridge library MsgDataTypes.BridgeSendType)
struct CelerIMData {
uint32 maxSlippage;
uint64 nonce;
bytes callTo;
bytes callData;
uint256 messageBusFee;
MsgDataTypes.BridgeSendType bridgeType;
}
}
/// @title CelerIM Facet Base
/// @author LI.FI (https://li.fi)
/// @notice Provides functionality for bridging tokens and data through CBridge
/// @notice Used to differentiate between contract instances for mutable and immutable diamond as these cannot be shared
/// @custom:version 2.0.0
abstract contract CelerIMFacetBase is
ILiFi,
ReentrancyGuard,
SwapperV2,
Validatable
{
/// Storage ///
/// @dev The contract address of the cBridge Message Bus
IMessageBus private immutable cBridgeMessageBus;
/// @dev The contract address of the RelayerCelerIM
RelayerCelerIM public immutable relayer;
/// @dev The contract address of the Celer Flow USDC
address private immutable cfUSDC;
/// Constructor ///
/// @notice Initialize the contract.
/// @param _messageBus The contract address of the cBridge Message Bus
/// @param _relayerOwner The address that will become the owner of the RelayerCelerIM contract
/// @param _diamondAddress The address of the diamond contract that will be connected with the RelayerCelerIM
/// @param _cfUSDC The contract address of the Celer Flow USDC
constructor(
IMessageBus _messageBus,
address _relayerOwner,
address _diamondAddress,
address _cfUSDC
) {
// deploy RelayerCelerIM
relayer = new RelayerCelerIM(
address(_messageBus),
_relayerOwner,
_diamondAddress
);
// store arguments in variables
cBridgeMessageBus = _messageBus;
cfUSDC = _cfUSDC;
}
/// External Methods ///
/// @notice Bridges tokens via CBridge
/// @param _bridgeData The core information needed for bridging
/// @param _celerIMData Data specific to CelerIM
function startBridgeTokensViaCelerIM(
ILiFi.BridgeData memory _bridgeData,
CelerIM.CelerIMData calldata _celerIMData
)
external
payable
nonReentrant
refundExcessNative(payable(msg.sender))
doesNotContainSourceSwaps(_bridgeData)
validateBridgeData(_bridgeData)
{
validateDestinationCallFlag(_bridgeData, _celerIMData);
if (!LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) {
// Transfer ERC20 tokens directly to relayer
IERC20 asset = _getRightAsset(_bridgeData.sendingAssetId);
// Deposit ERC20 token
uint256 prevBalance = asset.balanceOf(address(relayer));
SafeERC20.safeTransferFrom(
asset,
msg.sender,
address(relayer),
_bridgeData.minAmount
);
if (
asset.balanceOf(address(relayer)) - prevBalance !=
_bridgeData.minAmount
) {
revert InvalidAmount();
}
}
_startBridge(_bridgeData, _celerIMData);
}
/// @notice Performs a swap before bridging via CBridge
/// @param _bridgeData The core information needed for bridging
/// @param _swapData An array of swap related data for performing swaps before bridging
/// @param _celerIMData Data specific to CelerIM
function swapAndStartBridgeTokensViaCelerIM(
ILiFi.BridgeData memory _bridgeData,
LibSwap.SwapData[] calldata _swapData,
CelerIM.CelerIMData calldata _celerIMData
)
external
payable
nonReentrant
refundExcessNative(payable(msg.sender))
containsSourceSwaps(_bridgeData)
validateBridgeData(_bridgeData)
{
validateDestinationCallFlag(_bridgeData, _celerIMData);
_bridgeData.minAmount = _depositAndSwap(
_bridgeData.transactionId,
_bridgeData.minAmount,
_swapData,
payable(msg.sender),
_celerIMData.messageBusFee
);
if (!LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) {
// Transfer ERC20 tokens directly to relayer
IERC20 asset = _getRightAsset(_bridgeData.sendingAssetId);
// Deposit ERC20 token
uint256 prevBalance = asset.balanceOf(address(relayer));
SafeERC20.safeTransfer(
asset,
address(relayer),
_bridgeData.minAmount
);
if (
asset.balanceOf(address(relayer)) - prevBalance !=
_bridgeData.minAmount
) {
revert InvalidAmount();
}
}
_startBridge(_bridgeData, _celerIMData);
}
/// Private Methods ///
/// @dev Contains the business logic for the bridge via CBridge
/// @param _bridgeData The core information needed for bridging
/// @param _celerIMData Data specific to CBridge
function _startBridge(
ILiFi.BridgeData memory _bridgeData,
CelerIM.CelerIMData calldata _celerIMData
) private {
// Assuming messageBusFee is pre-calculated off-chain and available in _celerIMData
// Determine correct native asset amount to be forwarded (if so) and send funds to relayer
uint256 msgValue = LibAsset.isNativeAsset(_bridgeData.sendingAssetId)
? _bridgeData.minAmount
: 0;
// Check if transaction contains a destination call
if (!_bridgeData.hasDestinationCall) {
// Case 'no': Simple bridge transfer - Send to receiver
relayer.sendTokenTransfer{ value: msgValue }(
_bridgeData,
_celerIMData
);
} else {
// Case 'yes': Bridge + Destination call - Send to relayer
// save address of original recipient
address receiver = _bridgeData.receiver;
// Set relayer as a receiver
_bridgeData.receiver = address(relayer);
// send token transfer
(bytes32 transferId, address bridgeAddress) = relayer
.sendTokenTransfer{ value: msgValue }(
_bridgeData,
_celerIMData
);
// Call message bus via relayer incl messageBusFee
relayer.forwardSendMessageWithTransfer{
value: _celerIMData.messageBusFee
}(
_bridgeData.receiver,
uint64(_bridgeData.destinationChainId),
bridgeAddress,
transferId,
_celerIMData.callData
);
// Reset receiver of bridge data for event emission
_bridgeData.receiver = receiver;
}
// emit LiFi event
emit LiFiTransferStarted(_bridgeData);
}
/// @dev Get right asset to transfer to relayer.
/// @param _sendingAssetId The address of asset to bridge.
/// @return _asset The address of asset to transfer to relayer.
function _getRightAsset(
address _sendingAssetId
) private returns (IERC20 _asset) {
if (_sendingAssetId == cfUSDC) {
// special case for cfUSDC token
_asset = IERC20(CelerToken(_sendingAssetId).canonical());
} else {
// any other ERC20 token
_asset = IERC20(_sendingAssetId);
}
}
function validateDestinationCallFlag(
ILiFi.BridgeData memory _bridgeData,
CelerIM.CelerIMData calldata _celerIMData
) private pure {
if (
(_celerIMData.callData.length > 0) !=
_bridgeData.hasDestinationCall
) {
revert InformationMismatch();
}
}
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
library LibBytes {
// solhint-disable no-inline-assembly
// LibBytes specific errors
error SliceOverflow();
error SliceOutOfBounds();
error AddressOutOfBounds();
bytes16 private constant _SYMBOLS = "0123456789abcdef";
// -------------------------
function slice(
bytes memory _bytes,
uint256 _start,
uint256 _length
) internal pure returns (bytes memory) {
if (_length + 31 < _length) revert SliceOverflow();
if (_bytes.length < _start + _length) revert SliceOutOfBounds();
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(
add(tempBytes, lengthmod),
mul(0x20, iszero(lengthmod))
)
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(
add(
add(_bytes, lengthmod),
mul(0x20, iszero(lengthmod))
),
_start
)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function toAddress(
bytes memory _bytes,
uint256 _start
) internal pure returns (address) {
if (_bytes.length < _start + 20) {
revert AddressOutOfBounds();
}
address tempAddress;
assembly {
tempAddress := div(
mload(add(add(_bytes, 0x20), _start)),
0x1000000000000000000000000
)
}
return tempAddress;
}
/// Copied from OpenZeppelin's `Strings.sol` utility library.
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8335676b0e99944eef6a742e16dcd9ff6e68e609/contracts/utils/Strings.sol
function toHexString(
uint256 value,
uint256 length
) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { LibUtil } from "../Libraries/LibUtil.sol";
import { LibSwap } from "../Libraries/LibSwap.sol";
import { LibAllowList } from "../Libraries/LibAllowList.sol";
import { LibAsset } from "../Libraries/LibAsset.sol";
import { ContractCallNotAllowed, CumulativeSlippageTooHigh, NativeAssetTransferFailed } from "../Errors/GenericErrors.sol";
import { ERC20, SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
/// @title GenericSwapFacetV3
/// @author LI.FI (https://li.fi)
/// @notice Provides gas-optimized functionality for fee collection and for swapping through any APPROVED DEX
/// @dev Can only execute calldata for APPROVED function selectors
/// @custom:version 1.0.1
contract GenericSwapFacetV3 is ILiFi {
using SafeTransferLib for ERC20;
/// Storage
address public immutable NATIVE_ADDRESS;
/// Constructor
/// @param _nativeAddress the address of the native token for this network
constructor(address _nativeAddress) {
NATIVE_ADDRESS = _nativeAddress;
}
/// External Methods ///
// SINGLE SWAPS
/// @notice Performs a single swap from an ERC20 token to another ERC20 token
/// @param _transactionId the transaction id associated with the operation
/// @param _integrator the name of the integrator
/// @param _referrer the address of the referrer
/// @param _receiver the address to receive the swapped tokens into (also excess tokens)
/// @param _minAmountOut the minimum amount of the final asset to receive
/// @param _swapData an object containing swap related data to perform swaps before bridging
function swapTokensSingleV3ERC20ToERC20(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData calldata _swapData
) external {
_depositAndSwapERC20Single(_swapData, _receiver);
address receivingAssetId = _swapData.receivingAssetId;
address sendingAssetId = _swapData.sendingAssetId;
// get contract's balance (which will be sent in full to user)
uint256 amountReceived = ERC20(receivingAssetId).balanceOf(
address(this)
);
// ensure that minAmountOut was received
if (amountReceived < _minAmountOut)
revert CumulativeSlippageTooHigh(_minAmountOut, amountReceived);
// transfer funds to receiver
ERC20(receivingAssetId).safeTransfer(_receiver, amountReceived);
// emit events (both required for tracking)
uint256 fromAmount = _swapData.fromAmount;
emit LibSwap.AssetSwapped(
_transactionId,
_swapData.callTo,
sendingAssetId,
receivingAssetId,
fromAmount,
amountReceived,
block.timestamp
);
emit ILiFi.LiFiGenericSwapCompleted(
_transactionId,
_integrator,
_referrer,
_receiver,
sendingAssetId,
receivingAssetId,
fromAmount,
amountReceived
);
}
/// @notice Performs a single swap from an ERC20 token to the network's native token
/// @param _transactionId the transaction id associated with the operation
/// @param _integrator the name of the integrator
/// @param _referrer the address of the referrer
/// @param _receiver the address to receive the swapped tokens into (also excess tokens)
/// @param _minAmountOut the minimum amount of the final asset to receive
/// @param _swapData an object containing swap related data to perform swaps before bridging
function swapTokensSingleV3ERC20ToNative(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData calldata _swapData
) external {
_depositAndSwapERC20Single(_swapData, _receiver);
// get contract's balance (which will be sent in full to user)
uint256 amountReceived = address(this).balance;
// ensure that minAmountOut was received
if (amountReceived < _minAmountOut)
revert CumulativeSlippageTooHigh(_minAmountOut, amountReceived);
// transfer funds to receiver
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = _receiver.call{ value: amountReceived }("");
if (!success) revert NativeAssetTransferFailed();
// emit events (both required for tracking)
address sendingAssetId = _swapData.sendingAssetId;
uint256 fromAmount = _swapData.fromAmount;
emit LibSwap.AssetSwapped(
_transactionId,
_swapData.callTo,
sendingAssetId,
NATIVE_ADDRESS,
fromAmount,
amountReceived,
block.timestamp
);
emit ILiFi.LiFiGenericSwapCompleted(
_transactionId,
_integrator,
_referrer,
_receiver,
sendingAssetId,
NATIVE_ADDRESS,
fromAmount,
amountReceived
);
}
/// @notice Performs a single swap from the network's native token to ERC20 token
/// @param _transactionId the transaction id associated with the operation
/// @param _integrator the name of the integrator
/// @param _referrer the address of the referrer
/// @param _receiver the address to receive the swapped tokens into (also excess tokens)
/// @param _minAmountOut the minimum amount of the final asset to receive
/// @param _swapData an object containing swap related data to perform swaps before bridging
function swapTokensSingleV3NativeToERC20(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData calldata _swapData
) external payable {
address callTo = _swapData.callTo;
// ensure that contract (callTo) and function selector are whitelisted
if (
!(LibAllowList.contractIsAllowed(callTo) &&
LibAllowList.selectorIsAllowed(bytes4(_swapData.callData[:4])))
) revert ContractCallNotAllowed();
// execute swap
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory res) = callTo.call{ value: msg.value }(
_swapData.callData
);
if (!success) {
LibUtil.revertWith(res);
}
_returnPositiveSlippageNative(_receiver);
// get contract's balance (which will be sent in full to user)
address receivingAssetId = _swapData.receivingAssetId;
uint256 amountReceived = ERC20(receivingAssetId).balanceOf(
address(this)
);
// ensure that minAmountOut was received
if (amountReceived < _minAmountOut)
revert CumulativeSlippageTooHigh(_minAmountOut, amountReceived);
// transfer funds to receiver
ERC20(receivingAssetId).safeTransfer(_receiver, amountReceived);
// emit events (both required for tracking)
uint256 fromAmount = _swapData.fromAmount;
emit LibSwap.AssetSwapped(
_transactionId,
callTo,
NATIVE_ADDRESS,
receivingAssetId,
fromAmount,
amountReceived,
block.timestamp
);
emit ILiFi.LiFiGenericSwapCompleted(
_transactionId,
_integrator,
_referrer,
_receiver,
NATIVE_ADDRESS,
receivingAssetId,
fromAmount,
amountReceived
);
}
// MULTIPLE SWAPS
/// @notice Performs multiple swaps in one transaction, starting with ERC20 and ending with native
/// @param _transactionId the transaction id associated with the operation
/// @param _integrator the name of the integrator
/// @param _referrer the address of the referrer
/// @param _receiver the address to receive the swapped tokens into (also excess tokens)
/// @param _minAmountOut the minimum amount of the final asset to receive
/// @param _swapData an object containing swap related data to perform swaps before bridging
function swapTokensMultipleV3ERC20ToNative(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData[] calldata _swapData
) external {
_depositMultipleERC20Tokens(_swapData);
_executeSwaps(_swapData, _transactionId, _receiver);
_transferNativeTokensAndEmitEvent(
_transactionId,
_integrator,
_referrer,
_receiver,
_minAmountOut,
_swapData
);
}
/// @notice Performs multiple swaps in one transaction, starting with ERC20 and ending with ERC20
/// @param _transactionId the transaction id associated with the operation
/// @param _integrator the name of the integrator
/// @param _referrer the address of the referrer
/// @param _receiver the address to receive the swapped tokens into (also excess tokens)
/// @param _minAmountOut the minimum amount of the final asset to receive
/// @param _swapData an object containing swap related data to perform swaps before bridging
function swapTokensMultipleV3ERC20ToERC20(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData[] calldata _swapData
) external {
_depositMultipleERC20Tokens(_swapData);
_executeSwaps(_swapData, _transactionId, _receiver);
_transferERC20TokensAndEmitEvent(
_transactionId,
_integrator,
_referrer,
_receiver,
_minAmountOut,
_swapData
);
}
/// @notice Performs multiple swaps in one transaction, starting with native and ending with ERC20
/// @param _transactionId the transaction id associated with the operation
/// @param _integrator the name of the integrator
/// @param _referrer the address of the referrer
/// @param _receiver the address to receive the swapped tokens into (also excess tokens)
/// @param _minAmountOut the minimum amount of the final asset to receive
/// @param _swapData an object containing swap related data to perform swaps before bridging
function swapTokensMultipleV3NativeToERC20(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData[] calldata _swapData
) external payable {
_executeSwaps(_swapData, _transactionId, _receiver);
_transferERC20TokensAndEmitEvent(
_transactionId,
_integrator,
_referrer,
_receiver,
_minAmountOut,
_swapData
);
}
/// Private helper methods ///
function _depositMultipleERC20Tokens(
LibSwap.SwapData[] calldata _swapData
) private {
// initialize variables before loop to save gas
uint256 numOfSwaps = _swapData.length;
LibSwap.SwapData calldata currentSwap;
// go through all swaps and deposit tokens, where required
for (uint256 i = 0; i < numOfSwaps; ) {
currentSwap = _swapData[i];
if (currentSwap.requiresDeposit) {
// we will not check msg.value as tx will fail anyway if not enough value available
// thus we only deposit ERC20 tokens here
ERC20(currentSwap.sendingAssetId).safeTransferFrom(
msg.sender,
address(this),
currentSwap.fromAmount
);
}
unchecked {
++i;
}
}
}
function _depositAndSwapERC20Single(
LibSwap.SwapData calldata _swapData,
address _receiver
) private {
ERC20 sendingAsset = ERC20(_swapData.sendingAssetId);
uint256 fromAmount = _swapData.fromAmount;
// deposit funds
sendingAsset.safeTransferFrom(msg.sender, address(this), fromAmount);
// ensure that contract (callTo) and function selector are whitelisted
address callTo = _swapData.callTo;
address approveTo = _swapData.approveTo;
bytes calldata callData = _swapData.callData;
if (
!(LibAllowList.contractIsAllowed(callTo) &&
LibAllowList.selectorIsAllowed(bytes4(callData[:4])))
) revert ContractCallNotAllowed();
// ensure that approveTo address is also whitelisted if it differs from callTo
if (approveTo != callTo && !LibAllowList.contractIsAllowed(approveTo))
revert ContractCallNotAllowed();
// check if the current allowance is sufficient
uint256 currentAllowance = sendingAsset.allowance(
address(this),
approveTo
);
// check if existing allowance is sufficient
if (currentAllowance < fromAmount) {
// check if is non-zero, set to 0 if not
if (currentAllowance != 0) sendingAsset.safeApprove(approveTo, 0);
// set allowance to uint max to avoid future approvals
sendingAsset.safeApprove(approveTo, type(uint256).max);
}
// execute swap
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory res) = callTo.call(callData);
if (!success) {
LibUtil.revertWith(res);
}
_returnPositiveSlippageERC20(sendingAsset, _receiver);
}
// @dev: this function will not work with swapData that has multiple swaps with the same sendingAssetId
// as the _returnPositiveSlippage... functionality will refund all remaining tokens after the first swap
// We accept this fact since the use case is not common yet. As an alternative you can always use the
// "swapTokensGeneric" function of the original GenericSwapFacet
function _executeSwaps(
LibSwap.SwapData[] calldata _swapData,
bytes32 _transactionId,
address _receiver
) private {
// initialize variables before loop to save gas
uint256 numOfSwaps = _swapData.length;
ERC20 sendingAsset;
address sendingAssetId;
address receivingAssetId;
LibSwap.SwapData calldata currentSwap;
bool success;
bytes memory returnData;
uint256 currentAllowance;
// go through all swaps
for (uint256 i = 0; i < numOfSwaps; ) {
currentSwap = _swapData[i];
sendingAssetId = currentSwap.sendingAssetId;
sendingAsset = ERC20(currentSwap.sendingAssetId);
receivingAssetId = currentSwap.receivingAssetId;
// check if callTo address is whitelisted
if (
!LibAllowList.contractIsAllowed(currentSwap.callTo) ||
!LibAllowList.selectorIsAllowed(
bytes4(currentSwap.callData[:4])
)
) {
revert ContractCallNotAllowed();
}
// if approveTo address is different to callTo, check if it's whitelisted, too
if (
currentSwap.approveTo != currentSwap.callTo &&
!LibAllowList.contractIsAllowed(currentSwap.approveTo)
) {
revert ContractCallNotAllowed();
}
if (LibAsset.isNativeAsset(sendingAssetId)) {
// Native
// execute the swap
(success, returnData) = currentSwap.callTo.call{
value: currentSwap.fromAmount
}(currentSwap.callData);
if (!success) {
LibUtil.revertWith(returnData);
}
// return any potential leftover sendingAsset tokens
// but only for swaps, not for fee collections (otherwise the whole amount would be returned before the actual swap)
if (sendingAssetId != receivingAssetId)
_returnPositiveSlippageNative(_receiver);
} else {
// ERC20
// check if the current allowance is sufficient
currentAllowance = sendingAsset.allowance(
address(this),
currentSwap.approveTo
);
if (currentAllowance < currentSwap.fromAmount) {
sendingAsset.safeApprove(currentSwap.approveTo, 0);
sendingAsset.safeApprove(
currentSwap.approveTo,
type(uint256).max
);
}
// execute the swap
(success, returnData) = currentSwap.callTo.call(
currentSwap.callData
);
if (!success) {
LibUtil.revertWith(returnData);
}
// return any potential leftover sendingAsset tokens
// but only for swaps, not for fee collections (otherwise the whole amount would be returned before the actual swap)
if (sendingAssetId != receivingAssetId)
_returnPositiveSlippageERC20(sendingAsset, _receiver);
}
// emit AssetSwapped event
// @dev: this event might in some cases emit inaccurate information. e.g. if a token is swapped and this contract already held a balance of the receivingAsset
// then the event will show swapOutputAmount + existingBalance as toAmount. We accept this potential inaccuracy in return for gas savings and may update this
// at a later stage when the described use case becomes more common
emit LibSwap.AssetSwapped(
_transactionId,
currentSwap.callTo,
sendingAssetId,
receivingAssetId,
currentSwap.fromAmount,
LibAsset.isNativeAsset(receivingAssetId)
? address(this).balance
: ERC20(receivingAssetId).balanceOf(address(this)),
block.timestamp
);
unchecked {
++i;
}
}
}
function _transferERC20TokensAndEmitEvent(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData[] calldata _swapData
) private {
// determine the end result of the swap
address finalAssetId = _swapData[_swapData.length - 1]
.receivingAssetId;
uint256 amountReceived = ERC20(finalAssetId).balanceOf(address(this));
// make sure minAmountOut was received
if (amountReceived < _minAmountOut)
revert CumulativeSlippageTooHigh(_minAmountOut, amountReceived);
// transfer to receiver
ERC20(finalAssetId).safeTransfer(_receiver, amountReceived);
// emit event
emit ILiFi.LiFiGenericSwapCompleted(
_transactionId,
_integrator,
_referrer,
_receiver,
_swapData[0].sendingAssetId,
finalAssetId,
_swapData[0].fromAmount,
amountReceived
);
}
function _transferNativeTokensAndEmitEvent(
bytes32 _transactionId,
string calldata _integrator,
string calldata _referrer,
address payable _receiver,
uint256 _minAmountOut,
LibSwap.SwapData[] calldata _swapData
) private {
uint256 amountReceived = address(this).balance;
// make sure minAmountOut was received
if (amountReceived < _minAmountOut)
revert CumulativeSlippageTooHigh(_minAmountOut, amountReceived);
// transfer funds to receiver
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = _receiver.call{ value: amountReceived }("");
if (!success) {
revert NativeAssetTransferFailed();
}
// emit event
emit ILiFi.LiFiGenericSwapCompleted(
_transactionId,
_integrator,
_referrer,
_receiver,
_swapData[0].sendingAssetId,
NATIVE_ADDRESS,
_swapData[0].fromAmount,
amountReceived
);
}
// returns any unused 'sendingAsset' tokens (=> positive slippage) to the receiver address
function _returnPositiveSlippageERC20(
ERC20 sendingAsset,
address receiver
) private {
// if a balance exists in sendingAsset, it must be positive slippage
if (address(sendingAsset) != NATIVE_ADDRESS) {
uint256 sendingAssetBalance = sendingAsset.balanceOf(
address(this)
);
if (sendingAssetBalance > 0) {
sendingAsset.safeTransfer(receiver, sendingAssetBalance);
}
}
}
// returns any unused native tokens (=> positive slippage) to the receiver address
function _returnPositiveSlippageNative(address receiver) private {
// if a native balance exists in sendingAsset, it must be positive slippage
uint256 nativeBalance = address(this).balance;
if (nativeBalance > 0) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = receiver.call{ value: nativeBalance }("");
if (!success) revert NativeAssetTransferFailed();
}
}
}// SPDX-License-Identifier: MIT /// @custom:version 1.0.0 pragma solidity ^0.8.17; error AlreadyInitialized(); error CannotAuthoriseSelf(); error CannotBridgeToSameNetwork(); error ContractCallNotAllowed(); error CumulativeSlippageTooHigh(uint256 minAmount, uint256 receivedAmount); error DiamondIsPaused(); error ExternalCallFailed(); error FunctionDoesNotExist(); error InformationMismatch(); error InsufficientBalance(uint256 required, uint256 balance); error InvalidAmount(); error InvalidCallData(); error InvalidConfig(); error InvalidContract(); error InvalidDestinationChain(); error InvalidFallbackAddress(); error InvalidReceiver(); error InvalidSendingToken(); error NativeAssetNotSupported(); error NativeAssetTransferFailed(); error NoSwapDataProvided(); error NoSwapFromZeroBalance(); error NotAContract(); error NotInitialized(); error NoTransferToNullAddress(); error NullAddrIsNotAnERC20Token(); error NullAddrIsNotAValidSpender(); error OnlyContractOwner(); error RecoveryAddressCannotBeZero(); error ReentrancyError(); error TokenNotSupported(); error UnAuthorized(); error UnsupportedChainId(uint256 chainId); error WithdrawFailed(); error ZeroAmount();
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import { InsufficientBalance, NullAddrIsNotAnERC20Token, NullAddrIsNotAValidSpender, NoTransferToNullAddress, InvalidAmount, NativeAssetTransferFailed } from "../Errors/GenericErrors.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { LibSwap } from "./LibSwap.sol";
/// @title LibAsset
/// @custom:version 1.0.2
/// @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 {
uint256 private constant MAX_UINT = type(uint256).max;
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(0)
/// @notice Gets the balance of the inheriting contract for the given asset
/// @param assetId The asset identifier to get the balance of
/// @return Balance held by contracts using this library
function getOwnBalance(address assetId) internal view returns (uint256) {
return
isNativeAsset(assetId)
? address(this).balance
: IERC20(assetId).balanceOf(address(this));
}
/// @notice Transfers ether from the inheriting contract to a given
/// recipient
/// @param recipient Address to send ether to
/// @param amount Amount to send to given recipient
function transferNativeAsset(
address payable recipient,
uint256 amount
) private {
if (recipient == NULL_ADDRESS) revert NoTransferToNullAddress();
if (amount > address(this).balance)
revert InsufficientBalance(amount, address(this).balance);
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = recipient.call{ value: amount }("");
if (!success) revert NativeAssetTransferFailed();
}
/// @notice If the current allowance is insufficient, the allowance for a given spender
/// is set to MAX_UINT.
/// @param assetId Token address to transfer
/// @param spender Address to give spend approval to
/// @param amount Amount to approve for spending
function maxApproveERC20(
IERC20 assetId,
address spender,
uint256 amount
) internal {
if (isNativeAsset(address(assetId))) {
return;
}
if (spender == NULL_ADDRESS) {
revert NullAddrIsNotAValidSpender();
}
if (assetId.allowance(address(this), spender) < amount) {
SafeERC20.forceApprove(IERC20(assetId), spender, MAX_UINT);
}
}
/// @notice Transfers tokens from the inheriting contract to a given
/// recipient
/// @param assetId Token address to transfer
/// @param recipient Address to send token to
/// @param amount Amount to send to given recipient
function transferERC20(
address assetId,
address recipient,
uint256 amount
) private {
if (isNativeAsset(assetId)) {
revert NullAddrIsNotAnERC20Token();
}
if (recipient == NULL_ADDRESS) {
revert NoTransferToNullAddress();
}
uint256 assetBalance = IERC20(assetId).balanceOf(address(this));
if (amount > assetBalance) {
revert InsufficientBalance(amount, assetBalance);
}
SafeERC20.safeTransfer(IERC20(assetId), recipient, amount);
}
/// @notice Transfers tokens from a sender to a given recipient
/// @param assetId Token address to transfer
/// @param from Address of sender/owner
/// @param to Address of recipient/spender
/// @param amount Amount to transfer from owner to spender
function transferFromERC20(
address assetId,
address from,
address to,
uint256 amount
) internal {
if (isNativeAsset(assetId)) {
revert NullAddrIsNotAnERC20Token();
}
if (to == NULL_ADDRESS) {
revert NoTransferToNullAddress();
}
IERC20 asset = IERC20(assetId);
uint256 prevBalance = asset.balanceOf(to);
SafeERC20.safeTransferFrom(asset, from, to, amount);
if (asset.balanceOf(to) - prevBalance != amount) {
revert InvalidAmount();
}
}
function depositAsset(address assetId, uint256 amount) internal {
if (amount == 0) revert InvalidAmount();
if (isNativeAsset(assetId)) {
if (msg.value < amount) revert InvalidAmount();
} else {
uint256 balance = IERC20(assetId).balanceOf(msg.sender);
if (balance < amount) revert InsufficientBalance(amount, balance);
transferFromERC20(assetId, msg.sender, address(this), amount);
}
}
function depositAssets(LibSwap.SwapData[] calldata swaps) internal {
for (uint256 i = 0; i < swaps.length; ) {
LibSwap.SwapData calldata swap = swaps[i];
if (swap.requiresDeposit) {
depositAsset(swap.sendingAssetId, swap.fromAmount);
}
unchecked {
i++;
}
}
}
/// @notice Determines whether the given assetId is the native asset
/// @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;
}
/// @notice Wrapper function to transfer a given asset (native or erc20) to
/// some recipient. Should handle all non-compliant return value
/// tokens as well by using the SafeERC20 contract by open zeppelin.
/// @param assetId Asset id for transfer (address(0) for native asset,
/// token address for erc20s)
/// @param recipient Address to send asset to
/// @param amount Amount to send to given recipient
function transferAsset(
address assetId,
address payable recipient,
uint256 amount
) internal {
isNativeAsset(assetId)
? transferNativeAsset(recipient, amount)
: transferERC20(assetId, recipient, amount);
}
/// @dev Checks whether the given address is a contract and contains code
function isContract(address _contractAddr) internal view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly {
size := extcodesize(_contractAddr)
}
return size > 0;
}
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import "./LibBytes.sol";
library LibUtil {
using LibBytes for bytes;
function getRevertMsg(
bytes memory _res
) internal pure returns (string memory) {
// If the _res length is less than 68, then the transaction failed silently (without a revert message)
if (_res.length < 68) return "Transaction reverted silently";
bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes
return abi.decode(revertData, (string)); // All that remains is the revert string
}
/// @notice Determines whether the given address is the zero address
/// @param addr The address to verify
/// @return Boolean indicating if the address is the zero address
function isZeroAddress(address addr) internal pure returns (bool) {
return addr == address(0);
}
function revertWith(bytes memory data) internal pure {
assembly {
let dataSize := mload(data) // Load the size of the data
let dataPtr := add(data, 0x20) // Advance data pointer to the next word
revert(dataPtr, dataSize) // Revert with the given data
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/// @title Interface for Across SpokePool
/// @author LI.FI (https://li.fi)
/// @custom:version 1.0.0
interface IAcrossSpokePool {
function deposit(
address recipient, // Recipient address
address originToken, // Address of the token
uint256 amount, // Token amount
uint256 destinationChainId, // ⛓ id
int64 relayerFeePct, // see #Fees Calculation
uint32 quoteTimestamp, // Timestamp for the quote creation
bytes memory message, // Arbitrary data that can be used to pass additional information to the recipient along with the tokens.
uint256 maxCount // Used to protect the depositor from frontrunning to guarantee their quote remains valid.
) external payable;
function depositV3(
address depositor,
address recipient,
address inputToken,
address outputToken,
uint256 inputAmount,
uint256 outputAmount, // <-- replaces fees
uint256 destinationChainId,
address exclusiveRelayer,
uint32 quoteTimestamp,
uint32 fillDeadline,
uint32 exclusivityDeadline,
bytes calldata message
) external payable;
}// SPDX-License-Identifier: UNLICENSED
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
/// @title Reentrancy Guard
/// @author LI.FI (https://li.fi)
/// @notice Abstract contract to provide protection against reentrancy
abstract contract ReentrancyGuard {
/// Storage ///
bytes32 private constant NAMESPACE = keccak256("com.lifi.reentrancyguard");
/// Types ///
struct ReentrancyStorage {
uint256 status;
}
/// Errors ///
error ReentrancyError();
/// Constants ///
uint256 private constant _NOT_ENTERED = 0;
uint256 private constant _ENTERED = 1;
/// Modifiers ///
modifier nonReentrant() {
ReentrancyStorage storage s = reentrancyStorage();
if (s.status == _ENTERED) revert ReentrancyError();
s.status = _ENTERED;
_;
s.status = _NOT_ENTERED;
}
/// Private Methods ///
/// @dev fetch local storage
function reentrancyStorage()
private
pure
returns (ReentrancyStorage storage data)
{
bytes32 position = NAMESPACE;
// solhint-disable-next-line no-inline-assembly
assembly {
data.slot := position
}
}
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { LibSwap } from "../Libraries/LibSwap.sol";
import { LibAsset } from "../Libraries/LibAsset.sol";
import { LibAllowList } from "../Libraries/LibAllowList.sol";
import { ContractCallNotAllowed, NoSwapDataProvided, CumulativeSlippageTooHigh } from "../Errors/GenericErrors.sol";
/// @title Swapper
/// @author LI.FI (https://li.fi)
/// @notice Abstract contract to provide swap functionality
contract SwapperV2 is ILiFi {
/// Types ///
/// @dev only used to get around "Stack Too Deep" errors
struct ReserveData {
bytes32 transactionId;
address payable leftoverReceiver;
uint256 nativeReserve;
}
/// Modifiers ///
/// @dev Sends any leftover balances back to the user
/// @notice Sends any leftover balances to the user
/// @param _swaps Swap data array
/// @param _leftoverReceiver Address to send leftover tokens to
/// @param _initialBalances Array of initial token balances
modifier noLeftovers(
LibSwap.SwapData[] calldata _swaps,
address payable _leftoverReceiver,
uint256[] memory _initialBalances
) {
uint256 numSwaps = _swaps.length;
if (numSwaps != 1) {
address finalAsset = _swaps[numSwaps - 1].receivingAssetId;
uint256 curBalance;
_;
for (uint256 i = 0; i < numSwaps - 1; ) {
address curAsset = _swaps[i].receivingAssetId;
// Handle multi-to-one swaps
if (curAsset != finalAsset) {
curBalance =
LibAsset.getOwnBalance(curAsset) -
_initialBalances[i];
if (curBalance > 0) {
LibAsset.transferAsset(
curAsset,
_leftoverReceiver,
curBalance
);
}
}
unchecked {
++i;
}
}
} else {
_;
}
}
/// @dev Sends any leftover balances back to the user reserving native tokens
/// @notice Sends any leftover balances to the user
/// @param _swaps Swap data array
/// @param _leftoverReceiver Address to send leftover tokens to
/// @param _initialBalances Array of initial token balances
modifier noLeftoversReserve(
LibSwap.SwapData[] calldata _swaps,
address payable _leftoverReceiver,
uint256[] memory _initialBalances,
uint256 _nativeReserve
) {
uint256 numSwaps = _swaps.length;
if (numSwaps != 1) {
address finalAsset = _swaps[numSwaps - 1].receivingAssetId;
uint256 curBalance;
_;
for (uint256 i = 0; i < numSwaps - 1; ) {
address curAsset = _swaps[i].receivingAssetId;
// Handle multi-to-one swaps
if (curAsset != finalAsset) {
curBalance =
LibAsset.getOwnBalance(curAsset) -
_initialBalances[i];
uint256 reserve = LibAsset.isNativeAsset(curAsset)
? _nativeReserve
: 0;
if (curBalance > 0) {
LibAsset.transferAsset(
curAsset,
_leftoverReceiver,
curBalance - reserve
);
}
}
unchecked {
++i;
}
}
} else {
_;
}
}
/// @dev Refunds any excess native asset sent to the contract after the main function
/// @notice Refunds any excess native asset sent to the contract after the main function
/// @param _refundReceiver Address to send refunds to
modifier refundExcessNative(address payable _refundReceiver) {
uint256 initialBalance = address(this).balance - msg.value;
_;
uint256 finalBalance = address(this).balance;
if (finalBalance > initialBalance) {
LibAsset.transferAsset(
LibAsset.NATIVE_ASSETID,
_refundReceiver,
finalBalance - initialBalance
);
}
}
/// Internal Methods ///
/// @dev Deposits value, executes swaps, and performs minimum amount check
/// @param _transactionId the transaction id associated with the operation
/// @param _minAmount the minimum amount of the final asset to receive
/// @param _swaps Array of data used to execute swaps
/// @param _leftoverReceiver The address to send leftover funds to
/// @return uint256 result of the swap
function _depositAndSwap(
bytes32 _transactionId,
uint256 _minAmount,
LibSwap.SwapData[] calldata _swaps,
address payable _leftoverReceiver
) internal returns (uint256) {
uint256 numSwaps = _swaps.length;
if (numSwaps == 0) {
revert NoSwapDataProvided();
}
address finalTokenId = _swaps[numSwaps - 1].receivingAssetId;
uint256 initialBalance = LibAsset.getOwnBalance(finalTokenId);
if (LibAsset.isNativeAsset(finalTokenId)) {
initialBalance -= msg.value;
}
uint256[] memory initialBalances = _fetchBalances(_swaps);
LibAsset.depositAssets(_swaps);
_executeSwaps(
_transactionId,
_swaps,
_leftoverReceiver,
initialBalances
);
uint256 newBalance = LibAsset.getOwnBalance(finalTokenId) -
initialBalance;
if (newBalance < _minAmount) {
revert CumulativeSlippageTooHigh(_minAmount, newBalance);
}
return newBalance;
}
/// @dev Deposits value, executes swaps, and performs minimum amount check and reserves native token for fees
/// @param _transactionId the transaction id associated with the operation
/// @param _minAmount the minimum amount of the final asset to receive
/// @param _swaps Array of data used to execute swaps
/// @param _leftoverReceiver The address to send leftover funds to
/// @param _nativeReserve Amount of native token to prevent from being swept back to the caller
function _depositAndSwap(
bytes32 _transactionId,
uint256 _minAmount,
LibSwap.SwapData[] calldata _swaps,
address payable _leftoverReceiver,
uint256 _nativeReserve
) internal returns (uint256) {
uint256 numSwaps = _swaps.length;
if (numSwaps == 0) {
revert NoSwapDataProvided();
}
address finalTokenId = _swaps[numSwaps - 1].receivingAssetId;
uint256 initialBalance = LibAsset.getOwnBalance(finalTokenId);
if (LibAsset.isNativeAsset(finalTokenId)) {
initialBalance -= msg.value;
}
uint256[] memory initialBalances = _fetchBalances(_swaps);
LibAsset.depositAssets(_swaps);
ReserveData memory rd = ReserveData(
_transactionId,
_leftoverReceiver,
_nativeReserve
);
_executeSwaps(rd, _swaps, initialBalances);
uint256 newBalance = LibAsset.getOwnBalance(finalTokenId) -
initialBalance;
if (LibAsset.isNativeAsset(finalTokenId)) {
newBalance -= _nativeReserve;
}
if (newBalance < _minAmount) {
revert CumulativeSlippageTooHigh(_minAmount, newBalance);
}
return newBalance;
}
/// Private Methods ///
/// @dev Executes swaps and checks that DEXs used are in the allowList
/// @param _transactionId the transaction id associated with the operation
/// @param _swaps Array of data used to execute swaps
/// @param _leftoverReceiver Address to send leftover tokens to
/// @param _initialBalances Array of initial balances
function _executeSwaps(
bytes32 _transactionId,
LibSwap.SwapData[] calldata _swaps,
address payable _leftoverReceiver,
uint256[] memory _initialBalances
) internal noLeftovers(_swaps, _leftoverReceiver, _initialBalances) {
uint256 numSwaps = _swaps.length;
for (uint256 i = 0; i < numSwaps; ) {
LibSwap.SwapData calldata currentSwap = _swaps[i];
if (
!((LibAsset.isNativeAsset(currentSwap.sendingAssetId) ||
LibAllowList.contractIsAllowed(currentSwap.approveTo)) &&
LibAllowList.contractIsAllowed(currentSwap.callTo) &&
LibAllowList.selectorIsAllowed(
bytes4(currentSwap.callData[:4])
))
) revert ContractCallNotAllowed();
LibSwap.swap(_transactionId, currentSwap);
unchecked {
++i;
}
}
}
/// @dev Executes swaps and checks that DEXs used are in the allowList
/// @param _reserveData Data passed used to reserve native tokens
/// @param _swaps Array of data used to execute swaps
function _executeSwaps(
ReserveData memory _reserveData,
LibSwap.SwapData[] calldata _swaps,
uint256[] memory _initialBalances
)
internal
noLeftoversReserve(
_swaps,
_reserveData.leftoverReceiver,
_initialBalances,
_reserveData.nativeReserve
)
{
uint256 numSwaps = _swaps.length;
for (uint256 i = 0; i < numSwaps; ) {
LibSwap.SwapData calldata currentSwap = _swaps[i];
if (
!((LibAsset.isNativeAsset(currentSwap.sendingAssetId) ||
LibAllowList.contractIsAllowed(currentSwap.approveTo)) &&
LibAllowList.contractIsAllowed(currentSwap.callTo) &&
LibAllowList.selectorIsAllowed(
bytes4(currentSwap.callData[:4])
))
) revert ContractCallNotAllowed();
LibSwap.swap(_reserveData.transactionId, currentSwap);
unchecked {
++i;
}
}
}
/// @dev Fetches balances of tokens to be swapped before swapping.
/// @param _swaps Array of data used to execute swaps
/// @return uint256[] Array of token balances.
function _fetchBalances(
LibSwap.SwapData[] calldata _swaps
) private view returns (uint256[] memory) {
uint256 numSwaps = _swaps.length;
uint256[] memory balances = new uint256[](numSwaps);
address asset;
for (uint256 i = 0; i < numSwaps; ) {
asset = _swaps[i].receivingAssetId;
balances[i] = LibAsset.getOwnBalance(asset);
if (LibAsset.isNativeAsset(asset)) {
balances[i] -= msg.value;
}
unchecked {
++i;
}
}
return balances;
}
}// SPDX-License-Identifier: UNLICENSED
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import { LibAsset } from "../Libraries/LibAsset.sol";
import { LibUtil } from "../Libraries/LibUtil.sol";
import { InvalidReceiver, InformationMismatch, InvalidSendingToken, InvalidAmount, NativeAssetNotSupported, InvalidDestinationChain, CannotBridgeToSameNetwork } from "../Errors/GenericErrors.sol";
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { LibSwap } from "../Libraries/LibSwap.sol";
contract Validatable {
modifier validateBridgeData(ILiFi.BridgeData memory _bridgeData) {
if (LibUtil.isZeroAddress(_bridgeData.receiver)) {
revert InvalidReceiver();
}
if (_bridgeData.minAmount == 0) {
revert InvalidAmount();
}
if (_bridgeData.destinationChainId == block.chainid) {
revert CannotBridgeToSameNetwork();
}
_;
}
modifier noNativeAsset(ILiFi.BridgeData memory _bridgeData) {
if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) {
revert NativeAssetNotSupported();
}
_;
}
modifier onlyAllowSourceToken(
ILiFi.BridgeData memory _bridgeData,
address _token
) {
if (_bridgeData.sendingAssetId != _token) {
revert InvalidSendingToken();
}
_;
}
modifier onlyAllowDestinationChain(
ILiFi.BridgeData memory _bridgeData,
uint256 _chainId
) {
if (_bridgeData.destinationChainId != _chainId) {
revert InvalidDestinationChain();
}
_;
}
modifier containsSourceSwaps(ILiFi.BridgeData memory _bridgeData) {
if (!_bridgeData.hasSourceSwaps) {
revert InformationMismatch();
}
_;
}
modifier doesNotContainSourceSwaps(ILiFi.BridgeData memory _bridgeData) {
if (_bridgeData.hasSourceSwaps) {
revert InformationMismatch();
}
_;
}
modifier doesNotContainDestinationCalls(
ILiFi.BridgeData memory _bridgeData
) {
if (_bridgeData.hasDestinationCall) {
revert InformationMismatch();
}
_;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/// @title Interface for StargateV2
/// @author LI.FI (https://li.fi)
/// @custom:version 1.0.0
interface IStargate {
/// @notice Stargate implementation type.
enum StargateType {
Pool,
OFT
}
/// @notice Ticket data for bus ride.
struct Ticket {
uint72 ticketId;
bytes passengerBytes;
}
/**
* @dev Struct representing token parameters for the OFT send() operation.
*/
struct SendParam {
uint32 dstEid; // Destination endpoint ID.
bytes32 to; // Recipient address.
uint256 amountLD; // Amount to send in local decimals.
uint256 minAmountLD; // Minimum amount to send in local decimals.
bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
bytes composeMsg; // The composed message for the send() operation.
bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.
}
/**
* @dev Struct representing OFT limit information.
* @dev These amounts can change dynamically and are up the the specific oft implementation.
*/
struct OFTLimit {
uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.
uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.
}
/**
* @dev Struct representing OFT receipt information.
*/
struct OFTReceipt {
uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.
// @dev In non-default implementations, the amountReceivedLD COULD differ from this value.
uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.
}
/**
* @dev Struct representing OFT fee details.
* @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.
*/
struct OFTFeeDetail {
int256 feeAmountLD; // Amount of the fee in local decimals.
string description; // Description of the fee.
}
struct MessagingFee {
uint256 nativeFee;
uint256 lzTokenFee;
}
struct MessagingReceipt {
bytes32 guid;
uint64 nonce;
MessagingFee fee;
}
/// @dev This function is same as `send` in OFT interface but returns the ticket data if in the bus ride mode,
/// which allows the caller to ride and drive the bus in the same transaction.
function sendToken(
SendParam calldata _sendParam,
MessagingFee calldata _fee,
address _refundAddress
)
external
payable
returns (
MessagingReceipt memory msgReceipt,
OFTReceipt memory oftReceipt,
Ticket memory ticket
);
/**
* @notice Provides a quote for OFT-related operations.
* @param _sendParam The parameters for the send operation.
* @return limit The OFT limit information.
* @return oftFeeDetails The details of OFT fees.
* @return receipt The OFT receipt information.
*/
function quoteOFT(
SendParam calldata _sendParam
)
external
view
returns (
OFTLimit memory,
OFTFeeDetail[] memory oftFeeDetails,
OFTReceipt memory
);
/**
* @notice Provides a quote for the send() operation.
* @param _sendParam The parameters for the send() operation.
* @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
* @return fee The calculated LayerZero messaging fee from the send() operation.
*
* @dev MessagingFee: LayerZero msg fee
* - nativeFee: The native fee.
* - lzTokenFee: The lzToken fee.
*/
function quoteSend(
SendParam calldata _sendParam,
bool _payInLzToken
) external view returns (MessagingFee memory);
}
interface ITokenMessaging {
function assetIds(address tokenAddress) external returns (uint16);
function stargateImpls(uint16 assetId) external returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success :=
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(and(call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), exists)) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `1` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero(call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00)) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// - The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - The `permit` function uses the ecrecover precompile (0x1).
///
/// If you are overriding:
/// - NEVER violate the ERC20 invariant:
/// the total sum of all balances must be equal to `totalSupply()`.
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();
/// @dev The allowance has overflowed.
error AllowanceOverflow();
/// @dev The allowance has underflowed.
error AllowanceUnderflow();
/// @dev Insufficient balance.
error InsufficientBalance();
/// @dev Insufficient allowance.
error InsufficientAllowance();
/// @dev The permit is invalid.
error InvalidPermit();
/// @dev The permit has expired.
error PermitExpired();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The storage slot for the total supply.
uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;
/// @dev The balance slot of `owner` is given by:
/// ```
/// mstore(0x0c, _BALANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;
/// @dev The allowance slot of (`owner`, `spender`) is given by:
/// ```
/// mstore(0x20, spender)
/// mstore(0x0c, _ALLOWANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let allowanceSlot := keccak256(0x0c, 0x34)
/// ```
uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
/// @dev The nonce slot of `owner` is given by:
/// ```
/// mstore(0x0c, _NONCES_SLOT_SEED)
/// mstore(0x00, owner)
/// let nonceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _NONCES_SLOT_SEED = 0x38377508;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 private constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev `keccak256("1")`.
bytes32 private constant _VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
bytes32 private constant _PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the decimals places of the token.
function decimals() public view virtual returns (uint8) {
return 18;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_TOTAL_SUPPLY_SLOT)
}
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x34))
}
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(msg.sender, to, amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, caller())
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
}
_afterTokenTransfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if add(allowance_, 1) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
return true;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-2612 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For more performance, override to return the constant value
/// of `keccak256(bytes(name()))` if `name()` will never change.
function _constantNameHash() internal view virtual returns (bytes32 result) {}
/// @dev Returns the current nonce for `owner`.
/// This value is used to compute the signature for EIP-2612 permit.
function nonces(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Compute the nonce slot and load its value.
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
/// authorized by a signed approval by `owner`.
///
/// Emits a {Approval} event.
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
// Revert if the block timestamp is greater than `deadline`.
if gt(timestamp(), deadline) {
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
revert(0x1c, 0x04)
}
let m := mload(0x40) // Grab the free memory pointer.
// Clean the upper 96 bits.
owner := shr(96, shl(96, owner))
spender := shr(96, shl(96, spender))
// Compute the nonce slot and load its value.
mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Prepare the domain separator.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), _VERSION_HASH)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
mstore(0x2e, keccak256(m, 0xa0))
// Prepare the struct hash.
mstore(m, _PERMIT_TYPEHASH)
mstore(add(m, 0x20), owner)
mstore(add(m, 0x40), spender)
mstore(add(m, 0x60), value)
mstore(add(m, 0x80), nonceValue)
mstore(add(m, 0xa0), deadline)
mstore(0x4e, keccak256(m, 0xc0))
// Prepare the ecrecover calldata.
mstore(0x00, keccak256(0x2c, 0x42))
mstore(0x20, and(0xff, v))
mstore(0x40, r)
mstore(0x60, s)
let t := staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)
// If the ecrecover fails, the returndatasize will be 0x00,
// `owner` will be checked if it equals the hash at 0x00,
// which evaluates to false (i.e. 0), and we will revert.
// If the ecrecover succeeds, the returndatasize will be 0x20,
// `owner` will be compared against the returned address at 0x20.
if iszero(eq(mload(returndatasize()), owner)) {
mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
revert(0x1c, 0x04)
}
// Increment and store the updated nonce.
sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
// Compute the allowance slot and store the value.
// The `owner` is already at slot 0x20.
mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
sstore(keccak256(0x2c, 0x34), value)
// Emit the {Approval} event.
log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Returns the EIP-712 domain separator for the EIP-2612 permit.
function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Grab the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), _VERSION_HASH)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
result := keccak256(m, 0xa0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 amount) internal virtual {
_beforeTokenTransfer(address(0), to, amount);
/// @solidity memory-safe-assembly
assembly {
let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT)
let totalSupplyAfter := add(totalSupplyBefore, amount)
// Revert if the total supply overflows.
if lt(totalSupplyAfter, totalSupplyBefore) {
mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`.
revert(0x1c, 0x04)
}
// Store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter)
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c)))
}
_afterTokenTransfer(address(0), to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
_beforeTokenTransfer(from, address(0), amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, from)
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Subtract and store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount))
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
}
_afterTokenTransfer(from, address(0), amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Moves `amount` of tokens from `from` to `to`.
function _transfer(address from, address to, uint256 amount) internal virtual {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
_afterTokenTransfer(from, to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL ALLOWANCE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Updates the allowance of `owner` for `spender` based on spent `amount`.
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and load its value.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if add(allowance_, 1) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
}
}
/// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
///
/// Emits a {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal virtual {
/// @solidity memory-safe-assembly
assembly {
let owner_ := shl(96, owner)
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED))
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c)))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any transfer of tokens.
/// This includes minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/// @dev Hook that is called after any transfer of tokens.
/// This includes minting and burning.
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
* 0 before setting it to a non-zero value.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../interfaces/IBridge.sol";
import "../../interfaces/IOriginalTokenVault.sol";
import "../../interfaces/IOriginalTokenVaultV2.sol";
import "../../interfaces/IPeggedTokenBridge.sol";
import "../../interfaces/IPeggedTokenBridgeV2.sol";
import "../interfaces/IMessageBus.sol";
import "./MsgDataTypes.sol";
library MessageSenderLib {
using SafeERC20 for IERC20;
// ============== Internal library functions called by apps ==============
/**
* @notice Sends a message to an app on another chain via MessageBus without an associated transfer.
* @param _receiver The address of the destination app contract.
* @param _dstChainId The destination chain ID.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
* @param _messageBus The address of the MessageBus on this chain.
* @param _fee The fee amount to pay to MessageBus.
*/
function sendMessage(
address _receiver,
uint64 _dstChainId,
bytes memory _message,
address _messageBus,
uint256 _fee
) internal {
IMessageBus(_messageBus).sendMessage{value: _fee}(_receiver, _dstChainId, _message);
}
// Send message to non-evm chain with bytes for receiver address,
// otherwise same as above.
function sendMessage(
bytes calldata _receiver,
uint64 _dstChainId,
bytes memory _message,
address _messageBus,
uint256 _fee
) internal {
IMessageBus(_messageBus).sendMessage{value: _fee}(_receiver, _dstChainId, _message);
}
/**
* @notice Sends a message to an app on another chain via MessageBus with an associated transfer.
* @param _receiver The address of the destination app contract.
* @param _token The address of the token to be sent.
* @param _amount The amount of tokens to be sent.
* @param _dstChainId The destination chain ID.
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%.
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
* transfer can be refunded. Only applicable to the {MsgDataTypes.BridgeSendType.Liquidity}.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
* @param _bridgeSendType One of the {MsgDataTypes.BridgeSendType} enum.
* @param _messageBus The address of the MessageBus on this chain.
* @param _fee The fee amount to pay to MessageBus.
* @return The transfer ID.
*/
function sendMessageWithTransfer(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage,
bytes memory _message,
MsgDataTypes.BridgeSendType _bridgeSendType,
address _messageBus,
uint256 _fee
) internal returns (bytes32) {
(bytes32 transferId, address bridge) = sendTokenTransfer(
_receiver,
_token,
_amount,
_dstChainId,
_nonce,
_maxSlippage,
_bridgeSendType,
_messageBus
);
if (_message.length > 0) {
IMessageBus(_messageBus).sendMessageWithTransfer{value: _fee}(
_receiver,
_dstChainId,
bridge,
transferId,
_message
);
}
return transferId;
}
/**
* @notice Sends a token transfer via a bridge.
* @param _receiver The address of the destination app contract.
* @param _token The address of the token to be sent.
* @param _amount The amount of tokens to be sent.
* @param _dstChainId The destination chain ID.
* @param _nonce A number input to guarantee uniqueness of transferId. Can be timestamp in practice.
* @param _maxSlippage The max slippage accepted, given as percentage in point (pip). Eg. 5000 means 0.5%.
* Must be greater than minimalMaxSlippage. Receiver is guaranteed to receive at least (100% - max slippage percentage) * amount or the
* transfer can be refunded.
* @param _bridgeSendType One of the {MsgDataTypes.BridgeSendType} enum.
*/
function sendTokenTransfer(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage,
MsgDataTypes.BridgeSendType _bridgeSendType,
address _messageBus
) internal returns (bytes32 transferId, address bridge) {
if (_bridgeSendType == MsgDataTypes.BridgeSendType.Liquidity) {
bridge = IMessageBus(_messageBus).liquidityBridge();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
IBridge(bridge).send(_receiver, _token, _amount, _dstChainId, _nonce, _maxSlippage);
transferId = computeLiqBridgeTransferId(_receiver, _token, _amount, _dstChainId, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegDeposit) {
bridge = IMessageBus(_messageBus).pegVault();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
IOriginalTokenVault(bridge).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
transferId = computePegV1DepositId(_receiver, _token, _amount, _dstChainId, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegBurn) {
bridge = IMessageBus(_messageBus).pegBridge();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
IPeggedTokenBridge(bridge).burn(_token, _amount, _receiver, _nonce);
// handle cases where certain tokens do not spend allowance for role-based burn
IERC20(_token).safeApprove(bridge, 0);
transferId = computePegV1BurnId(_receiver, _token, _amount, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Deposit) {
bridge = IMessageBus(_messageBus).pegVaultV2();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
transferId = IOriginalTokenVaultV2(bridge).deposit(_token, _amount, _dstChainId, _receiver, _nonce);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2Burn) {
bridge = IMessageBus(_messageBus).pegBridgeV2();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
transferId = IPeggedTokenBridgeV2(bridge).burn(_token, _amount, _dstChainId, _receiver, _nonce);
// handle cases where certain tokens do not spend allowance for role-based burn
IERC20(_token).safeApprove(bridge, 0);
} else if (_bridgeSendType == MsgDataTypes.BridgeSendType.PegV2BurnFrom) {
bridge = IMessageBus(_messageBus).pegBridgeV2();
IERC20(_token).safeIncreaseAllowance(bridge, _amount);
transferId = IPeggedTokenBridgeV2(bridge).burnFrom(_token, _amount, _dstChainId, _receiver, _nonce);
// handle cases where certain tokens do not spend allowance for role-based burn
IERC20(_token).safeApprove(bridge, 0);
} else {
revert("bridge type not supported");
}
}
function computeLiqBridgeTransferId(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce
) internal view returns (bytes32) {
return
keccak256(
abi.encodePacked(address(this), _receiver, _token, _amount, _dstChainId, _nonce, uint64(block.chainid))
);
}
function computePegV1DepositId(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce
) internal view returns (bytes32) {
return
keccak256(
abi.encodePacked(address(this), _token, _amount, _dstChainId, _receiver, _nonce, uint64(block.chainid))
);
}
function computePegV1BurnId(
address _receiver,
address _token,
uint256 _amount,
uint64 _nonce
) internal view returns (bytes32) {
return keccak256(abi.encodePacked(address(this), _token, _amount, _receiver, _nonce, uint64(block.chainid)));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { LibSwap } from "../Libraries/LibSwap.sol";
import { ContractCallNotAllowed, ExternalCallFailed, InvalidConfig, UnAuthorized, WithdrawFailed } from "../Errors/GenericErrors.sol";
import { LibAsset } from "../Libraries/LibAsset.sol";
import { LibUtil } from "../Libraries/LibUtil.sol";
import { ILiFi } from "../Interfaces/ILiFi.sol";
import { PeripheryRegistryFacet } from "../Facets/PeripheryRegistryFacet.sol";
import { IExecutor } from "../Interfaces/IExecutor.sol";
import { WithdrawablePeriphery } from "../Helpers/WithdrawablePeriphery.sol";
import { IMessageReceiverApp } from "celer-network/contracts/message/interfaces/IMessageReceiverApp.sol";
import { CelerIM } from "lifi/Helpers/CelerIMFacetBase.sol";
import { MessageSenderLib, MsgDataTypes, IMessageBus, IOriginalTokenVault, IPeggedTokenBridge, IOriginalTokenVaultV2, IPeggedTokenBridgeV2 } from "celer-network/contracts/message/libraries/MessageSenderLib.sol";
import { IBridge as ICBridge } from "celer-network/contracts/interfaces/IBridge.sol";
/// @title RelayerCelerIM
/// @author LI.FI (https://li.fi)
/// @notice Relayer contract for CelerIM that forwards calls and handles refunds on src side and acts receiver on dest
/// @custom:version 2.1.1
contract RelayerCelerIM is ILiFi, WithdrawablePeriphery {
using SafeERC20 for IERC20;
/// Storage ///
IMessageBus public cBridgeMessageBus;
address public diamondAddress;
/// Modifiers ///
modifier onlyCBridgeMessageBus() {
if (msg.sender != address(cBridgeMessageBus)) revert UnAuthorized();
_;
}
modifier onlyDiamond() {
if (msg.sender != diamondAddress) revert UnAuthorized();
_;
}
/// Constructor
constructor(
address _cBridgeMessageBusAddress,
address _owner,
address _diamondAddress
) WithdrawablePeriphery(_owner) {
cBridgeMessageBus = IMessageBus(_cBridgeMessageBusAddress);
diamondAddress = _diamondAddress;
}
/// External Methods ///
/**
* @notice Called by MessageBus to execute a message with an associated token transfer.
* The Receiver is guaranteed to have received the right amount of tokens before this function is called.
* @param * (unused) The address of the source app contract
* @param _token The address of the token that comes out of the bridge
* @param _amount The amount of tokens received at this contract through the cross-chain bridge.
* @param * (unused) The source chain ID where the transfer is originated from
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param * (unused) Address who called the MessageBus execution function
*/
function executeMessageWithTransfer(
address,
address _token,
uint256 _amount,
uint64,
bytes calldata _message,
address
)
external
payable
onlyCBridgeMessageBus
returns (IMessageReceiverApp.ExecutionStatus)
{
// decode message
(
bytes32 transactionId,
LibSwap.SwapData[] memory swapData,
address receiver,
address refundAddress
) = abi.decode(
_message,
(bytes32, LibSwap.SwapData[], address, address)
);
_swapAndCompleteBridgeTokens(
transactionId,
swapData,
_token,
payable(receiver),
_amount,
refundAddress
);
return IMessageReceiverApp.ExecutionStatus.Success;
}
/**
* @notice Called by MessageBus to process refund of the original transfer from this contract.
* The contract is guaranteed to have received the refund before this function is called.
* @param _token The token address of the original transfer
* @param _amount The amount of the original transfer
* @param _message The same message associated with the original transfer
* @param * (unused) Address who called the MessageBus execution function
*/
function executeMessageWithTransferRefund(
address _token,
uint256 _amount,
bytes calldata _message,
address
)
external
payable
onlyCBridgeMessageBus
returns (IMessageReceiverApp.ExecutionStatus)
{
(bytes32 transactionId, , , address refundAddress) = abi.decode(
_message,
(bytes32, LibSwap.SwapData[], address, address)
);
// return funds to cBridgeData.refundAddress
LibAsset.transferAsset(_token, payable(refundAddress), _amount);
emit LiFiTransferRecovered(
transactionId,
_token,
refundAddress,
_amount,
block.timestamp
);
return IMessageReceiverApp.ExecutionStatus.Success;
}
/**
* @notice Forwards a call to transfer tokens to cBridge (sent via this contract to ensure that potential refunds are sent here)
* @param _bridgeData the core information needed for bridging
* @param _celerIMData data specific to CelerIM
*/
// solhint-disable-next-line code-complexity
function sendTokenTransfer(
ILiFi.BridgeData memory _bridgeData,
CelerIM.CelerIMData calldata _celerIMData
)
external
payable
onlyDiamond
returns (bytes32 transferId, address bridgeAddress)
{
// approve to and call correct bridge depending on BridgeSendType
// @dev copied and slightly adapted from Celer MessageSenderLib
if (_celerIMData.bridgeType == MsgDataTypes.BridgeSendType.Liquidity) {
bridgeAddress = cBridgeMessageBus.liquidityBridge();
if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) {
// case: native asset bridging
ICBridge(bridgeAddress).sendNative{
value: _bridgeData.minAmount
}(
_bridgeData.receiver,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_celerIMData.nonce,
_celerIMData.maxSlippage
);
} else {
// case: ERC20 asset bridging
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
bridgeAddress,
_bridgeData.minAmount
);
// solhint-disable-next-line check-send-result
ICBridge(bridgeAddress).send(
_bridgeData.receiver,
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_celerIMData.nonce,
_celerIMData.maxSlippage
);
}
transferId = MessageSenderLib.computeLiqBridgeTransferId(
_bridgeData.receiver,
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_celerIMData.nonce
);
} else if (
_celerIMData.bridgeType == MsgDataTypes.BridgeSendType.PegDeposit
) {
bridgeAddress = cBridgeMessageBus.pegVault();
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
bridgeAddress,
_bridgeData.minAmount
);
IOriginalTokenVault(bridgeAddress).deposit(
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_bridgeData.receiver,
_celerIMData.nonce
);
transferId = MessageSenderLib.computePegV1DepositId(
_bridgeData.receiver,
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_celerIMData.nonce
);
} else if (
_celerIMData.bridgeType == MsgDataTypes.BridgeSendType.PegBurn
) {
bridgeAddress = cBridgeMessageBus.pegBridge();
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
bridgeAddress,
_bridgeData.minAmount
);
IPeggedTokenBridge(bridgeAddress).burn(
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
_bridgeData.receiver,
_celerIMData.nonce
);
transferId = MessageSenderLib.computePegV1BurnId(
_bridgeData.receiver,
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
_celerIMData.nonce
);
} else if (
_celerIMData.bridgeType == MsgDataTypes.BridgeSendType.PegV2Deposit
) {
bridgeAddress = cBridgeMessageBus.pegVaultV2();
if (LibAsset.isNativeAsset(_bridgeData.sendingAssetId)) {
// case: native asset bridging
transferId = IOriginalTokenVaultV2(bridgeAddress)
.depositNative{ value: _bridgeData.minAmount }(
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_bridgeData.receiver,
_celerIMData.nonce
);
} else {
// case: ERC20 bridging
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
bridgeAddress,
_bridgeData.minAmount
);
transferId = IOriginalTokenVaultV2(bridgeAddress).deposit(
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_bridgeData.receiver,
_celerIMData.nonce
);
}
} else if (
_celerIMData.bridgeType == MsgDataTypes.BridgeSendType.PegV2Burn
) {
bridgeAddress = cBridgeMessageBus.pegBridgeV2();
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
bridgeAddress,
_bridgeData.minAmount
);
transferId = IPeggedTokenBridgeV2(bridgeAddress).burn(
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_bridgeData.receiver,
_celerIMData.nonce
);
} else if (
_celerIMData.bridgeType ==
MsgDataTypes.BridgeSendType.PegV2BurnFrom
) {
bridgeAddress = cBridgeMessageBus.pegBridgeV2();
LibAsset.maxApproveERC20(
IERC20(_bridgeData.sendingAssetId),
bridgeAddress,
_bridgeData.minAmount
);
transferId = IPeggedTokenBridgeV2(bridgeAddress).burnFrom(
_bridgeData.sendingAssetId,
_bridgeData.minAmount,
uint64(_bridgeData.destinationChainId),
_bridgeData.receiver,
_celerIMData.nonce
);
} else {
revert InvalidConfig();
}
}
/**
* @notice Forwards a call to the CBridge Messagebus
* @param _receiver The address of the destination app contract.
* @param _dstChainId The destination chain ID.
* @param _srcBridge The bridge contract to send the transfer with.
* @param _srcTransferId The transfer ID.
* @param _dstChainId The destination chain ID.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
*/
function forwardSendMessageWithTransfer(
address _receiver,
uint256 _dstChainId,
address _srcBridge,
bytes32 _srcTransferId,
bytes calldata _message
) external payable onlyDiamond {
cBridgeMessageBus.sendMessageWithTransfer{ value: msg.value }(
_receiver,
_dstChainId,
_srcBridge,
_srcTransferId,
_message
);
}
// ------------------------------------------------------------------------------------------------
/// Private Methods ///
/// @notice Performs a swap before completing a cross-chain transaction
/// @param _transactionId the transaction id associated with the operation
/// @param _swapData array of data needed for swaps
/// @param assetId token received from the other chain
/// @param receiver address that will receive tokens in the end
/// @param amount amount of token
function _swapAndCompleteBridgeTokens(
bytes32 _transactionId,
LibSwap.SwapData[] memory _swapData,
address assetId,
address payable receiver,
uint256 amount,
address refundAddress
) private {
bool success;
IExecutor executor = IExecutor(
PeripheryRegistryFacet(diamondAddress).getPeripheryContract(
"Executor"
)
);
if (LibAsset.isNativeAsset(assetId)) {
try
executor.swapAndCompleteBridgeTokens{ value: amount }(
_transactionId,
_swapData,
assetId,
receiver
)
{
success = true;
} catch {
SafeTransferLib.safeTransferETH(refundAddress, amount);
}
} else {
IERC20 token = IERC20(assetId);
token.safeApprove(address(executor), 0);
token.safeIncreaseAllowance(address(executor), amount);
try
executor.swapAndCompleteBridgeTokens(
_transactionId,
_swapData,
assetId,
receiver
)
{
success = true;
} catch {
token.safeTransfer(refundAddress, amount);
}
token.safeApprove(address(executor), 0);
}
if (!success) {
emit LiFiTransferRecovered(
_transactionId,
assetId,
refundAddress,
amount,
block.timestamp
);
}
}
/// @notice Triggers a cBridge refund with calldata produced by cBridge API
/// @param _callTo The address to execute the calldata on
/// @param _callData The data to execute
/// @param _assetAddress Asset to be withdrawn
/// @param _to Address to withdraw to
/// @param _amount Amount of asset to withdraw
function triggerRefund(
address payable _callTo,
bytes calldata _callData,
address _assetAddress,
address _to,
uint256 _amount
) external onlyOwner {
bool success;
// make sure that callTo address is either of the cBridge addresses
if (
cBridgeMessageBus.liquidityBridge() != _callTo &&
cBridgeMessageBus.pegBridge() != _callTo &&
cBridgeMessageBus.pegBridgeV2() != _callTo &&
cBridgeMessageBus.pegVault() != _callTo &&
cBridgeMessageBus.pegVaultV2() != _callTo
) {
revert ContractCallNotAllowed();
}
// call contract
// solhint-disable-next-line avoid-low-level-calls
(success, ) = _callTo.call(_callData);
// forward funds to _to address and emit event, if cBridge refund successful
if (success) {
address payable sendTo = payable(
(LibUtil.isZeroAddress(_to)) ? msg.sender : _to
);
LibAsset.transferAsset(_assetAddress, sendTo, _amount);
emit TokensWithdrawn(_assetAddress, sendTo, _amount);
} else {
revert WithdrawFailed();
}
}
// required in order to receive native tokens from cBridge facet
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import { InvalidContract } from "../Errors/GenericErrors.sol";
/// @title Lib Allow List
/// @author LI.FI (https://li.fi)
/// @notice Library for managing and accessing the conract address allow list
library LibAllowList {
/// Storage ///
bytes32 internal constant NAMESPACE =
keccak256("com.lifi.library.allow.list");
struct AllowListStorage {
mapping(address => bool) allowlist;
mapping(bytes4 => bool) selectorAllowList;
address[] contracts;
}
/// @dev Adds a contract address to the allow list
/// @param _contract the contract address to add
function addAllowedContract(address _contract) internal {
_checkAddress(_contract);
AllowListStorage storage als = _getStorage();
if (als.allowlist[_contract]) return;
als.allowlist[_contract] = true;
als.contracts.push(_contract);
}
/// @dev Checks whether a contract address has been added to the allow list
/// @param _contract the contract address to check
function contractIsAllowed(
address _contract
) internal view returns (bool) {
return _getStorage().allowlist[_contract];
}
/// @dev Remove a contract address from the allow list
/// @param _contract the contract address to remove
function removeAllowedContract(address _contract) internal {
AllowListStorage storage als = _getStorage();
if (!als.allowlist[_contract]) {
return;
}
als.allowlist[_contract] = false;
uint256 length = als.contracts.length;
// Find the contract in the list
for (uint256 i = 0; i < length; i++) {
if (als.contracts[i] == _contract) {
// Move the last element into the place to delete
als.contracts[i] = als.contracts[length - 1];
// Remove the last element
als.contracts.pop();
break;
}
}
}
/// @dev Fetch contract addresses from the allow list
function getAllowedContracts() internal view returns (address[] memory) {
return _getStorage().contracts;
}
/// @dev Add a selector to the allow list
/// @param _selector the selector to add
function addAllowedSelector(bytes4 _selector) internal {
_getStorage().selectorAllowList[_selector] = true;
}
/// @dev Removes a selector from the allow list
/// @param _selector the selector to remove
function removeAllowedSelector(bytes4 _selector) internal {
_getStorage().selectorAllowList[_selector] = false;
}
/// @dev Returns if selector has been added to the allow list
/// @param _selector the selector to check
function selectorIsAllowed(bytes4 _selector) internal view returns (bool) {
return _getStorage().selectorAllowList[_selector];
}
/// @dev Fetch local storage struct
function _getStorage()
internal
pure
returns (AllowListStorage storage als)
{
bytes32 position = NAMESPACE;
// solhint-disable-next-line no-inline-assembly
assembly {
als.slot := position
}
}
/// @dev Contains business logic for validating a contract address.
/// @param _contract address of the dex to check
function _checkAddress(address _contract) private view {
if (_contract == address(0)) revert InvalidContract();
if (_contract.code.length == 0) revert InvalidContract();
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IBridge {
function send(
address _receiver,
address _token,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage
) external;
function sendNative(
address _receiver,
uint256 _amount,
uint64 _dstChainId,
uint64 _nonce,
uint32 _maxSlippage
) external payable;
function relay(
bytes calldata _relayRequest,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
function transfers(bytes32 transferId) external view returns (bool);
function withdraws(bytes32 withdrawId) external view returns (bool);
function withdraw(
bytes calldata _wdmsg,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
/**
* @notice Verifies that a message is signed by a quorum among the signers.
* @param _msg signed message
* @param _sigs list of signatures sorted by signer addresses in ascending order
* @param _signers sorted list of current signers
* @param _powers powers of current signers
*/
function verifySigs(
bytes memory _msg,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external view;
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IOriginalTokenVault {
/**
* @notice Lock original tokens to trigger mint at a remote chain's PeggedTokenBridge
* @param _token local token address
* @param _amount locked token amount
* @param _mintChainId destination chainId to mint tokens
* @param _mintAccount destination account to receive minted tokens
* @param _nonce user input to guarantee unique depositId
*/
function deposit(
address _token,
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external;
/**
* @notice Lock native token as original token to trigger mint at a remote chain's PeggedTokenBridge
* @param _amount locked token amount
* @param _mintChainId destination chainId to mint tokens
* @param _mintAccount destination account to receive minted tokens
* @param _nonce user input to guarantee unique depositId
*/
function depositNative(
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external payable;
/**
* @notice Withdraw locked original tokens triggered by a burn at a remote chain's PeggedTokenBridge.
* @param _request The serialized Withdraw protobuf.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the bridge's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function withdraw(
bytes calldata _request,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
function records(bytes32 recordId) external view returns (bool);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IOriginalTokenVaultV2 {
/**
* @notice Lock original tokens to trigger mint at a remote chain's PeggedTokenBridge
* @param _token local token address
* @param _amount locked token amount
* @param _mintChainId destination chainId to mint tokens
* @param _mintAccount destination account to receive minted tokens
* @param _nonce user input to guarantee unique depositId
*/
function deposit(
address _token,
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external returns (bytes32);
/**
* @notice Lock native token as original token to trigger mint at a remote chain's PeggedTokenBridge
* @param _amount locked token amount
* @param _mintChainId destination chainId to mint tokens
* @param _mintAccount destination account to receive minted tokens
* @param _nonce user input to guarantee unique depositId
*/
function depositNative(
uint256 _amount,
uint64 _mintChainId,
address _mintAccount,
uint64 _nonce
) external payable returns (bytes32);
/**
* @notice Withdraw locked original tokens triggered by a burn at a remote chain's PeggedTokenBridge.
* @param _request The serialized Withdraw protobuf.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the bridge's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function withdraw(
bytes calldata _request,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external returns (bytes32);
function records(bytes32 recordId) external view returns (bool);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IPeggedTokenBridge {
/**
* @notice Burn tokens to trigger withdrawal at a remote chain's OriginalTokenVault
* @param _token local token address
* @param _amount locked token amount
* @param _withdrawAccount account who withdraw original tokens on the remote chain
* @param _nonce user input to guarantee unique depositId
*/
function burn(
address _token,
uint256 _amount,
address _withdrawAccount,
uint64 _nonce
) external;
/**
* @notice Mint tokens triggered by deposit at a remote chain's OriginalTokenVault.
* @param _request The serialized Mint protobuf.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the sigsVerifier's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function mint(
bytes calldata _request,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
function records(bytes32 recordId) external view returns (bool);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IPeggedTokenBridgeV2 {
/**
* @notice Burn pegged tokens to trigger a cross-chain withdrawal of the original tokens at a remote chain's
* OriginalTokenVault, or mint at another remote chain
* @param _token The pegged token address.
* @param _amount The amount to burn.
* @param _toChainId If zero, withdraw from original vault; otherwise, the remote chain to mint tokens.
* @param _toAccount The account to receive tokens on the remote chain
* @param _nonce A number to guarantee unique depositId. Can be timestamp in practice.
*/
function burn(
address _token,
uint256 _amount,
uint64 _toChainId,
address _toAccount,
uint64 _nonce
) external returns (bytes32);
// same with `burn` above, use openzeppelin ERC20Burnable interface
function burnFrom(
address _token,
uint256 _amount,
uint64 _toChainId,
address _toAccount,
uint64 _nonce
) external returns (bytes32);
/**
* @notice Mint tokens triggered by deposit at a remote chain's OriginalTokenVault.
* @param _request The serialized Mint protobuf.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the sigsVerifier's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function mint(
bytes calldata _request,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external returns (bytes32);
function records(bytes32 recordId) external view returns (bool);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import "../libraries/MsgDataTypes.sol";
interface IMessageBus {
/**
* @notice Send a message to a contract on another chain.
* Sender needs to make sure the uniqueness of the message Id, which is computed as
* hash(type.MessageOnly, sender, receiver, srcChainId, srcTxHash, dstChainId, message).
* If messages with the same Id are sent, only one of them will succeed at dst chain..
* A fee is charged in the native gas token.
* @param _receiver The address of the destination app contract.
* @param _dstChainId The destination chain ID.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
*/
function sendMessage(
address _receiver,
uint256 _dstChainId,
bytes calldata _message
) external payable;
// same as above, except that receiver is an non-evm chain address,
function sendMessage(
bytes calldata _receiver,
uint256 _dstChainId,
bytes calldata _message
) external payable;
/**
* @notice Send a message associated with a token transfer to a contract on another chain.
* If messages with the same srcTransferId are sent, only one of them will succeed at dst chain..
* A fee is charged in the native token.
* @param _receiver The address of the destination app contract.
* @param _dstChainId The destination chain ID.
* @param _srcBridge The bridge contract to send the transfer with.
* @param _srcTransferId The transfer ID.
* @param _dstChainId The destination chain ID.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
*/
function sendMessageWithTransfer(
address _receiver,
uint256 _dstChainId,
address _srcBridge,
bytes32 _srcTransferId,
bytes calldata _message
) external payable;
/**
* @notice Execute a message not associated with a transfer.
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the sigsVerifier's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function executeMessage(
bytes calldata _message,
MsgDataTypes.RouteInfo calldata _route,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external payable;
/**
* @notice Execute a message with a successful transfer.
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param _transfer The transfer info.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the sigsVerifier's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function executeMessageWithTransfer(
bytes calldata _message,
MsgDataTypes.TransferInfo calldata _transfer,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external payable;
/**
* @notice Execute a message with a refunded transfer.
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param _transfer The transfer info.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A relay must be signed-off by
* +2/3 of the sigsVerifier's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function executeMessageWithTransferRefund(
bytes calldata _message, // the same message associated with the original transfer
MsgDataTypes.TransferInfo calldata _transfer,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external payable;
/**
* @notice Withdraws message fee in the form of native gas token.
* @param _account The address receiving the fee.
* @param _cumulativeFee The cumulative fee credited to the account. Tracked by SGN.
* @param _sigs The list of signatures sorted by signing addresses in ascending order. A withdrawal must be
* signed-off by +2/3 of the sigsVerifier's current signing power to be delivered.
* @param _signers The sorted list of signers.
* @param _powers The signing powers of the signers.
*/
function withdrawFee(
address _account,
uint256 _cumulativeFee,
bytes[] calldata _sigs,
address[] calldata _signers,
uint256[] calldata _powers
) external;
/**
* @notice Calculates the required fee for the message.
* @param _message Arbitrary message bytes to be decoded by the destination app contract.
@ @return The required fee.
*/
function calcFee(bytes calldata _message) external view returns (uint256);
function liquidityBridge() external view returns (address);
function pegBridge() external view returns (address);
function pegBridgeV2() external view returns (address);
function pegVault() external view returns (address);
function pegVaultV2() external view returns (address);
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
library MsgDataTypes {
string constant ABORT_PREFIX = "MSG::ABORT:";
// bridge operation type at the sender side (src chain)
enum BridgeSendType {
Null,
Liquidity,
PegDeposit,
PegBurn,
PegV2Deposit,
PegV2Burn,
PegV2BurnFrom
}
// bridge operation type at the receiver side (dst chain)
enum TransferType {
Null,
LqRelay, // relay through liquidity bridge
LqWithdraw, // withdraw from liquidity bridge
PegMint, // mint through pegged token bridge
PegWithdraw, // withdraw from original token vault
PegV2Mint, // mint through pegged token bridge v2
PegV2Withdraw // withdraw from original token vault v2
}
enum MsgType {
MessageWithTransfer,
MessageOnly
}
enum TxStatus {
Null,
Success,
Fail,
Fallback,
Pending // transient state within a transaction
}
struct TransferInfo {
TransferType t;
address sender;
address receiver;
address token;
uint256 amount;
uint64 wdseq; // only needed for LqWithdraw (refund)
uint64 srcChainId;
bytes32 refId;
bytes32 srcTxHash; // src chain msg tx hash
}
struct RouteInfo {
address sender;
address receiver;
uint64 srcChainId;
bytes32 srcTxHash; // src chain msg tx hash
}
// used for msg from non-evm chains with longer-bytes address
struct RouteInfo2 {
bytes sender;
address receiver;
uint64 srcChainId;
bytes32 srcTxHash;
}
// combination of RouteInfo and RouteInfo2 for easier processing
struct Route {
address sender; // from RouteInfo
bytes senderBytes; // from RouteInfo2
address receiver;
uint64 srcChainId;
bytes32 srcTxHash;
}
struct MsgWithTransferExecutionParams {
bytes message;
TransferInfo transfer;
bytes[] sigs;
address[] signers;
uint256[] powers;
}
struct BridgeTransferParams {
bytes request;
bytes[] sigs;
address[] signers;
uint256[] powers;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { LibDiamond } from "../Libraries/LibDiamond.sol";
/// @title Periphery Registry Facet
/// @author LI.FI (https://li.fi)
/// @notice A simple registry to track LIFI periphery contracts
/// @custom:version 1.0.0
contract PeripheryRegistryFacet {
/// Storage ///
bytes32 internal constant NAMESPACE =
keccak256("com.lifi.facets.periphery_registry");
/// Types ///
struct Storage {
mapping(string => address) contracts;
}
/// Events ///
event PeripheryContractRegistered(string name, address contractAddress);
/// External Methods ///
/// @notice Registers a periphery contract address with a specified name
/// @param _name the name to register the contract address under
/// @param _contractAddress the address of the contract to register
function registerPeripheryContract(
string calldata _name,
address _contractAddress
) external {
LibDiamond.enforceIsContractOwner();
Storage storage s = getStorage();
s.contracts[_name] = _contractAddress;
emit PeripheryContractRegistered(_name, _contractAddress);
}
/// @notice Returns the registered contract address by its name
/// @param _name the registered name of the contract
function getPeripheryContract(
string calldata _name
) external view returns (address) {
return getStorage().contracts[_name];
}
/// @dev fetch local storage
function getStorage() private pure returns (Storage storage s) {
bytes32 namespace = NAMESPACE;
// solhint-disable-next-line no-inline-assembly
assembly {
s.slot := namespace
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { LibSwap } from "../Libraries/LibSwap.sol";
/// @title Interface for Executor
/// @author LI.FI (https://li.fi)
/// @custom:version 1.0.0
interface IExecutor {
/// @notice Performs a swap before completing a cross-chain transaction
/// @param _transactionId the transaction id associated with the operation
/// @param _swapData array of data needed for swaps
/// @param transferredAssetId token received from the other chain
/// @param receiver address that will receive tokens in the end
function swapAndCompleteBridgeTokens(
bytes32 _transactionId,
LibSwap.SwapData[] calldata _swapData,
address transferredAssetId,
address payable receiver
) external payable;
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import { TransferrableOwnership } from "./TransferrableOwnership.sol";
import { LibAsset } from "../Libraries/LibAsset.sol";
import { ExternalCallFailed } from "../Errors/GenericErrors.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
abstract contract WithdrawablePeriphery is TransferrableOwnership {
using SafeTransferLib for address;
event TokensWithdrawn(
address assetId,
address payable receiver,
uint256 amount
);
constructor(address _owner) TransferrableOwnership(_owner) {}
function withdrawToken(
address assetId,
address payable receiver,
uint256 amount
) external onlyOwner {
if (LibAsset.isNativeAsset(assetId)) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = receiver.call{ value: amount }("");
if (!success) revert ExternalCallFailed();
} else {
assetId.safeTransfer(receiver, amount);
}
emit TokensWithdrawn(assetId, receiver, amount);
}
}// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
interface IMessageReceiverApp {
enum ExecutionStatus {
Fail, // execution failed, finalized
Success, // execution succeeded, finalized
Retry // execution rejected, can retry later
}
/**
* @notice Called by MessageBus to execute a message
* @param _sender The address of the source app contract
* @param _srcChainId The source chain ID where the transfer is originated from
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param _executor Address who called the MessageBus execution function
*/
function executeMessage(
address _sender,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
// same as above, except that sender is an non-evm chain address,
// otherwise same as above.
function executeMessage(
bytes calldata _sender,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
/**
* @notice Called by MessageBus to execute a message with an associated token transfer.
* The contract is guaranteed to have received the right amount of tokens before this function is called.
* @param _sender The address of the source app contract
* @param _token The address of the token that comes out of the bridge
* @param _amount The amount of tokens received at this contract through the cross-chain bridge.
* @param _srcChainId The source chain ID where the transfer is originated from
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param _executor Address who called the MessageBus execution function
*/
function executeMessageWithTransfer(
address _sender,
address _token,
uint256 _amount,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
/**
* @notice Only called by MessageBus if
* 1. executeMessageWithTransfer reverts, or
* 2. executeMessageWithTransfer returns ExecutionStatus.Fail
* The contract is guaranteed to have received the right amount of tokens before this function is called.
* @param _sender The address of the source app contract
* @param _token The address of the token that comes out of the bridge
* @param _amount The amount of tokens received at this contract through the cross-chain bridge.
* @param _srcChainId The source chain ID where the transfer is originated from
* @param _message Arbitrary message bytes originated from and encoded by the source app contract
* @param _executor Address who called the MessageBus execution function
*/
function executeMessageWithTransferFallback(
address _sender,
address _token,
uint256 _amount,
uint64 _srcChainId,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
/**
* @notice Called by MessageBus to process refund of the original transfer from this contract.
* The contract is guaranteed to have received the refund before this function is called.
* @param _token The token address of the original transfer
* @param _amount The amount of the original transfer
* @param _message The same message associated with the original transfer
* @param _executor Address who called the MessageBus execution function
*/
function executeMessageWithTransferRefund(
address _token,
uint256 _amount,
bytes calldata _message,
address _executor
) external payable returns (ExecutionStatus);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// import { IDiamondCut } from "../Interfaces/LibDiamond.sol";
import { LibDiamond } from "../Libraries/LibDiamond.sol";
import { LibUtil } from "../Libraries/LibUtil.sol";
import { OnlyContractOwner } from "../Errors/GenericErrors.sol";
/// Implementation of EIP-2535 Diamond Standard
/// https://eips.ethereum.org/EIPS/eip-2535
library LibDiamond {
bytes32 internal constant DIAMOND_STORAGE_POSITION =
keccak256("diamond.standard.diamond.storage");
// Diamond specific errors
error IncorrectFacetCutAction();
error NoSelectorsInFace();
error FunctionAlreadyExists();
error FacetAddressIsZero();
error FacetAddressIsNotZero();
error FacetContainsNoCode();
error FunctionDoesNotExist();
error FunctionIsImmutable();
error InitZeroButCalldataNotEmpty();
error CalldataEmptyButInitNotZero();
error InitReverted();
// ----------------
struct FacetAddressAndPosition {
address facetAddress;
uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
}
struct FacetFunctionSelectors {
bytes4[] functionSelectors;
uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
}
struct DiamondStorage {
// maps function selector to the facet address and
// the position of the selector in the facetFunctionSelectors.selectors array
mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
// maps facet addresses to function selectors
mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
// facet addresses
address[] facetAddresses;
// Used to query if a contract implements an interface.
// Used to implement ERC-165.
mapping(bytes4 => bool) supportedInterfaces;
// owner of the contract
address contractOwner;
}
enum FacetCutAction {
Add,
Replace,
Remove
}
// Add=0, Replace=1, Remove=2
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
function diamondStorage()
internal
pure
returns (DiamondStorage storage ds)
{
bytes32 position = DIAMOND_STORAGE_POSITION;
// solhint-disable-next-line no-inline-assembly
assembly {
ds.slot := position
}
}
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
function setContractOwner(address _newOwner) internal {
DiamondStorage storage ds = diamondStorage();
address previousOwner = ds.contractOwner;
ds.contractOwner = _newOwner;
emit OwnershipTransferred(previousOwner, _newOwner);
}
function contractOwner() internal view returns (address contractOwner_) {
contractOwner_ = diamondStorage().contractOwner;
}
function enforceIsContractOwner() internal view {
if (msg.sender != diamondStorage().contractOwner)
revert OnlyContractOwner();
}
// Internal function version of diamondCut
function diamondCut(
FacetCut[] memory _diamondCut,
address _init,
bytes memory _calldata
) internal {
for (uint256 facetIndex; facetIndex < _diamondCut.length; ) {
LibDiamond.FacetCutAction action = _diamondCut[facetIndex].action;
if (action == LibDiamond.FacetCutAction.Add) {
addFunctions(
_diamondCut[facetIndex].facetAddress,
_diamondCut[facetIndex].functionSelectors
);
} else if (action == LibDiamond.FacetCutAction.Replace) {
replaceFunctions(
_diamondCut[facetIndex].facetAddress,
_diamondCut[facetIndex].functionSelectors
);
} else if (action == LibDiamond.FacetCutAction.Remove) {
removeFunctions(
_diamondCut[facetIndex].facetAddress,
_diamondCut[facetIndex].functionSelectors
);
} else {
revert IncorrectFacetCutAction();
}
unchecked {
++facetIndex;
}
}
emit DiamondCut(_diamondCut, _init, _calldata);
initializeDiamondCut(_init, _calldata);
}
function addFunctions(
address _facetAddress,
bytes4[] memory _functionSelectors
) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsZero();
}
uint96 selectorPosition = uint96(
ds.facetFunctionSelectors[_facetAddress].functionSelectors.length
);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
for (
uint256 selectorIndex;
selectorIndex < _functionSelectors.length;
) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds
.selectorToFacetAndPosition[selector]
.facetAddress;
if (!LibUtil.isZeroAddress(oldFacetAddress)) {
revert FunctionAlreadyExists();
}
addFunction(ds, selector, selectorPosition, _facetAddress);
unchecked {
++selectorPosition;
++selectorIndex;
}
}
}
function replaceFunctions(
address _facetAddress,
bytes4[] memory _functionSelectors
) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsZero();
}
uint96 selectorPosition = uint96(
ds.facetFunctionSelectors[_facetAddress].functionSelectors.length
);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
for (
uint256 selectorIndex;
selectorIndex < _functionSelectors.length;
) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds
.selectorToFacetAndPosition[selector]
.facetAddress;
if (oldFacetAddress == _facetAddress) {
revert FunctionAlreadyExists();
}
removeFunction(ds, oldFacetAddress, selector);
addFunction(ds, selector, selectorPosition, _facetAddress);
unchecked {
++selectorPosition;
++selectorIndex;
}
}
}
function removeFunctions(
address _facetAddress,
bytes4[] memory _functionSelectors
) internal {
if (_functionSelectors.length == 0) {
revert NoSelectorsInFace();
}
DiamondStorage storage ds = diamondStorage();
// if function does not exist then do nothing and return
if (!LibUtil.isZeroAddress(_facetAddress)) {
revert FacetAddressIsNotZero();
}
for (
uint256 selectorIndex;
selectorIndex < _functionSelectors.length;
) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds
.selectorToFacetAndPosition[selector]
.facetAddress;
removeFunction(ds, oldFacetAddress, selector);
unchecked {
++selectorIndex;
}
}
}
function addFacet(
DiamondStorage storage ds,
address _facetAddress
) internal {
enforceHasContractCode(_facetAddress);
ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds
.facetAddresses
.length;
ds.facetAddresses.push(_facetAddress);
}
function addFunction(
DiamondStorage storage ds,
bytes4 _selector,
uint96 _selectorPosition,
address _facetAddress
) internal {
ds
.selectorToFacetAndPosition[_selector]
.functionSelectorPosition = _selectorPosition;
ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(
_selector
);
ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;
}
function removeFunction(
DiamondStorage storage ds,
address _facetAddress,
bytes4 _selector
) internal {
if (LibUtil.isZeroAddress(_facetAddress)) {
revert FunctionDoesNotExist();
}
// an immutable function is a function defined directly in a diamond
if (_facetAddress == address(this)) {
revert FunctionIsImmutable();
}
// replace selector with last selector, then delete last selector
uint256 selectorPosition = ds
.selectorToFacetAndPosition[_selector]
.functionSelectorPosition;
uint256 lastSelectorPosition = ds
.facetFunctionSelectors[_facetAddress]
.functionSelectors
.length - 1;
// if not the same then replace _selector with lastSelector
if (selectorPosition != lastSelectorPosition) {
bytes4 lastSelector = ds
.facetFunctionSelectors[_facetAddress]
.functionSelectors[lastSelectorPosition];
ds.facetFunctionSelectors[_facetAddress].functionSelectors[
selectorPosition
] = lastSelector;
ds
.selectorToFacetAndPosition[lastSelector]
.functionSelectorPosition = uint96(selectorPosition);
}
// delete the last selector
ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();
delete ds.selectorToFacetAndPosition[_selector];
// if no more selectors for facet address then delete the facet address
if (lastSelectorPosition == 0) {
// replace facet address with last facet address and delete last facet address
uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;
uint256 facetAddressPosition = ds
.facetFunctionSelectors[_facetAddress]
.facetAddressPosition;
if (facetAddressPosition != lastFacetAddressPosition) {
address lastFacetAddress = ds.facetAddresses[
lastFacetAddressPosition
];
ds.facetAddresses[facetAddressPosition] = lastFacetAddress;
ds
.facetFunctionSelectors[lastFacetAddress]
.facetAddressPosition = facetAddressPosition;
}
ds.facetAddresses.pop();
delete ds
.facetFunctionSelectors[_facetAddress]
.facetAddressPosition;
}
}
function initializeDiamondCut(
address _init,
bytes memory _calldata
) internal {
if (LibUtil.isZeroAddress(_init)) {
if (_calldata.length != 0) {
revert InitZeroButCalldataNotEmpty();
}
} else {
if (_calldata.length == 0) {
revert CalldataEmptyButInitNotZero();
}
if (_init != address(this)) {
enforceHasContractCode(_init);
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory error) = _init.delegatecall(_calldata);
if (!success) {
if (error.length > 0) {
// bubble up the error
revert(string(error));
} else {
revert InitReverted();
}
}
}
}
function enforceHasContractCode(address _contract) internal view {
uint256 contractSize;
// solhint-disable-next-line no-inline-assembly
assembly {
contractSize := extcodesize(_contract)
}
if (contractSize == 0) {
revert FacetContainsNoCode();
}
}
}// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity ^0.8.17;
import { IERC173 } from "../Interfaces/IERC173.sol";
import { LibAsset } from "../Libraries/LibAsset.sol";
contract TransferrableOwnership is IERC173 {
address public owner;
address public pendingOwner;
/// Errors ///
error UnAuthorized();
error NoNullOwner();
error NewOwnerMustNotBeSelf();
error NoPendingOwnershipTransfer();
error NotPendingOwner();
/// Events ///
event OwnershipTransferRequested(
address indexed _from,
address indexed _to
);
constructor(address initialOwner) {
owner = initialOwner;
}
modifier onlyOwner() {
if (msg.sender != owner) revert UnAuthorized();
_;
}
/// @notice Initiates transfer of ownership to a new address
/// @param _newOwner the address to transfer ownership to
function transferOwnership(address _newOwner) external onlyOwner {
if (_newOwner == LibAsset.NULL_ADDRESS) revert NoNullOwner();
if (_newOwner == msg.sender) revert NewOwnerMustNotBeSelf();
pendingOwner = _newOwner;
emit OwnershipTransferRequested(msg.sender, pendingOwner);
}
/// @notice Cancel transfer of ownership
function cancelOwnershipTransfer() external onlyOwner {
if (pendingOwner == LibAsset.NULL_ADDRESS)
revert NoPendingOwnershipTransfer();
pendingOwner = LibAsset.NULL_ADDRESS;
}
/// @notice Confirms transfer of ownership to the calling address (msg.sender)
function confirmOwnershipTransfer() external {
address _pendingOwner = pendingOwner;
if (msg.sender != _pendingOwner) revert NotPendingOwner();
emit OwnershipTransferred(owner, _pendingOwner);
owner = _pendingOwner;
pendingOwner = LibAsset.NULL_ADDRESS;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/// @title Interface for ERC-173 (Contract Ownership Standard)
/// @author LI.FI (https://li.fi)
/// Note: the ERC-165 identifier for this interface is 0x7f5828d0
/// @custom:version 1.0.0
interface IERC173 {
/// @dev This emits when ownership of a contract changes.
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/// @notice Get the address of the owner
/// @return owner_ The address of the owner.
function owner() external view returns (address owner_);
/// @notice Set the address of the new owner of the contract
/// @dev Set _newOwner to address(0) to renounce any ownership.
/// @param _newOwner The address of the new owner of the contract
function transferOwnership(address _newOwner) external;
}{
"remappings": [
"@eth-optimism/=node_modules/@hop-protocol/sdk/node_modules/@eth-optimism/",
"@uniswap/=node_modules/@uniswap/",
"eth-gas-reporter/=node_modules/eth-gas-reporter/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"celer-network/=lib/sgn-v2-contracts/",
"create3-factory/=lib/create3-factory/src/",
"solmate/=lib/solmate/src/",
"solady/=lib/solady/src/",
"permit2/=lib/Permit2/src/",
"ds-test/=lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"lifi/=src/",
"test/=test/",
"Permit2/=lib/Permit2/",
"erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
"forge-gas-snapshot/=lib/Permit2/lib/forge-gas-snapshot/src/",
"hardhat/=node_modules/hardhat/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"openzeppelin/=lib/openzeppelin-contracts/contracts/",
"sgn-v2-contracts/=lib/sgn-v2-contracts/contracts/"
],
"optimizer": {
"enabled": true,
"runs": 1000000
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": false,
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[],"name":"InvalidCallData","type":"error"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"extractBridgeData","outputs":[{"components":[{"internalType":"bytes32","name":"transactionId","type":"bytes32"},{"internalType":"string","name":"bridge","type":"string"},{"internalType":"string","name":"integrator","type":"string"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"address","name":"sendingAssetId","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"minAmount","type":"uint256"},{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"bool","name":"hasSourceSwaps","type":"bool"},{"internalType":"bool","name":"hasDestinationCall","type":"bool"}],"internalType":"struct ILiFi.BridgeData","name":"bridgeData","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"extractData","outputs":[{"components":[{"internalType":"bytes32","name":"transactionId","type":"bytes32"},{"internalType":"string","name":"bridge","type":"string"},{"internalType":"string","name":"integrator","type":"string"},{"internalType":"address","name":"referrer","type":"address"},{"internalType":"address","name":"sendingAssetId","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"minAmount","type":"uint256"},{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"bool","name":"hasSourceSwaps","type":"bool"},{"internalType":"bool","name":"hasDestinationCall","type":"bool"}],"internalType":"struct ILiFi.BridgeData","name":"bridgeData","type":"tuple"},{"components":[{"internalType":"address","name":"callTo","type":"address"},{"internalType":"address","name":"approveTo","type":"address"},{"internalType":"address","name":"sendingAssetId","type":"address"},{"internalType":"address","name":"receivingAssetId","type":"address"},{"internalType":"uint256","name":"fromAmount","type":"uint256"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bool","name":"requiresDeposit","type":"bool"}],"internalType":"struct LibSwap.SwapData[]","name":"swapData","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"extractGenericSwapParameters","outputs":[{"internalType":"address","name":"sendingAssetId","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"receivingAssetId","type":"address"},{"internalType":"uint256","name":"receivingAmount","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"extractMainParameters","outputs":[{"internalType":"string","name":"bridge","type":"string"},{"internalType":"address","name":"sendingAssetId","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"bool","name":"hasSourceSwaps","type":"bool"},{"internalType":"bool","name":"hasDestinationCall","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"extractNonEVMAddress","outputs":[{"internalType":"bytes32","name":"nonEVMAddress","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"extractSwapData","outputs":[{"components":[{"internalType":"address","name":"callTo","type":"address"},{"internalType":"address","name":"approveTo","type":"address"},{"internalType":"address","name":"sendingAssetId","type":"address"},{"internalType":"address","name":"receivingAssetId","type":"address"},{"internalType":"uint256","name":"fromAmount","type":"uint256"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bool","name":"requiresDeposit","type":"bool"}],"internalType":"struct LibSwap.SwapData[]","name":"swapData","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"bridge","type":"string"},{"internalType":"address","name":"sendingAssetId","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"bool","name":"hasSourceSwaps","type":"bool"},{"internalType":"bool","name":"hasDestinationCall","type":"bool"}],"name":"validateCalldata","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"callTo","type":"bytes"},{"internalType":"bytes","name":"dstCalldata","type":"bytes"}],"name":"validateDestinationCalldata","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"pure","type":"function"}]Contract Creation Code
6080604052348015600e575f5ffd5b506121b48061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610085575f3560e01c8063d53482cf11610058578063d53482cf1461014e578063df1c3a5b14610171578063ee0aa32014610192578063f58ae2ce146101b8575f5ffd5b8063070e81f114610089578063103c5200146100b25780637f99d7af146100d3578063c318eeda146100f3575b5f5ffd5b61009c610097366004610fc0565b6101cb565b6040516100a99190611177565b60405180910390f35b6100c56100c0366004610fc0565b6101de565b6040516100a9929190611284565b6100e66100e1366004610fc0565b61029f565b6040516100a991906112b1565b610106610101366004610fc0565b61033d565b6040805173ffffffffffffffffffffffffffffffffffffffff96871681526020810195909552928516928401929092529092166060820152608081019190915260a0016100a9565b61016161015c366004611306565b6105dd565b60405190151581526020016100a9565b61018461017f366004610fc0565b6107d9565b6040519081526020016100a9565b6101a56101a0366004610fc0565b610851565b6040516100a997969594939291906113cc565b6101616101c6366004611429565b610905565b60606101d78383610df1565b9392505050565b6102726040518061014001604052805f815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581525090565b606061027e8484610e15565b915081610100015115610298576102958484610df1565b90505b9250929050565b6103336040518061014001604052805f815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581525090565b6101d78383610e15565b5f808080806101e4861161037d576040517f1c49f4d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60605f61038d6004828a8c6114c8565b610396916114ef565b90507fffffffff0000000000000000000000000000000000000000000000000000000081167f4666fc8000000000000000000000000000000000000000000000000000000000148061042957507fffffffff0000000000000000000000000000000000000000000000000000000081167f733214a300000000000000000000000000000000000000000000000000000000145b8061047557507fffffffff0000000000000000000000000000000000000000000000000000000081167faf7060fd00000000000000000000000000000000000000000000000000000000145b1561053f5760408051600180825281830190925290816020015b6040805160e0810182525f808252602080830182905292820181905260608083018290526080830182905260a083015260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161048f579050509150610503886004818c6114c8565b8101906105109190611797565b8751929550909350915085905f9061052a5761052a611849565b60209081029190910101529095509250610564565b61054c886004818c6114c8565b8101906105599190611910565b919950965094505050505b815f8151811061057657610576611849565b6020026020010151604001519650815f8151811061059657610596611849565b602002602001015160800151955081600183516105b391906119b5565b815181106105c3576105c3611849565b602002602001015160600151935050509295509295909350565b5f5f6105e98c8c610e15565b90505f8a8a6040516020016105ff9291906119ed565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201205f8452908301918290529151902090915081148061067c575080826020015160405160200161066491906119fc565b60405160208183030381529060405280519060200120145b80156106d6575073ffffffffffffffffffffffffffffffffffffffff808a1614806106d657508873ffffffffffffffffffffffffffffffffffffffff16826080015173ffffffffffffffffffffffffffffffffffffffff16145b8015610730575073ffffffffffffffffffffffffffffffffffffffff808916148061073057508773ffffffffffffffffffffffffffffffffffffffff168260a0015173ffffffffffffffffffffffffffffffffffffffff16145b801561076857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8714806107685750868260c00151145b80156107a057507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8614806107a05750858260e00151145b80156107b457508415158261010001511515145b80156107c857508315158261012001511515145b9d9c50505050505050505050505050565b5f5f83838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152509293506108209250869150859050610e15565b61010001511561083c576064810151810160240151915061084a565b604481015181016024015191505b5092915050565b60605f5f5f5f5f5f5f6108648a8a610e15565b9050806101000151156108c4575f61087c8b8b610df1565b9050805f8151811061089057610890611849565b6020026020010151604001519750805f815181106108b0576108b0611849565b6020026020010151608001519550506108d3565b806080015196508060c0015194505b602081015160a082015160e083015161010084015161012090940151929d999c50909a50959850949690955092505050565b5f80610914600482898b6114c8565b61091d916114ef565b90507feb2acf89000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610a04575f610976886004818c6114c8565b8101906109839190611c92565b915050806020015160a001518051906020012085856040516109a69291906119ed565b60405180910390201480156109fb57506109fb87878080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505050506020838101510151610ec3565b92505050610de7565b7f59fef59a000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610a8c575f610a5b886004818c6114c8565b810190610a689190611cf7565b92505050806020015160a001518051906020012085856040516109a69291906119ed565b7ffaf6a213000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610b4d575f610ae3886004818c6114c8565b810190610af09190611e57565b9150508060600151805190602001208585604051610b0f9291906119ed565b60405180910390201480156109fb57508060400151805190602001208787604051610b3b9291906119ed565b60405180910390201492505050610de7565b7f4f93ad26000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610bd1575f610ba4886004818c6114c8565b810190610bb19190611eb2565b925050508060600151805190602001208585604051610b0f9291906119ed565b7fd733bcea000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610caf575f610c28886004818c6114c8565b810190610c359190612012565b915050806101200151805190602001208585604051610c559291906119ed565b60405180910390201480156109fb575080516040805173ffffffffffffffffffffffffffffffffffffffff909216602083015201604051602081830303815290604052805190602001208787604051610b3b9291906119ed565b7fd77cd343000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610d34575f610d06886004818c6114c8565b810190610d13919061206d565b92505050806101200151805190602001208585604051610c559291906119ed565b7fd733bcea000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610d8b575f610c28886004818c6114c8565b7fd77cd343000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610de2575f610d06886004818c6114c8565b5f9150505b9695505050505050565b6060610e0082600481866114c8565b810190610e0d91906120f1565b949350505050565b610ea96040518061014001604052805f815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581525090565b610eb682600481866114c8565b8101906101d7919061214c565b5f601483511015610f5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603160248201527f496e76616c69642063616c6c546f206c656e6774683b2065787065637465642060448201527f6174206c65617374203230206279746573000000000000000000000000000000606482015260840160405180910390fd5b50602082015173ffffffffffffffffffffffffffffffffffffffff8281169116145b92915050565b5f5f83601f840112610f92575f5ffd5b50813567ffffffffffffffff811115610fa9575f5ffd5b602083019150836020828501011115610298575f5ffd5b5f5f60208385031215610fd1575f5ffd5b823567ffffffffffffffff811115610fe7575f5ffd5b610ff385828601610f82565b90969095509350505050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b5f82825180855260208501945060208160051b830101602085015f5b8381101561116b577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0858403018852815173ffffffffffffffffffffffffffffffffffffffff815116845273ffffffffffffffffffffffffffffffffffffffff602082015116602085015273ffffffffffffffffffffffffffffffffffffffff60408201511660408501526060810151611119606086018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151608085015260a081015160e060a086015261113d60e0860182610fff565b905060c0820151915061115460c086018315159052565b6020998a0199909450929092019150600101611067565b50909695505050505050565b602081525f6101d7602083018461104b565b805182525f602082015161014060208501526111a9610140850182610fff565b9050604083015184820360408601526111c28282610fff565b91505060608301516111ec606086018273ffffffffffffffffffffffffffffffffffffffff169052565b506080830151611214608086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a083015161123c60a086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c083015160c085015260e083015160e085015261010083015161126661010086018215159052565b5061012083015161127c61012086018215159052565b509392505050565b604081525f6112966040830185611189565b82810360208401526112a8818561104b565b95945050505050565b602081525f6101d76020830184611189565b73ffffffffffffffffffffffffffffffffffffffff811681146112e4575f5ffd5b50565b80356112f2816112c3565b919050565b803580151581146112f2575f5ffd5b5f5f5f5f5f5f5f5f5f5f6101008b8d031215611320575f5ffd5b8a3567ffffffffffffffff811115611336575f5ffd5b6113428d828e01610f82565b909b5099505060208b013567ffffffffffffffff811115611361575f5ffd5b61136d8d828e01610f82565b90995097505060408b0135611381816112c3565b955060608b0135611391816112c3565b945060808b0135935060a08b013592506113ad60c08c016112f7565b91506113bb60e08c016112f7565b90509295989b9194979a5092959850565b60e081525f6113de60e083018a610fff565b73ffffffffffffffffffffffffffffffffffffffff988916602084015296909716604082015260608101949094526080840192909252151560a0830152151560c09091015292915050565b5f5f5f5f5f5f6060878903121561143e575f5ffd5b863567ffffffffffffffff811115611454575f5ffd5b61146089828a01610f82565b909750955050602087013567ffffffffffffffff81111561147f575f5ffd5b61148b89828a01610f82565b909550935050604087013567ffffffffffffffff8111156114aa575f5ffd5b6114b689828a01610f82565b979a9699509497509295939492505050565b5f5f858511156114d6575f5ffd5b838611156114e2575f5ffd5b5050820193919092039150565b80357fffffffff00000000000000000000000000000000000000000000000000000000811690600484101561084a577fffffffff00000000000000000000000000000000000000000000000000000000808560040360031b1b82161691505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60405160e0810167ffffffffffffffff811182821017156115a4576115a4611554565b60405290565b604051610140810167ffffffffffffffff811182821017156115a4576115a4611554565b6040516080810167ffffffffffffffff811182821017156115a4576115a4611554565b60405160c0810167ffffffffffffffff811182821017156115a4576115a4611554565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561165b5761165b611554565b604052919050565b5f82601f830112611672575f5ffd5b8135602083015f5f67ffffffffffffffff84111561169257611692611554565b50601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0166020016116c581611614565b9150508281528583830111156116d9575f5ffd5b828260208301375f92810160200192909252509392505050565b5f60e08284031215611703575f5ffd5b61170b611581565b9050611716826112e7565b8152611724602083016112e7565b6020820152611735604083016112e7565b6040820152611746606083016112e7565b60608201526080828101359082015260a082013567ffffffffffffffff81111561176e575f5ffd5b61177a84828501611663565b60a08301525061178c60c083016112f7565b60c082015292915050565b5f5f5f5f5f5f60c087890312156117ac575f5ffd5b86359550602087013567ffffffffffffffff8111156117c9575f5ffd5b6117d589828a01611663565b955050604087013567ffffffffffffffff8111156117f1575f5ffd5b6117fd89828a01611663565b945050606087013561180e816112c3565b92506080870135915060a087013567ffffffffffffffff811115611830575f5ffd5b61183c89828a016116f3565b9150509295509295509295565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f82601f830112611885575f5ffd5b813567ffffffffffffffff81111561189f5761189f611554565b8060051b6118af60208201611614565b918252602081850181019290810190868411156118ca575f5ffd5b6020860192505b83831015610de757823567ffffffffffffffff8111156118ef575f5ffd5b6118fe886020838a01016116f3565b835250602092830192909101906118d1565b5f5f5f5f5f5f60c08789031215611925575f5ffd5b86359550602087013567ffffffffffffffff811115611942575f5ffd5b61194e89828a01611663565b955050604087013567ffffffffffffffff81111561196a575f5ffd5b61197689828a01611663565b9450506060870135611987816112c3565b92506080870135915060a087013567ffffffffffffffff8111156119a9575f5ffd5b61183c89828a01611876565b81810381811115610f7c577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818382375f9101908152919050565b5f82518060208501845e5f920191825250919050565b5f6101408284031215611a23575f5ffd5b611a2b6115aa565b823581529050602082013567ffffffffffffffff811115611a4a575f5ffd5b611a5684828501611663565b602083015250604082013567ffffffffffffffff811115611a75575f5ffd5b611a8184828501611663565b604083015250611a93606083016112e7565b6060820152611aa4608083016112e7565b6080820152611ab560a083016112e7565b60a082015260c0828101359082015260e08083013590820152611adb61010083016112f7565b610100820152611aee61012083016112f7565b61012082015292915050565b803563ffffffff811681146112f2575f5ffd5b5f60408284031215611b1d575f5ffd5b6040805190810167ffffffffffffffff81118282101715611b4057611b40611554565b604052823581526020928301359281019290925250919050565b5f60a08284031215611b6a575f5ffd5b611b726115ce565b9050813561ffff81168114611b85575f5ffd5b8152602082013567ffffffffffffffff811115611ba0575f5ffd5b820160e08185031215611bb1575f5ffd5b611bb9611581565b611bc282611afa565b8152602082810135908201526040808301359082015260608083013590820152608082013567ffffffffffffffff811115611bfb575f5ffd5b611c0786828501611663565b60808301525060a082013567ffffffffffffffff811115611c26575f5ffd5b611c3286828501611663565b60a08301525060c082013567ffffffffffffffff811115611c51575f5ffd5b611c5d86828501611663565b60c083015250602083015250611c768360408401611b0d565b6040820152611c87608083016112e7565b606082015292915050565b5f5f60408385031215611ca3575f5ffd5b823567ffffffffffffffff811115611cb9575f5ffd5b611cc585828601611a12565b925050602083013567ffffffffffffffff811115611ce1575f5ffd5b611ced85828601611b5a565b9150509250929050565b5f5f5f60608486031215611d09575f5ffd5b833567ffffffffffffffff811115611d1f575f5ffd5b611d2b86828701611a12565b935050602084013567ffffffffffffffff811115611d47575f5ffd5b611d5386828701611876565b925050604084013567ffffffffffffffff811115611d6f575f5ffd5b611d7b86828701611b5a565b9150509250925092565b803567ffffffffffffffff811681146112f2575f5ffd5b8035600781106112f2575f5ffd5b5f60c08284031215611dba575f5ffd5b611dc26115f1565b9050611dcd82611afa565b8152611ddb60208301611d85565b6020820152604082013567ffffffffffffffff811115611df9575f5ffd5b611e0584828501611663565b604083015250606082013567ffffffffffffffff811115611e24575f5ffd5b611e3084828501611663565b60608301525060808281013590820152611e4c60a08301611d9c565b60a082015292915050565b5f5f60408385031215611e68575f5ffd5b823567ffffffffffffffff811115611e7e575f5ffd5b611e8a85828601611a12565b925050602083013567ffffffffffffffff811115611ea6575f5ffd5b611ced85828601611daa565b5f5f5f60608486031215611ec4575f5ffd5b833567ffffffffffffffff811115611eda575f5ffd5b611ee686828701611a12565b935050602084013567ffffffffffffffff811115611f02575f5ffd5b611f0e86828701611876565b925050604084013567ffffffffffffffff811115611f2a575f5ffd5b611d7b86828701611daa565b5f6101408284031215611f47575f5ffd5b611f4f6115aa565b9050611f5a826112e7565b8152611f68602083016112e7565b6020820152611f79604083016112e7565b604082015260608281013590820152611f9460808301611d85565b6080820152611fa560a083016112e7565b60a0820152611fb660c08301611afa565b60c0820152611fc760e08301611afa565b60e0820152611fd96101008301611afa565b61010082015261012082013567ffffffffffffffff811115611ff9575f5ffd5b61200584828501611663565b6101208301525092915050565b5f5f60408385031215612023575f5ffd5b823567ffffffffffffffff811115612039575f5ffd5b61204585828601611a12565b925050602083013567ffffffffffffffff811115612061575f5ffd5b611ced85828601611f36565b5f5f5f6060848603121561207f575f5ffd5b833567ffffffffffffffff811115612095575f5ffd5b6120a186828701611a12565b935050602084013567ffffffffffffffff8111156120bd575f5ffd5b6120c986828701611876565b925050604084013567ffffffffffffffff8111156120e5575f5ffd5b611d7b86828701611f36565b5f5f60408385031215612102575f5ffd5b823567ffffffffffffffff811115612118575f5ffd5b61212485828601611a12565b925050602083013567ffffffffffffffff811115612140575f5ffd5b611ced85828601611876565b5f6020828403121561215c575f5ffd5b813567ffffffffffffffff811115612172575f5ffd5b610e0d84828501611a1256fea264697066735822122090d7f30edbb2df9425b9f9bc213c05855b0c0f44de3603f23db30d02dfc7544f64736f6c634300081c0033
Deployed Bytecode
0x608060405234801561000f575f5ffd5b5060043610610085575f3560e01c8063d53482cf11610058578063d53482cf1461014e578063df1c3a5b14610171578063ee0aa32014610192578063f58ae2ce146101b8575f5ffd5b8063070e81f114610089578063103c5200146100b25780637f99d7af146100d3578063c318eeda146100f3575b5f5ffd5b61009c610097366004610fc0565b6101cb565b6040516100a99190611177565b60405180910390f35b6100c56100c0366004610fc0565b6101de565b6040516100a9929190611284565b6100e66100e1366004610fc0565b61029f565b6040516100a991906112b1565b610106610101366004610fc0565b61033d565b6040805173ffffffffffffffffffffffffffffffffffffffff96871681526020810195909552928516928401929092529092166060820152608081019190915260a0016100a9565b61016161015c366004611306565b6105dd565b60405190151581526020016100a9565b61018461017f366004610fc0565b6107d9565b6040519081526020016100a9565b6101a56101a0366004610fc0565b610851565b6040516100a997969594939291906113cc565b6101616101c6366004611429565b610905565b60606101d78383610df1565b9392505050565b6102726040518061014001604052805f815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581525090565b606061027e8484610e15565b915081610100015115610298576102958484610df1565b90505b9250929050565b6103336040518061014001604052805f815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581525090565b6101d78383610e15565b5f808080806101e4861161037d576040517f1c49f4d100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60605f61038d6004828a8c6114c8565b610396916114ef565b90507fffffffff0000000000000000000000000000000000000000000000000000000081167f4666fc8000000000000000000000000000000000000000000000000000000000148061042957507fffffffff0000000000000000000000000000000000000000000000000000000081167f733214a300000000000000000000000000000000000000000000000000000000145b8061047557507fffffffff0000000000000000000000000000000000000000000000000000000081167faf7060fd00000000000000000000000000000000000000000000000000000000145b1561053f5760408051600180825281830190925290816020015b6040805160e0810182525f808252602080830182905292820181905260608083018290526080830182905260a083015260c082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161048f579050509150610503886004818c6114c8565b8101906105109190611797565b8751929550909350915085905f9061052a5761052a611849565b60209081029190910101529095509250610564565b61054c886004818c6114c8565b8101906105599190611910565b919950965094505050505b815f8151811061057657610576611849565b6020026020010151604001519650815f8151811061059657610596611849565b602002602001015160800151955081600183516105b391906119b5565b815181106105c3576105c3611849565b602002602001015160600151935050509295509295909350565b5f5f6105e98c8c610e15565b90505f8a8a6040516020016105ff9291906119ed565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815282825280516020918201205f8452908301918290529151902090915081148061067c575080826020015160405160200161066491906119fc565b60405160208183030381529060405280519060200120145b80156106d6575073ffffffffffffffffffffffffffffffffffffffff808a1614806106d657508873ffffffffffffffffffffffffffffffffffffffff16826080015173ffffffffffffffffffffffffffffffffffffffff16145b8015610730575073ffffffffffffffffffffffffffffffffffffffff808916148061073057508773ffffffffffffffffffffffffffffffffffffffff168260a0015173ffffffffffffffffffffffffffffffffffffffff16145b801561076857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8714806107685750868260c00151145b80156107a057507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8614806107a05750858260e00151145b80156107b457508415158261010001511515145b80156107c857508315158261012001511515145b9d9c50505050505050505050505050565b5f5f83838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152509293506108209250869150859050610e15565b61010001511561083c576064810151810160240151915061084a565b604481015181016024015191505b5092915050565b60605f5f5f5f5f5f5f6108648a8a610e15565b9050806101000151156108c4575f61087c8b8b610df1565b9050805f8151811061089057610890611849565b6020026020010151604001519750805f815181106108b0576108b0611849565b6020026020010151608001519550506108d3565b806080015196508060c0015194505b602081015160a082015160e083015161010084015161012090940151929d999c50909a50959850949690955092505050565b5f80610914600482898b6114c8565b61091d916114ef565b90507feb2acf89000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610a04575f610976886004818c6114c8565b8101906109839190611c92565b915050806020015160a001518051906020012085856040516109a69291906119ed565b60405180910390201480156109fb57506109fb87878080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152505050506020838101510151610ec3565b92505050610de7565b7f59fef59a000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610a8c575f610a5b886004818c6114c8565b810190610a689190611cf7565b92505050806020015160a001518051906020012085856040516109a69291906119ed565b7ffaf6a213000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610b4d575f610ae3886004818c6114c8565b810190610af09190611e57565b9150508060600151805190602001208585604051610b0f9291906119ed565b60405180910390201480156109fb57508060400151805190602001208787604051610b3b9291906119ed565b60405180910390201492505050610de7565b7f4f93ad26000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610bd1575f610ba4886004818c6114c8565b810190610bb19190611eb2565b925050508060600151805190602001208585604051610b0f9291906119ed565b7fd733bcea000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610caf575f610c28886004818c6114c8565b810190610c359190612012565b915050806101200151805190602001208585604051610c559291906119ed565b60405180910390201480156109fb575080516040805173ffffffffffffffffffffffffffffffffffffffff909216602083015201604051602081830303815290604052805190602001208787604051610b3b9291906119ed565b7fd77cd343000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610d34575f610d06886004818c6114c8565b810190610d13919061206d565b92505050806101200151805190602001208585604051610c559291906119ed565b7fd733bcea000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610d8b575f610c28886004818c6114c8565b7fd77cd343000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000821601610de2575f610d06886004818c6114c8565b5f9150505b9695505050505050565b6060610e0082600481866114c8565b810190610e0d91906120f1565b949350505050565b610ea96040518061014001604052805f815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581525090565b610eb682600481866114c8565b8101906101d7919061214c565b5f601483511015610f5a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603160248201527f496e76616c69642063616c6c546f206c656e6774683b2065787065637465642060448201527f6174206c65617374203230206279746573000000000000000000000000000000606482015260840160405180910390fd5b50602082015173ffffffffffffffffffffffffffffffffffffffff8281169116145b92915050565b5f5f83601f840112610f92575f5ffd5b50813567ffffffffffffffff811115610fa9575f5ffd5b602083019150836020828501011115610298575f5ffd5b5f5f60208385031215610fd1575f5ffd5b823567ffffffffffffffff811115610fe7575f5ffd5b610ff385828601610f82565b90969095509350505050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b5f82825180855260208501945060208160051b830101602085015f5b8381101561116b577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0858403018852815173ffffffffffffffffffffffffffffffffffffffff815116845273ffffffffffffffffffffffffffffffffffffffff602082015116602085015273ffffffffffffffffffffffffffffffffffffffff60408201511660408501526060810151611119606086018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151608085015260a081015160e060a086015261113d60e0860182610fff565b905060c0820151915061115460c086018315159052565b6020998a0199909450929092019150600101611067565b50909695505050505050565b602081525f6101d7602083018461104b565b805182525f602082015161014060208501526111a9610140850182610fff565b9050604083015184820360408601526111c28282610fff565b91505060608301516111ec606086018273ffffffffffffffffffffffffffffffffffffffff169052565b506080830151611214608086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a083015161123c60a086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c083015160c085015260e083015160e085015261010083015161126661010086018215159052565b5061012083015161127c61012086018215159052565b509392505050565b604081525f6112966040830185611189565b82810360208401526112a8818561104b565b95945050505050565b602081525f6101d76020830184611189565b73ffffffffffffffffffffffffffffffffffffffff811681146112e4575f5ffd5b50565b80356112f2816112c3565b919050565b803580151581146112f2575f5ffd5b5f5f5f5f5f5f5f5f5f5f6101008b8d031215611320575f5ffd5b8a3567ffffffffffffffff811115611336575f5ffd5b6113428d828e01610f82565b909b5099505060208b013567ffffffffffffffff811115611361575f5ffd5b61136d8d828e01610f82565b90995097505060408b0135611381816112c3565b955060608b0135611391816112c3565b945060808b0135935060a08b013592506113ad60c08c016112f7565b91506113bb60e08c016112f7565b90509295989b9194979a5092959850565b60e081525f6113de60e083018a610fff565b73ffffffffffffffffffffffffffffffffffffffff988916602084015296909716604082015260608101949094526080840192909252151560a0830152151560c09091015292915050565b5f5f5f5f5f5f6060878903121561143e575f5ffd5b863567ffffffffffffffff811115611454575f5ffd5b61146089828a01610f82565b909750955050602087013567ffffffffffffffff81111561147f575f5ffd5b61148b89828a01610f82565b909550935050604087013567ffffffffffffffff8111156114aa575f5ffd5b6114b689828a01610f82565b979a9699509497509295939492505050565b5f5f858511156114d6575f5ffd5b838611156114e2575f5ffd5b5050820193919092039150565b80357fffffffff00000000000000000000000000000000000000000000000000000000811690600484101561084a577fffffffff00000000000000000000000000000000000000000000000000000000808560040360031b1b82161691505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60405160e0810167ffffffffffffffff811182821017156115a4576115a4611554565b60405290565b604051610140810167ffffffffffffffff811182821017156115a4576115a4611554565b6040516080810167ffffffffffffffff811182821017156115a4576115a4611554565b60405160c0810167ffffffffffffffff811182821017156115a4576115a4611554565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561165b5761165b611554565b604052919050565b5f82601f830112611672575f5ffd5b8135602083015f5f67ffffffffffffffff84111561169257611692611554565b50601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0166020016116c581611614565b9150508281528583830111156116d9575f5ffd5b828260208301375f92810160200192909252509392505050565b5f60e08284031215611703575f5ffd5b61170b611581565b9050611716826112e7565b8152611724602083016112e7565b6020820152611735604083016112e7565b6040820152611746606083016112e7565b60608201526080828101359082015260a082013567ffffffffffffffff81111561176e575f5ffd5b61177a84828501611663565b60a08301525061178c60c083016112f7565b60c082015292915050565b5f5f5f5f5f5f60c087890312156117ac575f5ffd5b86359550602087013567ffffffffffffffff8111156117c9575f5ffd5b6117d589828a01611663565b955050604087013567ffffffffffffffff8111156117f1575f5ffd5b6117fd89828a01611663565b945050606087013561180e816112c3565b92506080870135915060a087013567ffffffffffffffff811115611830575f5ffd5b61183c89828a016116f3565b9150509295509295509295565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f82601f830112611885575f5ffd5b813567ffffffffffffffff81111561189f5761189f611554565b8060051b6118af60208201611614565b918252602081850181019290810190868411156118ca575f5ffd5b6020860192505b83831015610de757823567ffffffffffffffff8111156118ef575f5ffd5b6118fe886020838a01016116f3565b835250602092830192909101906118d1565b5f5f5f5f5f5f60c08789031215611925575f5ffd5b86359550602087013567ffffffffffffffff811115611942575f5ffd5b61194e89828a01611663565b955050604087013567ffffffffffffffff81111561196a575f5ffd5b61197689828a01611663565b9450506060870135611987816112c3565b92506080870135915060a087013567ffffffffffffffff8111156119a9575f5ffd5b61183c89828a01611876565b81810381811115610f7c577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818382375f9101908152919050565b5f82518060208501845e5f920191825250919050565b5f6101408284031215611a23575f5ffd5b611a2b6115aa565b823581529050602082013567ffffffffffffffff811115611a4a575f5ffd5b611a5684828501611663565b602083015250604082013567ffffffffffffffff811115611a75575f5ffd5b611a8184828501611663565b604083015250611a93606083016112e7565b6060820152611aa4608083016112e7565b6080820152611ab560a083016112e7565b60a082015260c0828101359082015260e08083013590820152611adb61010083016112f7565b610100820152611aee61012083016112f7565b61012082015292915050565b803563ffffffff811681146112f2575f5ffd5b5f60408284031215611b1d575f5ffd5b6040805190810167ffffffffffffffff81118282101715611b4057611b40611554565b604052823581526020928301359281019290925250919050565b5f60a08284031215611b6a575f5ffd5b611b726115ce565b9050813561ffff81168114611b85575f5ffd5b8152602082013567ffffffffffffffff811115611ba0575f5ffd5b820160e08185031215611bb1575f5ffd5b611bb9611581565b611bc282611afa565b8152602082810135908201526040808301359082015260608083013590820152608082013567ffffffffffffffff811115611bfb575f5ffd5b611c0786828501611663565b60808301525060a082013567ffffffffffffffff811115611c26575f5ffd5b611c3286828501611663565b60a08301525060c082013567ffffffffffffffff811115611c51575f5ffd5b611c5d86828501611663565b60c083015250602083015250611c768360408401611b0d565b6040820152611c87608083016112e7565b606082015292915050565b5f5f60408385031215611ca3575f5ffd5b823567ffffffffffffffff811115611cb9575f5ffd5b611cc585828601611a12565b925050602083013567ffffffffffffffff811115611ce1575f5ffd5b611ced85828601611b5a565b9150509250929050565b5f5f5f60608486031215611d09575f5ffd5b833567ffffffffffffffff811115611d1f575f5ffd5b611d2b86828701611a12565b935050602084013567ffffffffffffffff811115611d47575f5ffd5b611d5386828701611876565b925050604084013567ffffffffffffffff811115611d6f575f5ffd5b611d7b86828701611b5a565b9150509250925092565b803567ffffffffffffffff811681146112f2575f5ffd5b8035600781106112f2575f5ffd5b5f60c08284031215611dba575f5ffd5b611dc26115f1565b9050611dcd82611afa565b8152611ddb60208301611d85565b6020820152604082013567ffffffffffffffff811115611df9575f5ffd5b611e0584828501611663565b604083015250606082013567ffffffffffffffff811115611e24575f5ffd5b611e3084828501611663565b60608301525060808281013590820152611e4c60a08301611d9c565b60a082015292915050565b5f5f60408385031215611e68575f5ffd5b823567ffffffffffffffff811115611e7e575f5ffd5b611e8a85828601611a12565b925050602083013567ffffffffffffffff811115611ea6575f5ffd5b611ced85828601611daa565b5f5f5f60608486031215611ec4575f5ffd5b833567ffffffffffffffff811115611eda575f5ffd5b611ee686828701611a12565b935050602084013567ffffffffffffffff811115611f02575f5ffd5b611f0e86828701611876565b925050604084013567ffffffffffffffff811115611f2a575f5ffd5b611d7b86828701611daa565b5f6101408284031215611f47575f5ffd5b611f4f6115aa565b9050611f5a826112e7565b8152611f68602083016112e7565b6020820152611f79604083016112e7565b604082015260608281013590820152611f9460808301611d85565b6080820152611fa560a083016112e7565b60a0820152611fb660c08301611afa565b60c0820152611fc760e08301611afa565b60e0820152611fd96101008301611afa565b61010082015261012082013567ffffffffffffffff811115611ff9575f5ffd5b61200584828501611663565b6101208301525092915050565b5f5f60408385031215612023575f5ffd5b823567ffffffffffffffff811115612039575f5ffd5b61204585828601611a12565b925050602083013567ffffffffffffffff811115612061575f5ffd5b611ced85828601611f36565b5f5f5f6060848603121561207f575f5ffd5b833567ffffffffffffffff811115612095575f5ffd5b6120a186828701611a12565b935050602084013567ffffffffffffffff8111156120bd575f5ffd5b6120c986828701611876565b925050604084013567ffffffffffffffff8111156120e5575f5ffd5b611d7b86828701611f36565b5f5f60408385031215612102575f5ffd5b823567ffffffffffffffff811115612118575f5ffd5b61212485828601611a12565b925050602083013567ffffffffffffffff811115612140575f5ffd5b611ced85828601611876565b5f6020828403121561215c575f5ffd5b813567ffffffffffffffff811115612172575f5ffd5b610e0d84828501611a1256fea264697066735822122090d7f30edbb2df9425b9f9bc213c05855b0c0f44de3603f23db30d02dfc7544f64736f6c634300081c0033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.