// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;import { IAcrossSpokePool } from"../Interfaces/IAcrossSpokePool.sol";
import { TransferrableOwnership } from"../Helpers/TransferrableOwnership.sol";
import { AcrossFacetV3 } from"./AcrossFacetV3.sol";
import { ILiFi } from"../Interfaces/ILiFi.sol";
import { ERC20, SafeTransferLib } from"solmate/utils/SafeTransferLib.sol";
import { LibAsset, IERC20 } from"../Libraries/LibAsset.sol";
/// @title AcrossFacetPackedV3/// @author LI.FI (https://li.fi)/// @notice Provides functionality for bridging through Across in a gas-optimized way/// @custom:version 1.2.0contractAcrossFacetPackedV3isILiFi, TransferrableOwnership{
usingSafeTransferLibforERC20;
/// Storage ////// @notice The contract address of the cbridge on the source chain.
IAcrossSpokePool publicimmutable spokePool;
/// @notice The WETH address on the current chain.addresspublicimmutable wrappedNative;
/// Events ///eventLiFiAcrossTransfer(bytes8 _transactionId);
eventCallExecutedAndFundsWithdrawn();
/// Errors ///errorWithdrawFailed();
// using this struct to bundle parameters is required since otherwise we run into stack-too-deep errors// (Solidity can only handle a limited amount of parameters at any given time)structPackedParameters {
bytes32 transactionId;
address receiver;
address depositor;
uint64 destinationChainId;
address receivingAssetId;
uint256 outputAmount;
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/// @param _owner The address of the contract ownerconstructor(
IAcrossSpokePool _spokePool,
address _wrappedNative,
address _owner
) TransferrableOwnership(_owner) {
spokePool = _spokePool;
wrappedNative = _wrappedNative;
}
/// External Methods ////// @dev Only meant to be called outside of the context of the diamond/// @notice Sets approval for the Across spoke pool Router to spend the specified token/// @param tokensToApprove The tokens to approve to the Across spoke poolfunctionsetApprovalForBridge(address[] calldata tokensToApprove
) externalonlyOwner{
for (uint256 i; i < tokensToApprove.length; i++) {
// Give Across spoke pool approval to pull tokens from this facet
LibAsset.maxApproveERC20(
IERC20(tokensToApprove[i]),
address(spokePool),
type(uint256).max
);
}
}
/// @notice Bridges native tokens via Across (packed implementation)/// @dev Calldata mapping:/// [0:4] - function selector/// [4:12] - transactionId/// [12:32] - receiver/// [32:52] - depositor/// [52:56] - destinationChainId/// [56:76] - receivingAssetId/// [76:108] - outputAmount/// [108:128] - exclusiveRelayer/// [128:132] - quoteTimestamp/// [132:136] - fillDeadline/// [136:140] - exclusivityDeadline/// [140:] - messagefunctionstartBridgeTokensViaAcrossV3NativePacked() externalpayable{
// call Across spoke pool to bridge assets
spokePool.depositV3{ value: msg.value }(
address(bytes20(msg.data[32:52])), // depositoraddress(bytes20(msg.data[12:32])), // recipient
wrappedNative, // inputTokenaddress(bytes20(msg.data[56:76])), // outputTokenmsg.value, // inputAmountuint256(bytes32(msg.data[76:108])), // outputAmountuint64(uint32(bytes4(msg.data[52:56]))), // destinationChainIdaddress(bytes20(msg.data[108:128])), // exclusiveRelayeruint32(bytes4(msg.data[128:132])), // quoteTimestampuint32(bytes4(msg.data[132:136])), // fillDeadlineuint32(bytes4(msg.data[136:140])), // exclusivityDeadlinemsg.data[140:msg.data.length]
);
emit LiFiAcrossTransfer(bytes8(msg.data[4:12]));
}
/// @notice Bridges native tokens via Across (minimal implementation)/// @param _parameters Contains all parameters required for native bridging with AcrossV3functionstartBridgeTokensViaAcrossV3NativeMin(
PackedParameters calldata _parameters
) externalpayable{
// call Across spoke pool to bridge assets
spokePool.depositV3{ value: msg.value }(
_parameters.depositor, // depositor
_parameters.receiver,
wrappedNative, // inputToken
_parameters.receivingAssetId, // outputTokenmsg.value, // inputAmount
_parameters.outputAmount,
_parameters.destinationChainId,
_parameters.exclusiveRelayer,
_parameters.quoteTimestamp,
_parameters.fillDeadline,
_parameters.exclusivityDeadline,
_parameters.message
);
emit LiFiAcrossTransfer(bytes8(_parameters.transactionId));
}
/// @notice Bridges ERC20 tokens via Across (packed implementation)/// @dev Calldata mapping:/// [0:4] - function selector/// [4:12] - transactionId/// [12:32] - receiver/// [32:52] - depositor/// [52:72] - sendingAssetId/// [72:88] - inputAmount/// [88:92] - destinationChainId/// [92:112] - receivingAssetId/// [112:144] - outputAmount/// [144:164] - exclusiveRelayer/// [164:168] - quoteTimestamp/// [168:172] - fillDeadline/// [172:176] - exclusivityDeadline/// [176:] - messagefunctionstartBridgeTokensViaAcrossV3ERC20Packed() external{
address sendingAssetId =address(bytes20(msg.data[52:72]));
uint256 inputAmount =uint256(uint128(bytes16(msg.data[72:88])));
ERC20(sendingAssetId).safeTransferFrom(
msg.sender,
address(this),
inputAmount
);
spokePool.depositV3(
address(bytes20(msg.data[32:52])), // depositoraddress(bytes20(msg.data[12:32])), // recipient
sendingAssetId, // inputTokenaddress(bytes20(msg.data[92:112])), // outputToken
inputAmount, // inputAmountuint256(bytes32(msg.data[112:144])), // outputAmountuint64(uint32(bytes4(msg.data[88:92]))), // destinationChainIdaddress(bytes20(msg.data[144:164])), // exclusiveRelayeruint32(bytes4(msg.data[164:168])), // quoteTimestampuint32(bytes4(msg.data[168:172])), // fillDeadlineuint32(bytes4(msg.data[172:176])), // exclusivityDeadlinemsg.data[176:msg.data.length]
);
emit LiFiAcrossTransfer(bytes8(msg.data[4:12]));
}
/// @notice Bridges ERC20 tokens via Across (minimal implementation)/// @param _parameters Contains all base parameters required for bridging with AcrossV3/// @param sendingAssetId The address of the asset/token to be bridged/// @param inputAmount The amount to be bridged (including fees)functionstartBridgeTokensViaAcrossV3ERC20Min(
PackedParameters calldata _parameters,
address sendingAssetId,
uint256 inputAmount
) external{
// Deposit assets
ERC20(sendingAssetId).safeTransferFrom(
msg.sender,
address(this),
inputAmount
);
// call Across SpokePool
spokePool.depositV3(
_parameters.depositor, // depositor
_parameters.receiver,
sendingAssetId, // inputToken
_parameters.receivingAssetId, // outputToken
inputAmount,
_parameters.outputAmount,
_parameters.destinationChainId,
_parameters.exclusiveRelayer,
_parameters.quoteTimestamp,
_parameters.fillDeadline,
_parameters.exclusivityDeadline,
_parameters.message
);
emit LiFiAcrossTransfer(bytes8(_parameters.transactionId));
}
/// @notice Encodes calldata that can be used to call the native 'packed' function/// @param _parameters Contains all parameters required for native bridging with AcrossV3functionencode_startBridgeTokensViaAcrossV3NativePacked(
PackedParameters calldata _parameters
) externalpurereturns (bytesmemory) {
// there are already existing networks with chainIds outside uint32 range but since we not support either of them yet,// we feel comfortable using this approach to save further gasrequire(
_parameters.destinationChainId <=type(uint32).max,
"destinationChainId value passed too big to fit in uint32"
);
returnbytes.concat(
AcrossFacetPackedV3
.startBridgeTokensViaAcrossV3NativePacked
.selector,
bytes8(_parameters.transactionId),
bytes20(_parameters.receiver),
bytes20(_parameters.depositor),
bytes4(uint32(_parameters.destinationChainId)),
bytes20(_parameters.receivingAssetId),
bytes32(_parameters.outputAmount),
bytes20(_parameters.exclusiveRelayer),
bytes4(_parameters.quoteTimestamp),
bytes4(_parameters.fillDeadline),
bytes4(_parameters.exclusivityDeadline),
_parameters.message
);
}
/// @notice Encodes calldata that can be used to call the ERC20 'packed' function/// @param _parameters Contains all base parameters required for bridging with AcrossV3/// @param sendingAssetId The address of the asset/token to be bridged/// @param inputAmount The amount to be bridged (including fees)functionencode_startBridgeTokensViaAcrossV3ERC20Packed(
PackedParameters calldata _parameters,
address sendingAssetId,
uint256 inputAmount
) externalpurereturns (bytesmemory) {
// there are already existing networks with chainIds outside uint32 range but since we not support either of them yet,// we feel comfortable using this approach to save further gasrequire(
_parameters.destinationChainId <=type(uint32).max,
"destinationChainId value passed too big to fit in uint32"
);
require(
inputAmount <=type(uint128).max,
"inputAmount value passed too big to fit in uint128"
);
// Split the concatenation into parts to avoid "stack too deep" errorsbytesmemory part1 =bytes.concat(
AcrossFacetPackedV3
.startBridgeTokensViaAcrossV3ERC20Packed
.selector,
bytes8(_parameters.transactionId),
bytes20(_parameters.receiver),
bytes20(_parameters.depositor),
bytes20(sendingAssetId)
);
bytesmemory part2 =bytes.concat(
bytes16(uint128(inputAmount)),
bytes4(uint32(_parameters.destinationChainId)),
bytes20(_parameters.receivingAssetId),
bytes32(_parameters.outputAmount)
);
bytesmemory part3 =bytes.concat(
bytes20(_parameters.exclusiveRelayer),
bytes4(_parameters.quoteTimestamp),
bytes4(_parameters.fillDeadline),
bytes4(_parameters.exclusivityDeadline)
);
// Combine all parts with the messagereturnbytes.concat(part1, part2, part3, _parameters.message);
}
/// @notice Decodes calldata that is meant to be used for calling the native 'packed' function/// @param data the calldata to be decodedfunctiondecode_startBridgeTokensViaAcrossV3NativePacked(bytescalldata data
)
externalpurereturns (
BridgeData memory bridgeData,
AcrossFacetV3.AcrossV3Data memory acrossData
)
{
require(
data.length>=140,
"invalid calldata (must have length >= 140)"
);
// extract bridgeData
bridgeData.transactionId =bytes32(bytes8(data[4:12]));
bridgeData.receiver =address(bytes20(data[12:32]));
bridgeData.destinationChainId =uint64(uint32(bytes4(data[52:56])));
// extract acrossData
acrossData.refundAddress =address(bytes20(data[32:52])); // depositor
acrossData.receivingAssetId =address(bytes20(data[56:76]));
acrossData.outputAmount =uint256(bytes32(data[76:108]));
acrossData.exclusiveRelayer =address(bytes20(data[108:128]));
acrossData.quoteTimestamp =uint32(bytes4(data[128:132]));
acrossData.fillDeadline =uint32(bytes4(data[132:136]));
acrossData.exclusivityDeadline =uint32(bytes4(data[136:140]));
acrossData.message = data[140:];
return (bridgeData, acrossData);
}
/// @notice Decodes calldata that is meant to be used for calling the ERC20 'packed' function/// @param data the calldata to be decodedfunctiondecode_startBridgeTokensViaAcrossV3ERC20Packed(bytescalldata data
)
externalpurereturns (
BridgeData memory bridgeData,
AcrossFacetV3.AcrossV3Data memory acrossData
)
{
require(
data.length>=176,
"invalid calldata (must have length >= 176)"
);
bridgeData.transactionId =bytes32(bytes8(data[4:12]));
bridgeData.receiver =address(bytes20(data[12:32]));
acrossData.refundAddress =address(bytes20(data[32:52])); // depositor
bridgeData.sendingAssetId =address(bytes20(data[52:72]));
bridgeData.minAmount =uint256(uint128(bytes16(data[72:88])));
bridgeData.destinationChainId =uint64(uint32(bytes4(data[88:92])));
acrossData.receivingAssetId =address(bytes20(data[92:112]));
acrossData.outputAmount =uint256(bytes32(data[112:144]));
acrossData.exclusiveRelayer =address(bytes20(data[144:164]));
acrossData.quoteTimestamp =uint32(bytes4(data[164:168]));
acrossData.fillDeadline =uint32(bytes4(data[168:172]));
acrossData.exclusivityDeadline =uint32(bytes4(data[172:176]));
acrossData.message = data[176:];
return (bridgeData, acrossData);
}
/// @notice Execute calldata and withdraw asset/// @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 withdrawfunctionexecuteCallAndWithdraw(address _callTo,
bytescalldata _callData,
address _assetAddress,
address _to,
uint256 _amount
) externalonlyOwner{
// execute calldata// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = _callTo.call(_callData);
// check success of callif (success) {
// call successful - withdraw the asset
LibAsset.transferAsset(_assetAddress, payable(_to), _amount);
emit CallExecutedAndFundsWithdrawn();
} else {
// call unsuccessful - revertrevert WithdrawFailed();
}
}
}
Contract Source Code
File 2 of 21: AcrossFacetV3.sol
// SPDX-License-Identifier: MITpragmasolidity ^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.0.0contractAcrossFacetV3isILiFi, ReentrancyGuard, SwapperV2, Validatable{
/// Storage ////// @notice The contract address of the spoke pool on the source chain.
IAcrossSpokePool publicimmutable spokePool;
/// @notice The WETH address on the current chain.addresspublicimmutable 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 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 tokensstructAcrossV3Data {
address receiverAddress;
address refundAddress;
address receivingAssetId;
uint256 outputAmount;
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 AcrossfunctionstartBridgeTokensViaAcrossV3(
ILiFi.BridgeData memory _bridgeData,
AcrossV3Data calldata _acrossData
)
externalpayablenonReentrantrefundExcessNative(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 AcrossfunctionswapAndStartBridgeTokensViaAcrossV3(
ILiFi.BridgeData memory _bridgeData,
LibSwap.SwapData[] calldata _swapData,
AcrossV3Data calldata _acrossData
)
externalpayablenonReentrantrefundExcessNative(payable(msg.sender))
containsSourceSwaps(_bridgeData)
validateBridgeData(_bridgeData)
{
_bridgeData.minAmount = _depositAndSwap(
_bridgeData.transactionId,
_bridgeData.minAmount,
_swapData,
payable(msg.sender)
);
_startBridge(_bridgeData, _acrossData);
}
/// 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 Acrossfunction_startBridge(
ILiFi.BridgeData memory _bridgeData,
AcrossV3Data calldata _acrossData
) internal{
// validate destination call flagif (_acrossData.message.length>0!= _bridgeData.hasDestinationCall)
revert InformationMismatch();
// ensure that receiver addresses match in case of no destination callif (
!_bridgeData.hasDestinationCall &&
(_bridgeData.receiver != _acrossData.receiverAddress)
) revert InformationMismatch();
// check if sendingAsset is native or ERC20if (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);
}
}
Contract Source Code
File 3 of 21: Address.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)pragmasolidity ^0.8.1;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @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.
* ====
*/functionisContract(address account) internalviewreturns (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].
*/functionsendValue(addresspayable 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._
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
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._
*/functionfunctionCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
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._
*/functionfunctionCallWithValue(address target, bytesmemory data, uint256 value) internalreturns (bytesmemory) {
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._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(address(this).balance>= value, "Address: insufficient balance for call");
(bool success, bytesmemory 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._
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
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._
*/functionfunctionStaticCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
(bool success, bytesmemory 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._
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
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._
*/functionfunctionDelegateCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
(bool success, bytesmemory 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._
*/functionverifyCallResultFromTarget(address target,
bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
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 contractrequire(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._
*/functionverifyCallResult(bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalpurereturns (bytesmemory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function_revert(bytesmemory returndata, stringmemory errorMessage) privatepure{
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assembly/// @solidity memory-safe-assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
Contract Source Code
File 4 of 21: ERC20.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=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.abstractcontractERC20{
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/eventTransfer(addressindexedfrom, addressindexed to, uint256 amount);
eventApproval(addressindexed owner, addressindexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
uint8publicimmutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/uint256public totalSupply;
mapping(address=>uint256) public balanceOf;
mapping(address=>mapping(address=>uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/uint256internalimmutable INITIAL_CHAIN_ID;
bytes32internalimmutable INITIAL_DOMAIN_SEPARATOR;
mapping(address=>uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name,
stringmemory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID =block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/functionapprove(address spender, uint256 amount) publicvirtualreturns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
returntrue;
}
functiontransfer(address to, uint256 amount) publicvirtualreturns (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);
returntrue;
}
functiontransferFrom(addressfrom,
address to,
uint256 amount
) publicvirtualreturns (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);
returntrue;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) publicvirtual{
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);
}
functionDOMAIN_SEPARATOR() publicviewvirtualreturns (bytes32) {
returnblock.chainid== INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
functioncomputeDomainSeparator() internalviewvirtualreturns (bytes32) {
returnkeccak256(
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) internalvirtual{
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(addressfrom, uint256 amount) internalvirtual{
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: MIT/// @custom:version 1.0.0pragmasolidity ^0.8.17;interfaceIAcrossSpokePool{
functiondeposit(address recipient, // Recipient addressaddress originToken, // Address of the tokenuint256 amount, // Token amountuint256 destinationChainId, // ⛓ idint64 relayerFeePct, // see #Fees Calculationuint32 quoteTimestamp, // Timestamp for the quote creationbytesmemory 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.) externalpayable;
functiondepositV3(address depositor,
address recipient,
address inputToken,
address outputToken,
uint256 inputAmount,
uint256 outputAmount, // <-- replaces feesuint256 destinationChainId,
address exclusiveRelayer,
uint32 quoteTimestamp,
uint32 fillDeadline,
uint32 exclusivityDeadline,
bytescalldata message
) externalpayable;
}
Contract Source Code
File 7 of 21: IERC173.sol
// SPDX-License-Identifier: MIT/// @custom:version 1.0.0pragmasolidity ^0.8.17;/// @title ERC-173 Contract Ownership Standard/// Note: the ERC-165 identifier for this interface is 0x7f5828d0/* is ERC165 */interfaceIERC173{
/// @dev This emits when ownership of a contract changes.eventOwnershipTransferred(addressindexed previousOwner,
addressindexed newOwner
);
/// @notice Get the address of the owner/// @return owner_ The address of the owner.functionowner() externalviewreturns (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 contractfunctiontransferOwnership(address _newOwner) external;
}
Contract Source Code
File 8 of 21: IERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed 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.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (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.
*/functiontransfer(address to, uint256 amount) externalreturns (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.
*/functionallowance(address owner, address spender) externalviewreturns (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.
*/functionapprove(address spender, uint256 amount) externalreturns (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.
*/functiontransferFrom(addressfrom, address to, uint256 amount) externalreturns (bool);
}
Contract Source Code
File 9 of 21: IERC20Permit.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)pragmasolidity ^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.
*/interfaceIERC20Permit{
/**
* @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].
*/functionpermit(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.
*/functionnonces(address owner) externalviewreturns (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-mixedcasefunctionDOMAIN_SEPARATOR() externalviewreturns (bytes32);
}
// SPDX-License-Identifier: MIT/// @custom:version 1.0.0pragmasolidity ^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 listlibraryLibAllowList{
/// Storage ///bytes32internalconstant NAMESPACE =keccak256("com.lifi.library.allow.list");
structAllowListStorage {
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 addfunctionaddAllowedContract(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 checkfunctioncontractIsAllowed(address _contract
) internalviewreturns (bool) {
return _getStorage().allowlist[_contract];
}
/// @dev Remove a contract address from the allow list/// @param _contract the contract address to removefunctionremoveAllowedContract(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 listfor (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 listfunctiongetAllowedContracts() internalviewreturns (address[] memory) {
return _getStorage().contracts;
}
/// @dev Add a selector to the allow list/// @param _selector the selector to addfunctionaddAllowedSelector(bytes4 _selector) internal{
_getStorage().selectorAllowList[_selector] =true;
}
/// @dev Removes a selector from the allow list/// @param _selector the selector to removefunctionremoveAllowedSelector(bytes4 _selector) internal{
_getStorage().selectorAllowList[_selector] =false;
}
/// @dev Returns if selector has been added to the allow list/// @param _selector the selector to checkfunctionselectorIsAllowed(bytes4 _selector) internalviewreturns (bool) {
return _getStorage().selectorAllowList[_selector];
}
/// @dev Fetch local storage structfunction_getStorage()
internalpurereturns (AllowListStorage storage als)
{
bytes32 position = NAMESPACE;
// solhint-disable-next-line no-inline-assemblyassembly {
als.slot:= position
}
}
/// @dev Contains business logic for validating a contract address./// @param _contract address of the dex to checkfunction_checkAddress(address _contract) privateview{
if (_contract ==address(0)) revert InvalidContract();
if (_contract.code.length==0) revert InvalidContract();
}
}
Contract Source Code
File 12 of 21: LibAsset.sol
// SPDX-License-Identifier: UNLICENSED/// @custom:version 1.0.0pragmasolidity ^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/// @notice This library contains helpers for dealing with onchain transfers/// of assets, including accounting for the native asset `assetId`/// conventions and any noncompliant ERC20 transferslibraryLibAsset{
uint256privateconstant MAX_UINT =type(uint256).max;
addressinternalconstant NULL_ADDRESS =address(0);
/// @dev All native assets use the empty address for their asset id/// by conventionaddressinternalconstant 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 libraryfunctiongetOwnBalance(address assetId) internalviewreturns (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 recipientfunctiontransferNativeAsset(addresspayable 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 spendingfunctionmaxApproveERC20(
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.safeApprove(IERC20(assetId), spender, 0);
SafeERC20.safeApprove(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 recipientfunctiontransferERC20(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 spenderfunctiontransferFromERC20(address assetId,
addressfrom,
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();
}
}
functiondepositAsset(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);
}
}
functiondepositAssets(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 assetfunctionisNativeAsset(address assetId) internalpurereturns (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 recipientfunctiontransferAsset(address assetId,
addresspayable recipient,
uint256 amount
) internal{
isNativeAsset(assetId)
? transferNativeAsset(recipient, amount)
: transferERC20(assetId, recipient, amount);
}
/// @dev Checks whether the given address is a contract and contains codefunctionisContract(address _contractAddr) internalviewreturns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assemblyassembly {
size :=extcodesize(_contractAddr)
}
return size >0;
}
}
Contract Source Code
File 13 of 21: LibBytes.sol
// SPDX-License-Identifier: MIT/// @custom:version 1.0.0pragmasolidity ^0.8.17;libraryLibBytes{
// solhint-disable no-inline-assembly// LibBytes specific errorserrorSliceOverflow();
errorSliceOutOfBounds();
errorAddressOutOfBounds();
bytes16privateconstant _SYMBOLS ="0123456789abcdef";
// -------------------------functionslice(bytesmemory _bytes,
uint256 _start,
uint256 _length
) internalpurereturns (bytesmemory) {
if (_length +31< _length) revert SliceOverflow();
if (_bytes.length< _start + _length) revert SliceOutOfBounds();
bytesmemory tempBytes;
assembly {
switchiszero(_length)
case0 {
// 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 nowmstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length arraydefault {
tempBytes :=mload(0x40)
//zero out the 32 bytes slice we are about to return//we need to do it because Solidity does not garbage collectmstore(tempBytes, 0)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
functiontoAddress(bytesmemory _bytes,
uint256 _start
) internalpurereturns (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.solfunctiontoHexString(uint256 value,
uint256 length
) internalpurereturns (stringmemory) {
bytesmemory buffer =newbytes(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");
returnstring(buffer);
}
}
// SPDX-License-Identifier: MIT/// @custom:version 1.0.0pragmasolidity ^0.8.17;import"./LibBytes.sol";
libraryLibUtil{
usingLibBytesforbytes;
functiongetRevertMsg(bytesmemory _res
) internalpurereturns (stringmemory) {
// 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";
bytesmemory revertData = _res.slice(4, _res.length-4); // Remove the selector which is the first 4 bytesreturnabi.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 addressfunctionisZeroAddress(address addr) internalpurereturns (bool) {
return addr ==address(0);
}
functionrevertWith(bytesmemory data) internalpure{
assembly {
let dataSize :=mload(data) // Load the size of the datalet dataPtr :=add(data, 0x20) // Advance data pointer to the next wordrevert(dataPtr, dataSize) // Revert with the given data
}
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)pragmasolidity ^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.
*/librarySafeERC20{
usingAddressforaddress;
/**
* @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.
*/functionsafeTransfer(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.
*/functionsafeTransferFrom(IERC20 token, addressfrom, 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.
*/functionsafeApprove(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.
*/functionsafeIncreaseAllowance(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.
*/functionsafeDecreaseAllowance(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.
*/functionforceApprove(IERC20 token, address spender, uint256 value) internal{
bytesmemory 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.
*/functionsafePermit(
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, bytesmemory 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.bytesmemory 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, bytesmemory data) privatereturns (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, bytesmemory returndata) =address(token).call(data);
return
success && (returndata.length==0||abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
Contract Source Code
File 18 of 21: SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=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.librarySafeTransferLib{
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/functionsafeTransferETH(address to, uint256 amount) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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
//////////////////////////////////////////////////////////////*/functionsafeTransferFrom(
ERC20 token,
addressfrom,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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");
}
functionsafeTransfer(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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");
}
functionsafeApprove(
ERC20 token,
address to,
uint256 amount
) internal{
bool success;
/// @solidity memory-safe-assemblyassembly {
// 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");
}
}
Contract Source Code
File 19 of 21: SwapperV2.sol
// SPDX-License-Identifier: MIT/// @custom:version 1.0.0pragmasolidity ^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 functionalitycontractSwapperV2isILiFi{
/// Types ////// @dev only used to get around "Stack Too Deep" errorsstructReserveData {
bytes32 transactionId;
addresspayable 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 balancesmodifiernoLeftovers(
LibSwap.SwapData[] calldata _swaps,
addresspayable _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 swapsif (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 balancesmodifiernoLeftoversReserve(
LibSwap.SwapData[] calldata _swaps,
addresspayable _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 swapsif (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 tomodifierrefundExcessNative(addresspayable _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 swapfunction_depositAndSwap(bytes32 _transactionId,
uint256 _minAmount,
LibSwap.SwapData[] calldata _swaps,
addresspayable _leftoverReceiver
) internalreturns (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 callerfunction_depositAndSwap(bytes32 _transactionId,
uint256 _minAmount,
LibSwap.SwapData[] calldata _swaps,
addresspayable _leftoverReceiver,
uint256 _nativeReserve
) internalreturns (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 balancesfunction_executeSwaps(bytes32 _transactionId,
LibSwap.SwapData[] calldata _swaps,
addresspayable _leftoverReceiver,
uint256[] memory _initialBalances
) internalnoLeftovers(_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 swapsfunction_executeSwaps(
ReserveData memory _reserveData,
LibSwap.SwapData[] calldata _swaps,
uint256[] memory _initialBalances
)
internalnoLeftoversReserve(
_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
) privateviewreturns (uint256[] memory) {
uint256 numSwaps = _swaps.length;
uint256[] memory balances =newuint256[](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;
}
}
Contract Source Code
File 20 of 21: TransferrableOwnership.sol
// SPDX-License-Identifier: MIT/// @custom:version 1.0.0pragmasolidity ^0.8.17;import { IERC173 } from"../Interfaces/IERC173.sol";
import { LibAsset } from"../Libraries/LibAsset.sol";
contractTransferrableOwnershipisIERC173{
addresspublic owner;
addresspublic pendingOwner;
/// Errors ///errorUnAuthorized();
errorNoNullOwner();
errorNewOwnerMustNotBeSelf();
errorNoPendingOwnershipTransfer();
errorNotPendingOwner();
/// Events ///eventOwnershipTransferRequested(addressindexed _from,
addressindexed _to
);
constructor(address initialOwner) {
owner = initialOwner;
}
modifieronlyOwner() {
if (msg.sender!= owner) revert UnAuthorized();
_;
}
/// @notice Initiates transfer of ownership to a new address/// @param _newOwner the address to transfer ownership tofunctiontransferOwnership(address _newOwner) externalonlyOwner{
if (_newOwner == LibAsset.NULL_ADDRESS) revert NoNullOwner();
if (_newOwner ==msg.sender) revert NewOwnerMustNotBeSelf();
pendingOwner = _newOwner;
emit OwnershipTransferRequested(msg.sender, pendingOwner);
}
/// @notice Cancel transfer of ownershipfunctioncancelOwnershipTransfer() externalonlyOwner{
if (pendingOwner == LibAsset.NULL_ADDRESS)
revert NoPendingOwnershipTransfer();
pendingOwner = LibAsset.NULL_ADDRESS;
}
/// @notice Confirms transfer of ownership to the calling address (msg.sender)functionconfirmOwnershipTransfer() external{
address _pendingOwner = pendingOwner;
if (msg.sender!= _pendingOwner) revert NotPendingOwner();
emit OwnershipTransferred(owner, _pendingOwner);
owner = _pendingOwner;
pendingOwner = LibAsset.NULL_ADDRESS;
}
}