CCTPBridgeModule

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

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

// Interfaces
import { ICCTPBridgeModule } from "@modules/bridge/interfaces/cctp/ICCTPBridgeModule.sol";
import { IModule } from "@modules/interfaces/IModule.sol";
import { ITokenMessenger } from "@modules/bridge/interfaces/cctp/ITokenMessenger.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 CCTP Bridge Module V2
/// @notice Circle's Cross-Chain Transfer Protocol (CCTP) V2-only bridge implementation
/// @dev Optimized for V2 with immutable constructor parameters and fast/standard finality modes
contract CCTPBridgeModule is BridgeModuleBase, ICCTPBridgeModule {
    using SafeTransferLib for address;
    using OrderHashLib for Order;
    using BridgeLib for Order;

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

    /// @notice The finality threshold for fast bridging
    uint32 constant FAST_MIN_FINALITY_THRESHOLD = 1000;

    /// @notice The USDC token address for this chain
    address public immutable USDC_TOKEN;

    /// @notice The CCTP V2 TokenMessenger contract address
    address public immutable TOKEN_MESSENGER;

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

    /// @notice Initialize the CCTP bridge module
    /// @param portikusV2 Address of the PortikusV2 main contract
    /// @param usdcToken Address of the USDC token on this chain
    /// @param tokenMessenger Address of the CCTP V2 TokenMessenger contract
    constructor(
        address portikusV2,
        address usdcToken,
        address tokenMessenger
    )
        BridgeModuleBase("CCTP Bridge Module", "2.0.0", portikusV2)
    {
        if (usdcToken == address(0)) revert InvalidUSDCToken();
        if (tokenMessenger == address(0)) revert InvalidTokenMessenger();

        USDC_TOKEN = usdcToken;
        TOKEN_MESSENGER = tokenMessenger;
    }

    /*//////////////////////////////////////////////////////////////
                              EXTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns the module's function selectors for registration
    /// @return selectors Array of function selectors this module implements
    function selectors() external pure override returns (bytes4[] memory) {
        bytes4[] memory moduleSelectors = new bytes4[](1);
        moduleSelectors[0] = this.bridgeWithCCTP.selector;
        return moduleSelectors;
    }

    /// @notice Bridges USDC using Circle's CCTP V2 protocol
    /// @param order The order containing bridge details
    /// @param beneficiary The address of the beneficiary on destination chain
    /// @param bridgeAmount The amount of USDC to bridge
    function bridgeWithCCTP(
        Order memory order,
        address beneficiary,
        uint256 bridgeAmount,
        bytes memory /* _bridgeData (unused) */
    )
        external
        payable
    {
        // Require bridge access authorization from settlement module
        BridgeLib.requireBridgeInitiated();

        // Validate that the destination token is USDC
        if (order.destToken != USDC_TOKEN) revert InvalidUSDCToken();

        // Bridge USDC using CCTP V2 and emit event
        _bridgeInternal(order, beneficiary, bridgeAmount);
    }

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

    /// @notice Internal function to bridge USDC using CCTP V2
    /// @param order The order containing bridge details
    /// @param beneficiary The address of the beneficiary on destination chain
    /// @param bridgeAmount The amount of USDC to bridge
    function _bridgeInternal(Order memory order, address beneficiary, uint256 bridgeAmount) internal {
        // Validate and decode protocol data
        CCTPBridgeData memory protocolData = _validateAndDecodeProtocolData(order);

        // Approve TokenMessenger to spend USDC
        USDC_TOKEN.safeApprove(TOKEN_MESSENGER, bridgeAmount);

        // Execute CCTP V2 bridge: Enhanced depositForBurn with finality thresholds
        ITokenMessenger(TOKEN_MESSENGER).depositForBurn(
            bridgeAmount,
            protocolData.destinationDomain,
            _addressToBytes32(beneficiary), // convert beneficiary address to bytes32 format for CCTP
            USDC_TOKEN,
            bytes32(0), // destinationCaller
            protocolData.maxFee,
            protocolData.minFinalityThreshold
        );

        // Emit the bridge event with the amount actually bridged
        emit TokensBridged(
            order.hash(),
            beneficiary,
            order.bridge.destinationChainId,
            bridgeAmount,
            _convertToDestDecimals(bridgeAmount, order.bridge.scalingFactor),
            "cctp"
        );
    }

    /// @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 CCTP bridge data
    function _validateAndDecodeProtocolData(Order memory order)
        internal
        pure
        returns (CCTPBridgeData memory protocolData)
    {
        // Validate protocol data exists
        if (order.bridge.protocolData.length == 0) revert MissingProtocolData();

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

        // Validate V2 finality threshold (≤1000 for fast, >1000 for standard)
        if (protocolData.minFinalityThreshold <= FAST_MIN_FINALITY_THRESHOLD && protocolData.maxFee == 0) {
            // Fast bridging requires non-zero maxFee for premium service
            revert FastFinalityRequiresFee();
        }
    }

    /// @notice Converts an address to bytes32 format for CCTP
    /// @param addr The address to convert
    /// @return The address as bytes32 (with 12 leading zero bytes)
    function _addressToBytes32(address addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(addr)));
    }
}
"
    },
    "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/cctp/ICCTPBridgeModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

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

/// @title CCTP Bridge Module Interface
/// @notice Interface for Circle's Cross-Chain Transfer Protocol (CCTP) V2 bridge module
/// @dev V2-only interface with fast/standard finality modes and immutable constructor parameters
interface ICCTPBridgeModule {
    /*//////////////////////////////////////////////////////////////
                                ERRORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Thrown when USDC token address is invalid (zero address)
    error InvalidUSDCToken();

    /// @notice Thrown when TokenMessenger address is invalid (zero address)
    error InvalidTokenMessenger();

    /// @notice Thrown when finality threshold is invalid (must be 1000 for fast or 2000 for standard)
    error InvalidFinalityThreshold();

    /// @notice Thrown when fast finality is used but maxFee is zero (fast bridging requires fees)
    error FastFinalityRequiresFee();

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

    /// @notice Protocol-specific data for CCTP V2 bridging operations
    /// @dev This struct defines the data encoded in order.bridge.protocolData by the backend
    ///      These parameters cannot be manipulated by agents during execution
    ///      USDC token and TokenMessenger addresses are now immutable constructor parameters
    struct CCTPBridgeData {
        /// @dev The destination domain ID for CCTP routing (not chain ID)
        uint32 destinationDomain;
        /// @dev Maximum fee willing to pay (in USDC) - V2 feature for better UX
        uint256 maxFee;
        /// @dev Minimum finality threshold - V2 enhanced feature for bridge speed control
        ///      - 1000: Fast bridging mode (lower security, faster settlement)
        ///      - 2000: Standard bridging mode (higher security, standard settlement)
        uint32 minFinalityThreshold;
    }

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

    /// @notice Bridges USDC using Circle's CCTP V2 protocol
    /// @param order The order containing bridge details
    /// @param beneficiary The address of the beneficiary on destination chain
    /// @param inputAmount The amount of USDC to bridge
    function bridgeWithCCTP(
        Order memory order,
        address beneficiary,
        uint256 inputAmount,
        bytes memory /* _bridgeData (unused) */
    )
        external
        payable;
}
"
    },
    "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/cctp/ITokenMessenger.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/// @title Token Messenger Interface
/// @notice Interface for Circle's CCTP TokenMessenger contracts (V1 and V2)
interface ITokenMessenger {
    /*//////////////////////////////////////////////////////////////
                                CCTP V1
    //////////////////////////////////////////////////////////////*/

    /// @notice Deposits and burns tokens from sender to be minted on destination domain
    /// @param amount Amount of tokens to deposit and burn
    /// @param destinationDomain Destination domain identifier
    /// @param mintRecipient Address of mint recipient on destination domain (as bytes32)
    /// @param burnToken Address of contract to burn deposited tokens on local domain
    /// @return nonce The nonce of the message
    function depositForBurn(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken
    )
        external
        returns (uint64 nonce);

    /*//////////////////////////////////////////////////////////////
                                CCTP V2
    //////////////////////////////////////////////////////////////*/

    /// @notice CCTP V2: Deposits and burns tokens with enhanced parameters
    /// @param amount Amount of tokens to deposit and burn
    /// @param destinationDomain Destination domain ID to send the message to
    /// @param mintRecipient Address of mint recipient on destination domain (as bytes32)
    /// @param burnToken Address of contract to burn deposited tokens on local domain
    /// @param destinationCaller Address which can call receiveMessage on destination domain
    /// @param maxFee Max fee paid for fast burn, specified in units of burnToken
    /// @param minFinalityThreshold Minimum finality threshold at which burn will be attested
    function depositForBurn(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken,
        bytes32 destinationCaller,
        uint256 maxFee,
        uint32 minFinalityThreshold
    )
        external;
}
"
    },
    "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
            case 0x01 { balanceOf := selfbalance() }
            // ERC20
            default {
                let x := mload(0x40) // get the free memory pointer
                mstore(x, 0x70a0823100000000000000000000000000000000000000000000000000000000) // store the selector
                mstore(add(x, 0x04), address()) // store the account
                let success := staticcall(gas(), token, x, 0x24, x, 0x20) // call balanceOf
                if success { balanceOf := mload(x) } // load the balance
            }
        }
    }
}
"
    },
    "src/modules/libraries/OrderHashLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

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

/// @title Order Hash Library
/// @dev Library with functions to handle hashing of Order structs
library OrderHashLib {
    /*//////////////////////////////////////////////////////////////
                              TYPESTRINGS
    //////////////////////////////////////////////////////////////*/

    /// @dev The type of the order struct
    bytes internal constant _ORDER_TYPESTRING = // solhint-disable-next-line max-line-length
        "Order(address owner,address beneficiary,address srcToken,address destToken,uint256 srcAmount,uint256 destAmount,uint256 expectedAmount,uint256 deadline,uint8 kind,uint256 nonce,uint256 partnerAndFee,bytes permit,bytes metadata,Bridge bridge)Bridge(bytes4 protocolSelector,uint256 destinationChainId,address outputToken,int8 scalingFactor,bytes protocolData)";

    bytes internal constant _BRIDGE_TYPESTRING = // solhint-disable-next-line max-line-length
        "Bridge(bytes4 protocolSelector,uint256 destinationChainId,address outputToken,int8 scalingFactor,bytes protocolData)";

    /*//////////////////////////////////////////////////////////////
                                TYPEHASH
    //////////////////////////////////////////////////////////////*/

    /// @dev The type hash of the order struct
    bytes32 internal constant _ORDER_TYPEHASH = 0xc75d848e51cd0f81113e24c5a62c9b8566b0ff0d476245a7882709315eefbbf7;

    /// @dev The type hash of the Bridge struct
    bytes32 internal constant _BRIDGE_TYPEHASH = 0x8d8bcbf96f8246b1dcca3f4e1750aecf6cd6971d370b908f10af1d459a2df6eb;

    /*//////////////////////////////////////////////////////////////
                                  HASH
    //////////////////////////////////////////////////////////////*/

    /// @dev EIP-712 struct-hash of an `Order` (all done in assembly, no `abi.encode`)
    /// @param order  The order to hash
    /// @return result The 32-byte hash
    function hash(Order memory order) internal pure returns (bytes32 result) {
        // Pre-compute hash of the dynamic bytes field
        bytes32 permitHash = keccak256(order.permit);
        bytes32 metadataHash = keccak256(order.metadata);
        bytes32 protocolDataHash = keccak256(order.bridge.protocolData);
        // bring struct into the stack
        Bridge memory bridge = order.bridge;

        assembly ("memory-safe") {
            /* ───────────────────────────────── Bridge hash ─────────────────────── */
            let ptr := mload(0x40) // free-mem pointer

            mstore(ptr, _BRIDGE_TYPEHASH) // type-hash
            mstore(add(ptr, 0x20), mload(bridge)) // protocolSelector (bytes4)
            mstore(add(ptr, 0x40), mload(add(bridge, 0x20))) // destinationChainId
            mstore(add(ptr, 0x60), mload(add(bridge, 0x40))) // outputToken
            mstore(add(ptr, 0x80), signextend(0, mload(add(bridge, 0x60)))) // scalingFactor (int8)
            mstore(add(ptr, 0xA0), protocolDataHash) // keccak256(protocolData)

            let bridgeHash := keccak256(ptr, 0xC0) // 6 words → 192 bytes

            /* ────────────────────────────── Order hash ────────────────────────── */
            ptr := add(ptr, 0xC0) // reuse memory after bridge blob

            mstore(ptr, _ORDER_TYPEHASH) // 0x00  type-hash
            mstore(add(ptr, 0x20), mload(order)) // owner
            mstore(add(ptr, 0x40), mload(add(order, 0x20))) // beneficiary
            mstore(add(ptr, 0x60), mload(add(order, 0x40))) // srcToken
            mstore(add(ptr, 0x80), mload(add(order, 0x60))) // destToken
            mstore(add(ptr, 0xA0), mload(add(order, 0x80))) // srcAmount
            mstore(add(ptr, 0xC0), mload(add(order, 0xA0))) // destAmount
            mstore(add(ptr, 0xE0), mload(add(order, 0xC0))) // expectedAmount
            mstore(add(ptr, 0x100), mload(add(order, 0xE0))) // deadline
            mstore(add(ptr, 0x120), mload(add(order, 0x100))) // kind
            mstore(add(ptr, 0x140), mload(add(order, 0x120))) // nonce
            mstore(add(ptr, 0x160), mload(add(order, 0x140))) // partnerAndFee
            mstore(add(ptr, 0x180), permitHash) // keccak256(order.permit)
            mstore(add(ptr, 0x1A0), metadataHash) // keccak256(order.metadata)
            mstore(add(ptr, 0x1C0), bridgeHash) // keccak256(Bridge blob)

            result := keccak256(ptr, 0x1E0) // 15 words → 480 bytes
        }
    }
}
"
    },
    "src/modules/libraries/BridgeLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { Address } from "@openzeppelin/utils/Address.sol";

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

/// @title Bridge Library
/// @dev A library that provides functions for bridging tokens using various protocols
library BridgeLib {
    using Address for address;
    /*//////////////////////////////////////////////////////////////
                                STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice keccak256(abi.encode(uint256(keccak256("BridgeLib.initiated")) - 1)) & ~bytes32(uint256(0xff));
    bytes32 internal constant BRIDGE_INITIATED_SLOT = 0x55784d688e8aecdf5908e67d2532e5fa1b0a3f379bbfebaef09e5fc7c0e69100;

    /*//////////////////////////////////////////////////////////////
                                ERRORS
    //////////////////////////////////////////////////////////////*/

    /// @notice Thrown when bridge is not initiated
    error BridgeNotInitiated();

    /*/////////////////////////////////////////////////////////////

Tags:
Multisig, Upgradeable, Multi-Signature, Factory|addr:0x87a5e9af10d33bdcc0d3725d95b7395965be77d2|verified:true|block:23434882|tx:0x37e54bfed3a49bb62b8258d21d7e633c76f678d426623aa04b1fd986e69c4ad5|first_check:1758741083

Submitted on: 2025-09-24 21:11:23

Comments

Log in to comment.

No comments yet.