ArbitrumBridgeModule

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/modules/bridge/ArbitrumBridgeModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Modules
import { BridgeModuleBase } from "@modules/bridge/base/BridgeModuleBase.sol";

// Interfaces
import { IArbitrumBridgeModule } from "@modules/bridge/interfaces/arbitrum/IArbitrumBridgeModule.sol";
import { IModule } from "@modules/interfaces/IModule.sol";
import { IL1GatewayRouter } from "@modules/bridge/interfaces/arbitrum/IL1GatewayRouter.sol";
import { IInbox } from "@modules/bridge/interfaces/arbitrum/IInbox.sol";

// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { ERC20UtilsLib } from "@modules/libraries/ERC20UtilsLib.sol";
import { OrderHashLib } from "@modules/libraries/OrderHashLib.sol";
import { BridgeLib } from "@modules/libraries/BridgeLib.sol";

// Types
import { Order } from "@types/Order.sol";

/// @title ArbitrumBridgeModule
/// @notice Module for bridging tokens from Ethereum to Arbitrum via the canonical Arbitrum bridge
/// @dev Supports both ERC20 tokens (via L1GatewayRouter) and ETH (via Inbox)
contract ArbitrumBridgeModule is BridgeModuleBase, IArbitrumBridgeModule {
    using SafeTransferLib for address;
    using ERC20UtilsLib for address;
    using OrderHashLib for Order;

    /*//////////////////////////////////////////////////////////////
                              IMMUTABLES
    //////////////////////////////////////////////////////////////*/

    /// @notice The L1 Gateway Router address for Arbitrum ERC20 bridging
    IL1GatewayRouter public immutable L1_GATEWAY_ROUTER;

    /// @notice The Inbox address for Arbitrum ETH bridging
    IInbox public immutable INBOX;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    /// @notice Constructs the ArbitrumBridgeModule
    /// @param _portikusV2 The PortikusV2 contract address
    /// @param l1GatewayRouter The L1 Gateway Router address for ERC20 bridging
    /// @param inbox The Inbox address for ETH bridging
    constructor(
        address _portikusV2,
        address l1GatewayRouter,
        address inbox
    )
        BridgeModuleBase("Arbitrum Bridge Module", "1.0.0", _portikusV2)
    {
        L1_GATEWAY_ROUTER = IL1GatewayRouter(l1GatewayRouter);
        INBOX = IInbox(inbox);
    }

    /*//////////////////////////////////////////////////////////////
                                EXTERNAL
    //////////////////////////////////////////////////////////////*/

    /// @notice Bridges tokens using Arbitrum's native bridge (L1GatewayRouter for ERC20, Inbox for ETH)
    /// @dev This is the unique function signature for Arbitrum bridging
    /// @param order The order containing bridge details
    /// @param beneficiary The address of the beneficiary on Arbitrum (L2)
    /// @param inputAmount The amount of tokens to bridge
    /// @param bridgeData Encoded (uint256) or (uint256, uint256) representing the agent fee and settlement (protocol +
    /// partner) fees
    /// @return nativeFee Returns the actual native fee paid to the bridge protocol
    function bridgeWithArbitrum(
        Order memory order,
        address beneficiary,
        uint256 inputAmount,
        bytes memory bridgeData
    )
        external
        payable
        returns (uint256 nativeFee)
    {
        // Require bridge access authorization from settlement module
        BridgeLib.requireBridgeInitiated();

        return _bridgeInternal(order, beneficiary, inputAmount, bridgeData);
    }

    /// @inheritdoc IModule
    function selectors() external pure override returns (bytes4[] memory moduleSelectors) {
        moduleSelectors = new bytes4[](1);
        moduleSelectors[0] = this.bridgeWithArbitrum.selector;
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @notice Internal bridge implementation for Arbitrum
    /// @param order The order containing bridge details
    /// @param beneficiary The address of the beneficiary on Arbitrum (L2)
    /// @param inputAmount The amount of input tokens to bridge
    /// @param bridgeData Encoded (uint256, uint256) representing the agent fee and settlement (protocol & partner) fees
    function _bridgeInternal(
        Order memory order,
        address beneficiary,
        uint256 inputAmount,
        bytes memory bridgeData
    )
        internal
        returns (uint256 nativeFee)
    {
        ArbitrumBridgeData memory protocolData = _validateAndDecodeProtocolData(order);

        // Decode bridge data and calculate capped agent fee
        uint256 agentFee = _calculateCappedFee(abi.decode(bridgeData, (uint256)), protocolData.maxAgentFee);

        uint256 bridgeAmount = inputAmount - agentFee;

        // Compensate agent in dest tokens for paying ETH gas fees
        if (agentFee > 0) order.destToken.transferTo(msg.sender, agentFee);

        // Calculate ETH value needed for gas fees
        nativeFee = _calculateGasFees(protocolData);

        // Handle ETH vs ERC20 bridging based on source token (what we're bridging)
        if (order.destToken.isEth()) {
            // Pass agentFee and settlementEthFees to ensure msg.value accounting and refunds are correct under
            // delegatecall
            _bridgeETH(beneficiary, bridgeAmount, protocolData, nativeFee);
        } else {
            _bridgeERC20(order.destToken, beneficiary, bridgeAmount, protocolData, nativeFee);
        }

        // Emit the bridge event with destination-decimal output amount
        emit TokensBridged(
            order.hash(),
            beneficiary,
            order.bridge.destinationChainId,
            inputAmount,
            _convertToDestDecimals(bridgeAmount, order.bridge.scalingFactor),
            "arbitrum"
        );
    }

    /// @notice Bridge ERC20 tokens via the L1 Gateway Router
    /// @dev Gas fees are paid from msg.value provided by the agent
    /// @param l1Token The L1 token address to bridge
    /// @param beneficiary The recipient address on Arbitrum
    /// @param amount The amount to bridge (after agent fee)
    /// @param protocolData The Arbitrum bridge data
    /// @param gasFees The gas fees required for the bridge operation
    function _bridgeERC20(
        address l1Token,
        address beneficiary,
        uint256 amount,
        ArbitrumBridgeData memory protocolData,
        uint256 gasFees
    )
        internal
    {
        // Get the gateway for this token
        address gateway = L1_GATEWAY_ROUTER.getGateway(l1Token);
        if (gateway == address(0)) revert NoGatewayFound();

        // Approve the gateway to spend the tokens
        l1Token.safeApprove(gateway, amount);

        // Encode submission cost and empty data for the user
        bytes memory userEncodedData = abi.encode(protocolData.maxSubmissionCost, protocolData.extraData);

        L1_GATEWAY_ROUTER.outboundTransferCustomRefund{ value: gasFees }(
            l1Token, beneficiary, beneficiary, amount, protocolData.maxGas, protocolData.gasPriceBid, userEncodedData
        );
    }

    /// @notice Bridge ETH to Arbitrum via Inbox using retryable tickets
    /// @dev Uses Arbitrum's Inbox.createRetryableTicket() for ETH bridging with custom recipient
    /// @dev Agent provides ETH for both bridge amount and gas fees via msg.value
    /// @param beneficiary The recipient address on Arbitrum L2
    /// @param amount The amount of ETH to bridge to the beneficiary
    /// @param protocolData The Arbitrum bridge data containing gas parameters
    /// @param gasFees The gas fees required for the bridge operation
    function _bridgeETH(
        address beneficiary,
        uint256 amount,
        ArbitrumBridgeData memory protocolData,
        uint256 gasFees
    )
        internal
    {
        // Only forward the exact value required by the Arbitrum Inbox (amount + gasFees)
        INBOX.createRetryableTicket{ value: amount + gasFees }(
            beneficiary, // to: destination address on L2
            amount, // l2CallValue: ETH amount to send to beneficiary
            protocolData.maxSubmissionCost, // maxSubmissionCost: base fee for submission
            beneficiary, // excessFeeRefundAddress: beneficiary gets gas refunds
            beneficiary, // callValueRefundAddress: beneficiary gets ETH if ticket times out
            protocolData.maxGas, // gasLimit: max gas for L2 execution
            protocolData.gasPriceBid, // maxFeePerGas: gas price bid
            "" // data: empty for simple ETH transfer
        );
    }

    /// @notice Validates and decodes the protocol data from the order
    /// @param order The order containing the protocol data to validate
    /// @return protocolData The validated and decoded Arbitrum bridge data
    function _validateAndDecodeProtocolData(Order memory order)
        internal
        pure
        returns (ArbitrumBridgeData memory protocolData)
    {
        // Validate protocol data exists
        if (order.bridge.protocolData.length == 0) revert MissingProtocolData();

        // Decode protocol data
        protocolData = abi.decode(order.bridge.protocolData, (ArbitrumBridgeData));

        // Validate gas parameters based on token type
        uint256 gasLimit = order.destToken.isEth() ? 21_000 : 200_000; // 21k for ETH, 200k for ERC20
        if (protocolData.maxGas < gasLimit) revert InsufficientMaxGas();
        if (protocolData.gasPriceBid < 1e7) revert InvalidGasPriceBid(); // Min 0.01 gwei
    }

    /// @notice Calculates total gas fees required for Arbitrum retryable tickets
    /// @param protocolData The Arbitrum bridge data containing gas parameters
    /// @return gasFees The total gas fees (submission cost + execution gas cost)
    function _calculateGasFees(ArbitrumBridgeData memory protocolData) internal pure returns (uint256 gasFees) {
        return protocolData.maxSubmissionCost + (protocolData.maxGas * protocolData.gasPriceBid);
    }
}
"
    },
    "src/modules/bridge/base/BridgeModuleBase.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Contracts
import { BaseModule } from "@modules/base/BaseModule.sol";

// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { ERC20UtilsLib } from "@modules/libraries/ERC20UtilsLib.sol";

/// @title Bridge Module Base
/// @notice Abstract base contract for bridge modules containing shared functionality
/// @dev Provides common patterns, validations, and utilities for bridge implementations
abstract contract BridgeModuleBase is BaseModule {
    using SafeTransferLib for address;
    /*//////////////////////////////////////////////////////////////
                                ERRORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Thrown when protocol data is missing or empty
    error MissingProtocolData();

    /*//////////////////////////////////////////////////////////////
                                EVENTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Emitted when tokens are bridged across chains
    /// @param orderHash The hash of the order being bridged
    /// @param beneficiary The recipient on the destination chain
    /// @param destChainId The destination chain ID
    /// @param inputAmount The amount of tokens being bridged (expressed in source token decimals)
    /// @param outputAmount The expected amount on the destination chain, expressed in destination token decimals
    ///        after applying `_convertToDestDecimals`
    /// @param bridgeProtocol The bridge protocol used (e.g., "optimism", "polygon", "cctp")
    event TokensBridged(
        bytes32 indexed orderHash,
        address indexed beneficiary,
        uint256 indexed destChainId,
        uint256 inputAmount,
        uint256 outputAmount,
        string bridgeProtocol
    );

    /*//////////////////////////////////////////////////////////////
                              CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    /// @notice Constructor for BridgeModuleBase
    /// @param _name The module name
    /// @param _version The module version
    /// @param _portikusV2 The PortikusV2 contract address
    constructor(
        string memory _name,
        string memory _version,
        address _portikusV2
    )
        BaseModule(_name, _version, _portikusV2)
    { }

    /*//////////////////////////////////////////////////////////////
                           SHARED UTILITIES
    //////////////////////////////////////////////////////////////*/

    /// @notice Calculates agent fee with cap protection
    /// @param requestedFee The requested agent fee
    /// @param maxFee The maximum allowed fee
    /// @return The capped agent fee
    function _calculateCappedFee(uint256 requestedFee, uint256 maxFee) internal pure returns (uint256) {
        return requestedFee > maxFee ? maxFee : requestedFee;
    }

    /*//////////////////////////////////////////////////////////////
                           DECIMAL CONVERSION
    //////////////////////////////////////////////////////////////*/

    /// @notice Converts an amount from source token decimals to destination token decimals
    /// @dev scalingFactor = destDecimals - srcDecimals
    ///
    /// Behavior:
    /// - scalingFactor > 0 (dest has more decimals than src): multiply by 10^scalingFactor
    /// - scalingFactor < 0 (dest has fewer decimals than src): divide by 10^(-scalingFactor)
    ///
    /// Constraints:
    /// - |scalingFactor| <= 18 to avoid 10^N overflow and keep gas predictable
    ///
    /// Examples:
    /// - USDC (6) -> WETH (18): scalingFactor = +12; 1e6 -> 1e6 * 10^12 = 1e18
    /// - WETH (18) -> USDC (6): scalingFactor = -12; 1e18 -> 1e18 / 10^12 = 1e6
    ///
    /// @param amount Amount expressed in source token decimals
    /// @param scalingFactor Signed decimal difference (destDecimals - srcDecimals)
    /// @return Amount expressed in destination token decimals
    function _convertToDestDecimals(uint256 amount, int8 scalingFactor) internal pure returns (uint256) {
        if (scalingFactor == 0) return amount;

        // Validate scaling factor to prevent overflow
        int8 absFactor = scalingFactor >= 0 ? scalingFactor : -scalingFactor;
        require(uint8(absFactor) <= 18, "Bridge: scaling factor too large");

        uint256 scale = 10 ** uint8(absFactor);

        if (scalingFactor > 0) {
            // destDecimals > srcDecimals: multiply to convert src -> dest
            return amount * scale;
        } else {
            // srcDecimals > destDecimals: divide to convert src -> dest
            return amount / scale;
        }
    }
}
"
    },
    "src/modules/bridge/interfaces/arbitrum/IArbitrumBridgeModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Types
import { Order } from "@types/Order.sol";

/// @title Arbitrum Bridge Module Interface
/// @notice Interface for Arbitrum's native L1GatewayRouter bridge protocol
/// @dev Supports both ERC20 tokens (via L1GatewayRouter) and ETH (via Inbox)
interface IArbitrumBridgeModule {
    /*//////////////////////////////////////////////////////////////
                                ERRORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Thrown when max gas is too low for L2 execution (21k for ETH, 200k for ERC20)
    error InsufficientMaxGas();

    /// @notice Thrown when gas price bid is too low (minimum 0.01 gwei)
    error InvalidGasPriceBid();

    /// @notice Thrown when no gateway is found for a token
    error NoGatewayFound();

    /// @notice Thrown when insufficient ETH provided for gas fees (ERC20 bridging)
    error InsufficientEthForGasFees();

    /// @notice Thrown when insufficient ETH provided for bridge amount and gas fees (ETH bridging)
    error InsufficientEthForBridgeAndGasFees();

    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Protocol-controlled Arbitrum bridge configuration
    /// @dev This struct defines the data encoded in order.bridge.protocolData by the backend
    ///      These parameters cannot be manipulated by agents during execution
    struct ArbitrumBridgeData {
        /// @dev Max gas deducted from user's L2 balance to cover execution
        uint256 maxGas;
        /// @dev Gas price for L2 execution
        uint256 gasPriceBid;
        /// @dev Max gas deducted for base submission fee
        uint256 maxSubmissionCost;
        /// @dev Optional extra data for L2 execution
        bytes extraData;
        /// @dev The maximum fee in order dest token that agent can set as compensation for paying for the bridge
        uint256 maxAgentFee;
    }

    /*//////////////////////////////////////////////////////////////
                                EXTERNAL
    //////////////////////////////////////////////////////////////*/

    /// @notice Bridges tokens using Arbitrum's native bridge infrastructure
    /// @dev For ERC20: Uses L1GatewayRouter, agent provides ETH for gas via msg.value
    /// @dev For ETH: Uses Inbox.createRetryableTicket, agent provides ETH for bridge amount + gas via msg.value
    /// @param order The order containing bridge details and protocol data
    /// @param beneficiary The address of the beneficiary on Arbitrum (L2)
    /// @param inputAmount The amount of tokens to bridge
    /// @param bridgeData Encoded uint256 representing the agent fee for gas compensation
    /// @return nativeFee Returns the actual native fee paid to the bridge protocol
    function bridgeWithArbitrum(
        Order memory order,
        address beneficiary,
        uint256 inputAmount,
        bytes memory bridgeData
    )
        external
        payable
        returns (uint256 nativeFee);
}
"
    },
    "src/modules/interfaces/IModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/// @title IModule
/// @notice Core interfaces that all modules must implement to be compatible with the Portikus protocol
interface IModule {
    /*//////////////////////////////////////////////////////////////
                                METADATA
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns the name of the module
    function name() external view returns (string memory);

    /// @notice Returns the version of the module
    function version() external view returns (string memory);

    /*//////////////////////////////////////////////////////////////
                               SELECTORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Used by the executor to determine which functions should be installed
    /// @dev The implementation should not include any of the function selectors defined in the IModule interface itself
    /// @return moduleSelectors An array of function selectors that the module implements
    function selectors() external pure returns (bytes4[] memory moduleSelectors);
}
"
    },
    "src/modules/bridge/interfaces/arbitrum/IL1GatewayRouter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/// @title Interface for Arbitrum's L1GatewayRouter
/// @notice Defines the API for routing token deposits to appropriate gateways
interface IL1GatewayRouter {
    /// @notice Deposit ERC20 token from Ethereum into Arbitrum using the registered or otherwise default gateway
    /// @dev Some legacy gateway might not have the outboundTransferCustomRefund method and will revert, in such case
    /// use outboundTransfer instead
    ///      L2 address alias will not be applied to the following types of addresses on L1:
    ///      - 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
    /// @param _token L1 address of ERC20
    /// @param _refundTo Account, or its L2 alias if it have code in L1, to be credited with excess gas refund in L2
    /// @param _to Account to be credited with the tokens in the L2 (can be the user's L2 account or a contract), not
    /// subject to L2 aliasing
    ///                This account, or its L2 alias if it have code in L1, will also be able to cancel the retryable
    /// ticket and receive callvalue refund
    /// @param _amount Token Amount
    /// @param _maxGas Max gas deducted from user's L2 balance to cover L2 execution
    /// @param _gasPriceBid Gas price for L2 execution
    /// @param _data encoded data from router and user
    /// @return res abi encoded inbox sequence number
    function outboundTransferCustomRefund(
        address _token,
        address _refundTo,
        address _to,
        uint256 _amount,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        bytes calldata _data
    )
        external
        payable
        returns (bytes memory res);

    /// @notice Gets the gateway contract for a given token
    /// @dev Returns the token-specific gateway if registered, otherwise returns the default gateway.
    ///      Returns address(0) if the gateway is disabled or not a valid contract.
    /// @param _token L1 address of the token
    /// @return gateway Address of the gateway contract for this token, or address(0) if no valid gateway exists
    function getGateway(address _token) external view returns (address gateway);
}
"
    },
    "src/modules/bridge/interfaces/arbitrum/IInbox.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/// @title Interface for Arbitrum's Inbox contract
/// @notice Used for depositing ETH from L1 to L2 Arbitrum
interface IInbox {
    /// @notice Deposit eth from L1 to L2 to address of the sender if sender is an EOA, and to its aliased address if
    /// the sender is a contract
    /// @dev This does not trigger the fallback function when receiving in the L2 side.
    ///      Look into retryable tickets if you are interested in this functionality.
    /// @dev This function should not be called inside contract constructors
    /// @return The unique message number of the deposit
    function depositEth() external payable returns (uint256);

    /// @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts
    /// @dev all msg.value will deposited to callValueRefundAddress on L2
    /// @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
    /// @param to destination L2 contract address
    /// @param l2CallValue call value for retryable L2 message
    /// @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
    /// @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance. In case
    /// this address is a contract, funds will be received in its alias on L2.
    /// @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled.
    /// In case this address is a contract, funds will be received in its alias on L2.
    /// @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic
    /// value used to trigger the RetryableData error)
    /// @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the
    /// RetryableData error)
    /// @param data ABI encoded data of L2 message
    /// @return unique message number of the retryable transaction
    function createRetryableTicket(
        address to,
        uint256 l2CallValue,
        uint256 maxSubmissionCost,
        address excessFeeRefundAddress,
        address callValueRefundAddress,
        uint256 gasLimit,
        uint256 maxFeePerGas,
        bytes calldata data
    )
        external
        payable
        returns (uint256);
}
"
    },
    "lib/solady/src/utils/SafeTransferLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /// @dev The Permit2 operation has failed.
    error Permit2Failed();

    /// @dev The Permit2 amount must be less than `2**160 - 1`.
    error Permit2AmountOverflow();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
    uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;

    /// @dev The unique EIP-712 domain domain separator for the DAI token contract.
    bytes32 internal constant DAI_DOMAIN_SEPARATOR =
        0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;

    /// @dev The address for the WETH9 contract on Ethereum mainnet.
    address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    /// @dev The canonical Permit2 address.
    /// [Github](https://github.com/Uniswap/permit2)
    /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
    address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
    //
    // The regular variants:
    // - Forwards all remaining gas to the target.
    // - Reverts if the target reverts.
    // - Reverts if the current contract has insufficient balance.
    //
    // The force variants:
    // - Forwards with an optional gas stipend
    //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
    // - If the target reverts, or if the gas stipend is exhausted,
    //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
    //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
    // - Reverts if the current contract has insufficient balance.
    //
    // The try variants:
    // - Forwards with a mandatory gas stipend.
    // - Instead of reverting, returns whether the transfer succeeded.

    /// @dev Sends `amount` (in wei) ETH to `to`.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`.
    function safeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer all the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function trySafeTransferAllETH(address to, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function trySafeTransferFrom(address token, address from, address to, uint256 amount)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
            amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, to) // Store the `to` argument.
            amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
    /// then retries the approval again (some tokens, e.g. USDT, requires this).
    /// Reverts upon failure.
    function safeApproveWithRetry(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, retrying upon failure.
            let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
            if iszero(and(eq(mload(0x00), 1), success)) {
                if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                    mstore(0x34, 0) // Store 0 for the `amount`.
                    mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                    pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                    mstore(0x34, amount) // Store back the original `amount`.
                    // Retry the approval, reverting upon failure.
                    success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                    if iszero(and(eq(mload(0x00), 1), success)) {
                        // Check the `extcodesize` again just in case the token selfdestructs lol.
                        if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
                            mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                            revert(0x1c, 0x04)
                        }
                    }
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            amount :=
                mul( // The arguments of `mul` are evaluated from right to left.
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// If the initial attempt fails, try to use Permit2 to transfer the token.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for the current contract to manage.
    function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
        if (!trySafeTransferFrom(token, from, to, amount)) {
            permit2TransferFrom(token, from, to, amount);
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
    /// Reverts upon failure.
    function permit2TransferFrom(address token, address from, address to, uint256 amount)
        internal
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(m, 0x74), shr(96, shl(96, token)))
            mstore(add(m, 0x54), amount)
            mstore(add(m, 0x34), to)
            mstore(add(m, 0x20), shl(96, from))
            // `transferFrom(address,address,uint160,address)`.
            mstore(m, 0x36c78516000000000000000000000000)
            let p := PERMIT2
            let exists := eq(chainid(), 1)
            if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
            if iszero(
                and(
                    call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
                    lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
                )
            ) {
                mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
                revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
            }
        }
    }

    /// @dev Permit a user to spend a given amount of
    /// another user's tokens via native EIP-2612 permit if possible, falling
    /// back to Permit2 if native permit fails or is not implemented on the token.
    function permit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        bool success;
        /// @solidity memory-safe-assembly
        assembly {
            for {} shl(96, xor(token, WETH9)) {} {
                mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
                if iszero(
                    and( // The arguments of `and` are evaluated from right to left.
                        lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
                        // Gas stipend to limit gas burn for tokens that don't refund gas when
                        // an non-existing function is called. 5K should be enough for a SLOAD.
                        staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
                    )
                ) { break }
                // After here, we can be sure that token is a contract.
                let m := mload(0x40)
                mstore(add(m, 0x34), spender)
                mstore(add(m, 0x20), shl(96, owner))
                mstore(add(m, 0x74), deadline)
                if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
                    mstore(0x14, owner)
                    mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
                    mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
                    mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
                    // `nonces` is already at `add(m, 0x54)`.
                    // `1` is already stored at `add(m, 0x94)`.
                    mstore(add(m, 0xb4), and(0xff, v))
                    mstore(add(m, 0xd4), r)
                    mstore(add(m, 0xf4), s)
                    success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
                    break
                }
                mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
                mstore(add(m, 0x54), amount)
                mstore(add(m, 0x94), and(0xff, v))
                mstore(add(m, 0xb4), r)
                mstore(add(m, 0xd4), s)
                success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
                break
            }
        }
        if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
    }

    /// @dev Simple permit on the Permit2 contract.
    function simplePermit2(
        address token,
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(m, 0x927da105) // `allowance(address,address,address)`.
            {
                let addressMask := shr(96, not(0))
                mstore(add(m, 0x20), and(addressMask, owner))
                mstore(add(m, 0x40), and(addressMask, token))
                mstore(add(m, 0x60), and(addressMask, spender))
                mstore(add(m, 0xc0), and(addressMask, spender))
            }
            let p := mul(PERMIT2, iszero(shr(160, amount)))
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
                    staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
                )
            ) {
                mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
                revert(add(0x18, shl(2, iszero(p))), 0x04)
            }
            mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
            // `owner` is already `add(m, 0x20)`.
            // `token` is already at `add(m, 0x40)`.
            mstore(add(m, 0x60), amount)
            mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
            // `nonce` is already at `add(m, 0xa0)`.
            // `spender` is already at `add(m, 0xc0)`.
            mstore(add(m, 0xe0), deadline)
            mstore(add(m, 0x100), 0x100) // `signature` offset.
            mstore(add(m, 0x120), 0x41) // `signature` length.
            mstore(add(m, 0x140), r)
            mstore(add(m, 0x160), s)
            mstore(add(m, 0x180), shl(248, v))
            if iszero( // Revert if token does not have code, or if the call fails.
            mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) {
                mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
                revert(0x1c, 0x04)
            }
        }
    }
}
"
    },
    "src/modules/libraries/ERC20UtilsLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";

/// @title ERC20 Utility Library
/// @dev Library with common functions used by different modules within the Portikus V2 protocol
library ERC20UtilsLib {
    /*//////////////////////////////////////////////////////////////
                                 ERRORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Emitted when the permit execution fails
    error PermitFailed();

    /*//////////////////////////////////////////////////////////////
                               CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @dev An address used to represent the native token
    address internal constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    /// @dev The address of the Permit2 contract
    address internal constant PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

    /*//////////////////////////////////////////////////////////////
                               LIBRARIES
    //////////////////////////////////////////////////////////////*/

    using SafeTransferLib for address;

    /*//////////////////////////////////////////////////////////////
                                TRANSFER
    //////////////////////////////////////////////////////////////*/

    /// @dev Transfer the dest token to the order beneficiary, reducing the amount by 1 wei for gas optimization
    ///      purposes. If the destToken is ETH, it will transfer native ETH to recipient
    /// @param destToken The address of the dest token
    /// @param recipient The address to transfer to
    /// @param amount The amount to transfer
    function transferTo(address destToken, address recipient, uint256 amount) internal {
        // If the destToken is ETH, transfer native ETH
        if (isEth(destToken)) {
            recipient.safeTransferETH(amount);
        } else {
            // Otherwise, transfer the dest token to the recipient, reducing the amount by 1 wei
            // for gas optimization purposes
            destToken.safeTransfer(recipient, amount);
        }
    }

    /// @dev Transfer the src token from the user to a recipient, using permit2 allowance or erc20 allowance based on
    ///      permit length, if the permit length is 192 or 1 it will call transferFrom using permit2
    ///      allowance, otherwise it will call transferFrom using erc20 allowance unless permit length is 96
    /// @param srcToken The address of the src token
    /// @param owner The owner of the token
    /// @param recipient The recipient of the token
    /// @param amount The amount to transfer
    /// @param permitLength The length of the permit
    function transferFrom(
        address srcToken,
        address owner,
        address recipient,
        uint256 amount,
        uint256 permitLength
    )
        internal
    {
        // Skip transferring if the permit length is 96 (permit2TransferFrom)
        if (permitLength == 96) {
            return;
        }
        // If the permit length is 192 or 1 execute permit2TransferFrom to transfer the
        // input assets from the owner to the agent, otherwise execute ERC20 transferFrom
        if (permitLength == 192 || permitLength == 1) {
            // Transfer the input assets from the owner to the recipient using permit2 allowance
            srcToken.permit2TransferFrom(owner, recipient, amount);
        } else {
            // Transfer the input assets from the owner to the recipient using the ERC20 transferFrom function
            srcToken.safeTransferFrom(owner, recipient, amount);
        }
    }

    /*//////////////////////////////////////////////////////////////
                                 PERMIT
    //////////////////////////////////////////////////////////////*/

    /// @dev Executes the permit function on the provided token, depending on the permit length,
    ///      it will call the EIP2612 permit (224), DAI-Style permit (256), Permit2 AllowanceTransfer (192)
    ///      or Permit2 Signature (96)
    /// @param token The address of the token
    /// @param data The permit data
    /// @param owner The owner of the token (used for Permit2)
    /// @param deadline The deadline for the permit (used for Permit2)
    /// @param amount The amount to permit (used for Permit2)
    function permit(
        address token,
        bytes calldata data,
        address owner,
        uint256 deadline,
        uint256 amount,
        address recipient
    )
        internal
    {
        // solhint-disable-next-line no-inline-assembly
        assembly ("memory-safe") {
            // check the permit length
            switch data.length
            // 0x00 = no permit
            case 0x00 {
                // do nothing
            }
            case 0x01 {
                // 0x01 is used to signify already existing permit2 allowance
            }
            // 32(permit2nonce) + 64(signature) = 96 Permit2 Transfer format
            case 0x60 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x30f28b7a00000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), token) // store the srcToken
                mstore(add(x, 0x24), amount) // store the amount
                calldatacopy(add(x, 0x44), data.offset, 0x20) // copy the nonce
                mstore(add(x, 0x64), deadline) // store the deadline
                mstore(add(x, 0x84), recipient) // store the recipient address (executor)
                mstore(add(x, 0xa4), amount) // store the amount
                mstore(add(x, 0xc4), owner) // store the owner
                mstore(add(x, 0xe4), 0x100) // store the offset
                mstore(add(x, 0x104), 0x40) // store the length
                calldatacopy(add(x, 0x124), add(data.offset, 0x20), 0x40) // copy the signature
                // Call Permit2 contract and revert on failure
                if iszero(call(gas(), PERMIT2_ADDRESS, 0x00, x, 0x164, 0x00, 0x00)) {
                    mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the
                        // selector
                    revert(0x00, 0x04) // Revert with PermitFailed error
                }
            }
            // 32(amount) + 32(nonce) + 32(expiration) + 32(sigDeadline) + 64(signature) = 192 Permit2 allowance format
            case 0xc0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x2b67b57000000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), owner) // store the owner address
                mstore(add(x, 0x24), token) // store the token address
                calldatacopy(add(x, 0x44), data.offset, 0x60) // copy amount, expiration, nonce
                mstore(add(x, 0xa4), address()) // store this contract's address as the spender
                calldatacopy(add(x, 0xc4), add(data.offset, 0x60), 0x20) // copy sigDeadline
                mstore(add(x, 0xe4), 0x100) // store the offset for signature
                mstore(add(x, 0x104), 0x40) // store the length of signature
                calldatacopy(add(x, 0x124), add(data.offset, 0x80), 0x40) // copy the signature
                // Call Permit2 contract and revert on failure
                if iszero(call(gas(), PERMIT2_ADDRESS, 0x00, x, 0x164, 0x00, 0x00)) {
                    mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the
                        // selector
                    revert(0x00, 0x04) // Revert with PermitFailed error
                }
            }
            // 32 * 7 = 224 EIP2612 Permit
            case 0xe0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0xd505accf00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0xe0) // store the args
                pop(call(gas(), token, 0x00, x, 0xe4, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // 32 * 8 = 256 DAI-Style Permit
            case 0x100 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x8fcbaf0c00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0x100) // store the args
                pop(call(gas(), token, 0x00, x, 0x104, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // Otherwise revert
            default {
                mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the selector
                revert(0x00, 0x04) // Revert with PermitFailed error
            }
        }
    }

    /// @dev A fillable version of the permit function from this lib, that doesn't support Permit2 SignatureTransfer
    /// @param token The address of the token
    /// @param data The permit data
    /// @param owner The owner of the token (used for Permit2)
    function permit(address token, bytes calldata data, address owner) internal {
        // solhint-disable-next-line no-inline-assembly
        assembly ("memory-safe") {
            // check the permit length
            switch data.length
            // 0x00 = no permit
            case 0x00 {
                // do nothing
            }
            case 0x01 {
                // 0x01 is used to signify already existing permit2 allowance
            }
            // 32(amount) + 32(nonce) + 32(expiration) + 32(sigDeadline) + 64(signature) = 192 Permit2 allowance format
            case 0xc0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x2b67b57000000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), owner) // store the owner address
                mstore(add(x, 0x24), token) // store the token address
                calldatacopy(add(x, 0x44), data.offset, 0x60) // copy amount, expiration, nonce
                mstore(add(x, 0xa4), address()) // store this contract's address as the spender
                calldatacopy(add(x, 0xc4), add(data.offset, 0x60), 0x20) // copy sigDeadline
                mstore(add(x, 0xe4), 0x100) // store the offset for signature
                mstore(add(x, 0x104), 0x40) // store the length of signature
                calldatacopy(add(x, 0x124), add(data.offset, 0x80), 0x40) // copy the signature
                // Call Permit2 contract and revert on failure
                if iszero(call(gas(), PERMIT2_ADDRESS, 0x00, x, 0x164, 0x00, 0x00)) {
                    mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the
                        // selector
                    revert(0x00, 0x04) // Revert with PermitFailed error
                }
            }
            // 32 * 7 = 224 EIP2612 Permit
            case 0xe0 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0xd505accf00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0xe0) // store the args
                pop(call(gas(), token, 0x00, x, 0xe4, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // 32 * 8 = 256 DAI-Style Permit
            case 0x100 {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x8fcbaf0c00000000000000000000000000000000000000000000000000000000) // store the selector
                calldatacopy(add(x, 0x04), data.offset, 0x100) // store the args
                pop(call(gas(), token, 0x00, x, 0x104, 0x00, 0x20)) // call ERC20 permit, skip checking return data
            }
            // Otherwise revert
            default {
                mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the selector
                revert(0x00, 0x04) // Revert with PermitFailed error
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                                UTILITIES
    //////////////////////////////////////////////////////////////*/

    /// @notice Checks if a token is ETH
    /// @param token The token address to check
    /// @return True if the token represents ETH
    function isEth(address token) internal pure returns (bool) {
        return token == ETH_ADDRESS;
    }

    /*//////////////////////////////////////////////////////////////
                                BALANCE
    //////////////////////////////////////////////////////////////*/

    /// @dev Returns the balance of address(this), works for both ETH and ERC20 tokens
    function getBalance(address token) internal view returns (uint256 balanceOf) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            switch eq(token, ETH_ADDRESS)
            // ETH\

Tags:
Proxy, Swap, Upgradeable, Factory|addr:0x27afaa59ee0ca9d8c6cbaab800952a5294a1b154|verified:true|block:23538988|tx:0xfaa51b505e41b13b96cc4b072c8bd0f4980f2123af74f3469e22afcd0942abe5|first_check:1760002174

Submitted on: 2025-10-09 11:29:34

Comments

Log in to comment.

No comments yet.