// File: @layerzerolabs/solidity-examples/contracts/interfaces/IStargateFactory.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;

import "./IStargatePool.sol";

interface IStargateFactory {
    function getPool(uint256 _srcPoolId) external returns (IStargatePool);
}

// File: @layerzerolabs/solidity-examples/contracts/interfaces/IStargatePool.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;

interface IStargatePool {
    function token() external returns (address);
}

// File: @layerzerolabs/solidity-examples/contracts/interfaces/IStargateRouter.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;
pragma abicoder v2;

interface IStargateRouter {
    struct lzTxObj {
        uint256 dstGasForCall;
        uint256 dstNativeAmount;
        bytes dstNativeAddr;
    }

    function addLiquidity(
        uint256 _poolId,
        uint256 _amountLD,
        address _to
    ) external;

    function swap(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress,
        uint256 _amountLD,
        uint256 _minAmountLD,
        lzTxObj memory _lzTxParams,
        bytes calldata _to,
        bytes calldata _payload
    ) external payable;

    function redeemRemote(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress,
        uint256 _amountLP,
        uint256 _minAmountLD,
        bytes calldata _to,
        lzTxObj memory _lzTxParams
    ) external payable;

    function instantRedeemLocal(
        uint16 _srcPoolId,
        uint256 _amountLP,
        address _to
    ) external returns (uint256);

    function redeemLocal(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress,
        uint256 _amountLP,
        bytes calldata _to,
        lzTxObj memory _lzTxParams
    ) external payable;

    function sendCredits(
        uint16 _dstChainId,
        uint256 _srcPoolId,
        uint256 _dstPoolId,
        address payable _refundAddress
    ) external payable;

    function quoteLayerZeroFee(
        uint16 _dstChainId,
        uint8 _functionType,
        bytes calldata _toAddress,
        bytes calldata _transferAndCallPayload,
        lzTxObj memory _lzTxParams
    ) external view returns (uint256, uint256);
}

// File: @solidstate/contracts/interfaces/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.8;

import { IERC20Internal } from './IERC20Internal.sol';

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 is IERC20Internal {
    /**
     * @notice query the total minted token supply
     * @return token supply
     */
    function totalSupply() external view returns (uint256);

    /**
     * @notice query the token balance of given account
     * @param account address to query
     * @return token balance
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @notice query the allowance granted from given holder to given spender
     * @param holder approver of allowance
     * @param spender recipient of allowance
     * @return token allowance
     */
    function allowance(
        address holder,
        address spender
    ) external view returns (uint256);

    /**
     * @notice grant approval to spender to spend tokens
     * @dev prefer ERC20Extended functions to avoid transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)
     * @param spender recipient of allowance
     * @param amount quantity of tokens approved for spending
     * @return success status (always true; otherwise function should revert)
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @notice transfer tokens to given recipient
     * @param recipient beneficiary of token transfer
     * @param amount quantity of tokens to transfer
     * @return success status (always true; otherwise function should revert)
     */
    function transfer(
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @notice transfer tokens to given recipient on behalf of given holder
     * @param holder holder of tokens prior to transfer
     * @param recipient beneficiary of token transfer
     * @param amount quantity of tokens to transfer
     * @return success status (always true; otherwise function should revert)
     */
    function transferFrom(
        address holder,
        address recipient,
        uint256 amount
    ) external returns (bool);
}

// File: @solidstate/contracts/interfaces/IERC20Internal.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.8;

/**
 * @title Partial ERC20 interface needed by internal functions
 */
interface IERC20Internal {
    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

// File: @solidstate/contracts/token/ERC20/permit/IERC2612.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.8;

import { IERC2612Internal } from './IERC2612Internal.sol';

/**
 * @title ERC2612 interface
 * @dev see https://eips.ethereum.org/EIPS/eip-2612.
 */
interface IERC2612 is IERC2612Internal {
    /**
     * @notice return the EIP-712 domain separator unique to contract and chain
     * @return domainSeparator domain separator
     */
    function DOMAIN_SEPARATOR() external view returns (bytes32 domainSeparator);

    /**
     * @notice get the current ERC2612 nonce for the given address
     * @return current nonce
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @notice approve spender to transfer tokens held by owner via signature
     * @dev this function may be vulnerable to approval replay attacks
     * @param owner holder of tokens and signer of permit
     * @param spender beneficiary of approval
     * @param amount quantity of tokens to approve
     * @param v secp256k1 'v' value
     * @param r secp256k1 'r' value
     * @param s secp256k1 's' value
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
}

// File: @solidstate/contracts/token/ERC20/permit/IERC2612Internal.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.8;

interface IERC2612Internal {}

// File: @solidstate/contracts/utils/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.8;

import { ReentrancyGuardStorage } from './ReentrancyGuardStorage.sol';

/**
 * @title Utility contract for preventing reentrancy attacks
 */
abstract contract ReentrancyGuard {
    error ReentrancyGuard__ReentrantCall();

    modifier nonReentrant() {
        ReentrancyGuardStorage.Layout storage l = ReentrancyGuardStorage
            .layout();
        if (l.status == 2) revert ReentrancyGuard__ReentrantCall();
        l.status = 2;
        _;
        l.status = 1;
    }
}

// File: @solidstate/contracts/utils/ReentrancyGuardStorage.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.8;

library ReentrancyGuardStorage {
    struct Layout {
        uint256 status;
    }

    bytes32 internal constant STORAGE_SLOT =
        keccak256('solidstate.contracts.storage.ReentrancyGuard');

    function layout() internal pure returns (Layout storage l) {
        bytes32 slot = STORAGE_SLOT;
        assembly {
            l.slot := slot
        }
    }
}

// File: @uniswap/permit2/src/interfaces/IEIP712.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IEIP712 {
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// File: @uniswap/permit2/src/interfaces/ISignatureTransfer.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IEIP712} from "./IEIP712.sol";

/// @title SignatureTransfer
/// @notice Handles ERC20 token transfers through signature based actions
/// @dev Requires user's token approval on the Permit2 contract
interface ISignatureTransfer is IEIP712 {
    /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount
    /// @param maxAmount The maximum amount a spender can request to transfer
    error InvalidAmount(uint256 maxAmount);

    /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred
    /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred
    error LengthMismatch();

    /// @notice Emits an event when the owner successfully invalidates an unordered nonce.
    event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);

    /// @notice The token and amount details for a transfer signed in the permit transfer signature
    struct TokenPermissions {
        // ERC20 token address
        address token;
        // the maximum amount that can be spent
        uint256 amount;
    }

    /// @notice The signed permit message for a single token transfer
    struct PermitTransferFrom {
        TokenPermissions permitted;
        // a unique value for every token owner's signature to prevent signature replays
        uint256 nonce;
        // deadline on the permit signature
        uint256 deadline;
    }

    /// @notice Specifies the recipient address and amount for batched transfers.
    /// @dev Recipients and amounts correspond to the index of the signed token permissions array.
    /// @dev Reverts if the requested amount is greater than the permitted signed amount.
    struct SignatureTransferDetails {
        // recipient address
        address to;
        // spender requested amount
        uint256 requestedAmount;
    }

    /// @notice Used to reconstruct the signed permit message for multiple token transfers
    /// @dev Do not need to pass in spender address as it is required that it is msg.sender
    /// @dev Note that a user still signs over a spender address
    struct PermitBatchTransferFrom {
        // the tokens and corresponding amounts permitted for a transfer
        TokenPermissions[] permitted;
        // a unique value for every token owner's signature to prevent signature replays
        uint256 nonce;
        // deadline on the permit signature
        uint256 deadline;
    }

    /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection
    /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order
    /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce
    /// @dev It returns a uint256 bitmap
    /// @dev The index, or wordPosition is capped at type(uint248).max
    function nonceBitmap(address, uint256) external view returns (uint256);

    /// @notice Transfers a token using a signed permit message
    /// @dev Reverts if the requested amount is greater than the permitted signed amount
    /// @param permit The permit data signed over by the owner
    /// @param owner The owner of the tokens to transfer
    /// @param transferDetails The spender's requested transfer details for the permitted token
    /// @param signature The signature to verify
    function permitTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        bytes calldata signature
    ) external;

    /// @notice Transfers a token using a signed permit message
    /// @notice Includes extra data provided by the caller to verify signature over
    /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
    /// @dev Reverts if the requested amount is greater than the permitted signed amount
    /// @param permit The permit data signed over by the owner
    /// @param owner The owner of the tokens to transfer
    /// @param transferDetails The spender's requested transfer details for the permitted token
    /// @param witness Extra data to include when checking the user signature
    /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
    /// @param signature The signature to verify
    function permitWitnessTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        bytes32 witness,
        string calldata witnessTypeString,
        bytes calldata signature
    ) external;


    /// @notice Transfers multiple tokens using a signed permit message
    /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition
    /// @notice Includes extra data provided by the caller to verify signature over
    /// @param permit The permit data signed over by the owner
    /// @param owner The owner of the tokens to transfer
    /// @param transferDetails Specifies the recipient and requested amount for the token transfer
    /// @param witness Extra data to include when checking the user signature
    /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash
    /// @param signature The signature to verify
    function permitWitnessTransferFrom(
        PermitBatchTransferFrom memory permit,
        SignatureTransferDetails[] calldata transferDetails,
        address owner,
        bytes32 witness,
        string calldata witnessTypeString,
        bytes calldata signature
    ) external;

    /// @notice Invalidates the bits specified in mask for the bitmap at the word position
    /// @dev The wordPos is maxed at type(uint248).max
    /// @param wordPos A number to index the nonceBitmap at
    /// @param mask A bitmap masked against msg.sender's current bitmap at the word position
    function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;
}

// File: contracts/lib/Call.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

// Bubbles up errors from delegatecall
library Call {
    function _delegate(address to, bytes memory data) internal {
        (bool success, bytes memory result) = to.delegatecall(data);

        if (!success) {
            if (result.length < 68) revert();
            assembly {
                result := add(result, 0x04)
            }
            revert(abi.decode(result, (string)));
        }
    }

    function _call(address to, bytes memory data) internal {
        (bool success, bytes memory result) = to.call(data);

        if (!success) {
            if (result.length < 68) revert('call failed');
            assembly {
                result := add(result, 0x04)
            }
            revert(abi.decode(result, (string)));
        }
    }
}

// File: contracts/multiswap-and-bridge/MultiSwapAndBridge.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { IStargateRouter } from '@layerzerolabs/solidity-examples/contracts/interfaces/IStargateRouter.sol';
import { IStargateFactory } from '@layerzerolabs/solidity-examples/contracts/interfaces/IStargateFactory.sol';
import { ISignatureTransfer } from '@uniswap/permit2/src/interfaces/ISignatureTransfer.sol';
import { IERC20 } from '@solidstate/contracts/interfaces/IERC20.sol';
import { ReentrancyGuard } from '@solidstate/contracts/utils/ReentrancyGuard.sol';
import { IERC2612 } from '@solidstate/contracts/token/ERC20/permit/IERC2612.sol';

import { Call } from '../lib/Call.sol';

contract MultiSwapAndBridge is ReentrancyGuard {
    struct SwapWithPermit2 {
        address sellToken;
        uint sellAmount;
        bytes zeroXQuoteData;
        ISignatureTransfer.PermitTransferFrom permitTransferFrom;
        bytes signature;
    }

    struct SwapWithERC2612Permit {
        address sellToken;
        uint sellAmount;
        bytes zeroXQuoteData;
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    string public name = 'MultiSwapAndBridge';
    address public zeroXExchangeRouter;
    IStargateRouter public stargateRouter;
    ISignatureTransfer public permit2;
    address public stargateUSDC;

    constructor(
        address _zeroXExchangeRouter,
        IStargateRouter _stargateRouter,
        ISignatureTransfer _permit2,
        address _stargateUSDC
    ) {
        zeroXExchangeRouter = _zeroXExchangeRouter;
        stargateRouter = _stargateRouter;
        permit2 = _permit2;
        stargateUSDC = _stargateUSDC;
    }

    /// @notice Using the provide permit signatures and the associated 0x swap routes it will execute the swaps
    /// and then bridge the USDC to the provided destination chainId. If no destination chainId is provided
    /// The USDC is transferred to the msg.sender on the local chain
    /// @dev 0x Swaps must result in USDC. Any other token will be lost
    function swapAndBridge(
        SwapWithPermit2[] memory swapsWithPermit2,
        SwapWithERC2612Permit[] memory swapsWithERC2612Permit,
        // Stargate/LayerZero chainId, if 0 wont bridge, only swap
        // Only supply chainId's where USDC poolId == 1
        uint16 lzDstChainId
    ) external payable nonReentrant {
        for (uint256 i = 0; i < swapsWithPermit2.length; i++) {
            _swapWithPermit2(swapsWithPermit2[i]);
        }

        for (uint256 i = 0; i < swapsWithERC2612Permit.length; i++) {
            _swapWithERC2612Permit(swapsWithERC2612Permit[i]);
        }

        if (lzDstChainId != 0) {
            _bridge(lzDstChainId);
        } else {
            _transferOutUsdc();
        }
    }

    /// @notice This function is used to recover any tokens that were sent to the contract by mistake
    /// Or any swaps routes that were sent in that did not result in USDC.
    /// This function cannot be called while there is a swapAndBridge taking place
    /// Under normal circumstances this contract should never hold a balance of any token or eth.
    function saveToken(address token) external nonReentrant {
        IERC20(token).transfer(
            msg.sender,
            IERC20(token).balanceOf(address(this))
        );
    }

    function bridgeQuote(
        uint16 dstChainId, // Stargate/LayerZero chainId
        address dstAddress
    ) external view returns (uint fee) {
        (fee, ) = stargateRouter.quoteLayerZeroFee(
            dstChainId,
            1, // function type: see Stargate Bridge.sol for all types
            abi.encodePacked(dstAddress),
            '0x',
            IStargateRouter.lzTxObj(0, 0, bytes(''))
        );
    }

    function _swapWithPermit2(SwapWithPermit2 memory swap) internal {
        permit2.permitTransferFrom(
            swap.permitTransferFrom,
            ISignatureTransfer.SignatureTransferDetails({
                to: address(this),
                requestedAmount: swap.sellAmount
            }),
            msg.sender,
            swap.signature
        );

        _swap(swap.sellToken, swap.sellAmount, swap.zeroXQuoteData);
    }

    function _swapWithERC2612Permit(
        SwapWithERC2612Permit memory swap
    ) internal {
        IERC2612(swap.sellToken).permit(
            msg.sender,
            address(this),
            swap.sellAmount,
            swap.deadline,
            swap.v,
            swap.r,
            swap.s
        );
        IERC20(swap.sellToken).transferFrom(
            msg.sender,
            address(this),
            swap.sellAmount
        );
        _swap(swap.sellToken, swap.sellAmount, swap.zeroXQuoteData);
    }

    function _swap(
        address sellToken,
        uint sellAmount,
        bytes memory zeroXSwapData
    ) internal {
        if (zeroXSwapData.length == 0) {
            return;
        }
        IERC20(sellToken).approve(zeroXExchangeRouter, sellAmount);
        Call._call(zeroXExchangeRouter, zeroXSwapData);
    }

    function _bridge(uint16 dstChainId) internal {
        uint usdcBalance = IERC20(stargateUSDC).balanceOf(address(this));
        IERC20(stargateUSDC).approve(
            address(stargateRouter),
            IERC20(stargateUSDC).balanceOf(address(this))
        );

        // Stargate's Router.swap() function sends the tokens to the destination chain.
        stargateRouter.swap{ value: msg.value }(
            dstChainId, // the destination chain id
            1, // USDC source Stargate poolId
            1, // USDC destination Stargate poolId
            payable(msg.sender), // refund adddress. if msg.sender pays too much gas, return extra eth
            usdcBalance, // total tokens to send to destination chain
            (usdcBalance * 995) / 1000, // min amount allowed out
            IStargateRouter.lzTxObj(0, 0, bytes('')), // default lzTxObj
            abi.encodePacked(payable(msg.sender)), // destination address
            bytes('') // bytes payload
        );
    }

    function _transferOutUsdc() internal {
        IERC20(stargateUSDC).transfer(
            msg.sender,
            IERC20(stargateUSDC).balanceOf(address(this))
        );
    }
}