TokenBridgeCctpV2

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": {
    "contracts/token/TokenBridgeCctpV2.sol": {
      "content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

import {TokenBridgeCctpBase} from "./TokenBridgeCctpBase.sol";
import {TokenRouter} from "./libs/TokenRouter.sol";
import {TypedMemView} from "./../libs/TypedMemView.sol";
import {Message} from "./../libs/Message.sol";
import {TokenMessage} from "./libs/TokenMessage.sol";
import {CctpMessageV2, BurnMessageV2} from "../libs/CctpMessageV2.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {IMessageHandlerV2} from "../interfaces/cctp/IMessageHandlerV2.sol";
import {ITokenMessengerV2} from "../interfaces/cctp/ITokenMessengerV2.sol";
import {IMessageTransmitterV2} from "../interfaces/cctp/IMessageTransmitterV2.sol";

// @dev Supports only CCTP V2
contract TokenBridgeCctpV2 is TokenBridgeCctpBase, IMessageHandlerV2 {
    using CctpMessageV2 for bytes29;
    using BurnMessageV2 for bytes29;
    using TypedMemView for bytes29;

    using Message for bytes;
    using TypeCasts for bytes32;

    // see https://developers.circle.com/cctp/cctp-finality-and-fees#defined-finality-thresholds
    uint32 public immutable minFinalityThreshold;
    uint256 public immutable maxFeeBps;

    constructor(
        address _erc20,
        address _mailbox,
        IMessageTransmitterV2 _messageTransmitter,
        ITokenMessengerV2 _tokenMessenger,
        uint256 _maxFeeBps,
        uint32 _minFinalityThreshold
    )
        TokenBridgeCctpBase(
            _erc20,
            _mailbox,
            _messageTransmitter,
            _tokenMessenger
        )
    {
        require(_maxFeeBps < 10_000, "maxFeeBps must be less than 100%");
        maxFeeBps = _maxFeeBps;
        minFinalityThreshold = _minFinalityThreshold;
    }

    // ============ TokenRouter overrides ============

    /**
     * @inheritdoc TokenRouter
     * @dev Overrides to indicate v2 fees.
     *
     * Hyperlane uses a "minimum amount out" approach where users specify the exact amount
     * they want the recipient to receive on the destination chain. This provides a better
     * UX by guaranteeing predictable outcomes regardless of underlying bridge fee structures.
     *
     * However, some underlying bridges like CCTP charge fees as a percentage of the input
     * amount (amountIn), not the output amount. This requires "reversing" the fee calculation:
     * we need to determine what input amount (after fees are deducted) will result in the
     * desired output amount reaching the recipient.
     *
     * The formula solves for the fee needed such that after Circle takes their percentage,
     * the recipient receives exactly `amount`:
     *
     *   (amount + fee) * (10_000 - maxFeeBps) / 10_000 = amount
     *
     * Solving for fee:
     *   fee = (amount * maxFeeBps) / (10_000 - maxFeeBps)
     *
     * Example: If amount = 100 USDC and maxFeeBps = 10 (0.1%):
     *   fee = (100 * 10) / (10_000 - 10) = 1000 / 9990 ≈ 0.1001 USDC
     *   We deposit 100.1001 USDC, Circle takes 0.1001 USDC, recipient gets exactly 100 USDC.
     */
    function _externalFeeAmount(
        uint32,
        bytes32,
        uint256 amount
    ) internal view override returns (uint256 feeAmount) {
        return (amount * maxFeeBps) / (10_000 - maxFeeBps);
    }

    function _getCCTPVersion() internal pure override returns (uint32) {
        return 1;
    }

    function _getCircleRecipient(
        bytes29 cctpMessage
    ) internal pure override returns (address) {
        return cctpMessage._getRecipient().bytes32ToAddress();
    }

    function _validateTokenMessage(
        bytes calldata hyperlaneMessage,
        bytes29 cctpMessage
    ) internal pure override {
        bytes29 burnMessage = cctpMessage._getMessageBody();
        burnMessage._validateBurnMessageFormat();

        bytes32 circleBurnSender = burnMessage._getMessageSender();
        require(
            circleBurnSender == hyperlaneMessage.sender(),
            "Invalid burn sender"
        );

        bytes calldata tokenMessage = hyperlaneMessage.body();

        require(
            TokenMessage.amount(tokenMessage) == burnMessage._getAmount(),
            "Invalid mint amount"
        );

        require(
            TokenMessage.recipient(tokenMessage) ==
                burnMessage._getMintRecipient(),
            "Invalid mint recipient"
        );
    }

    function _validateHookMessage(
        bytes calldata hyperlaneMessage,
        bytes29 cctpMessage
    ) internal pure override {
        bytes32 circleMessageId = cctpMessage._getMessageBody().index(0, 32);
        require(circleMessageId == hyperlaneMessage.id(), "Invalid message id");
    }

    // @inheritdoc IMessageHandlerV2
    function handleReceiveFinalizedMessage(
        uint32 sourceDomain,
        bytes32 sender,
        uint32 /*finalityThresholdExecuted*/,
        bytes calldata messageBody
    ) external override returns (bool) {
        return
            _receiveMessageId(
                sourceDomain,
                sender,
                abi.decode(messageBody, (bytes32))
            );
    }

    // @inheritdoc IMessageHandlerV2
    function handleReceiveUnfinalizedMessage(
        uint32 sourceDomain,
        bytes32 sender,
        uint32 /*finalityThresholdExecuted*/,
        bytes calldata messageBody
    ) external override returns (bool) {
        return
            _receiveMessageId(
                sourceDomain,
                sender,
                abi.decode(messageBody, (bytes32))
            );
    }

    function _sendMessageIdToIsm(
        uint32 destinationDomain,
        bytes32 ism,
        bytes32 messageId
    ) internal override {
        IMessageTransmitterV2(address(messageTransmitter)).sendMessage(
            destinationDomain,
            ism,
            bytes32(0), // allow anyone to relay
            minFinalityThreshold,
            abi.encode(messageId)
        );
    }

    function _bridgeViaCircle(
        uint32 circleDomain,
        bytes32 _recipient,
        uint256 _amount,
        uint256 _maxFee
    ) internal override {
        ITokenMessengerV2(address(tokenMessenger)).depositForBurn(
            _amount,
            circleDomain,
            _recipient,
            address(wrappedToken),
            bytes32(0), // allow anyone to relay
            _maxFee,
            minFinalityThreshold
        );
    }
}
"
    },
    "contracts/token/TokenBridgeCctpBase.sol": {
      "content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

import {TokenRouter} from "./libs/TokenRouter.sol";
import {HypERC20Collateral} from "./HypERC20Collateral.sol";
import {IMessageTransmitter} from "./../interfaces/cctp/IMessageTransmitter.sol";
import {IInterchainSecurityModule} from "./../interfaces/IInterchainSecurityModule.sol";
import {AbstractCcipReadIsm} from "./../isms/ccip-read/AbstractCcipReadIsm.sol";
import {TypedMemView} from "./../libs/TypedMemView.sol";
import {ITokenMessenger} from "./../interfaces/cctp/ITokenMessenger.sol";
import {Message} from "./../libs/Message.sol";
import {TokenMessage} from "./libs/TokenMessage.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol";
import {IMessageHandler} from "../interfaces/cctp/IMessageHandler.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {MovableCollateralRouter, MovableCollateralRouterStorage} from "./libs/MovableCollateralRouter.sol";
import {TokenRouter} from "./libs/TokenRouter.sol";
import {AbstractPostDispatchHook} from "../hooks/libs/AbstractPostDispatchHook.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

interface CctpService {
    function getCCTPAttestation(
        bytes calldata _message
    )
        external
        view
        returns (bytes memory cctpMessage, bytes memory attestation);
}

// need intermediate contract to insert slots between TokenRouter and AbstractCcipReadIsm
abstract contract TokenBridgeCctpBaseStorage is TokenRouter {
    /// @dev This is used to enable storage layout backwards compatibility. It should not be read or written to.
    MovableCollateralRouterStorage private __MOVABLE_COLLATERAL_GAP;
}

struct Domain {
    uint32 hyperlane;
    uint32 circle;
}

// see ./CCTP.md for sequence diagrams of the destination chain control flow
abstract contract TokenBridgeCctpBase is
    TokenBridgeCctpBaseStorage,
    AbstractCcipReadIsm,
    AbstractPostDispatchHook
{
    using Message for bytes;
    using TypeCasts for bytes32;
    using SafeERC20 for IERC20;

    uint256 private constant _SCALE = 1;

    IERC20 public immutable wrappedToken;

    // @notice CCTP message transmitter contract
    IMessageTransmitter public immutable messageTransmitter;

    // @notice CCTP token messenger contract
    ITokenMessenger public immutable tokenMessenger;

    /// @notice Hyperlane domain => Domain struct.
    /// We use a struct to avoid ambiguity with domain 0 being unknown.
    mapping(uint32 hypDomain => Domain circleDomain)
        internal _hyperlaneDomainMap;

    /// @notice Circle domain => Domain struct.
    // We use a struct to avoid ambiguity with domain 0 being unknown.
    mapping(uint32 circleDomain => Domain hyperlaneDomain)
        internal _circleDomainMap;

    /// @notice Maps messageId to whether or not the message has been verified
    /// by the CCTP message transmitter
    mapping(bytes32 messageId => bool verified) public isVerified;

    /**
     * @notice Emitted when the Hyperlane domain to Circle domain mapping is updated.
     * @param hyperlaneDomain The Hyperlane domain.
     * @param circleDomain The Circle domain.
     */
    event DomainAdded(uint32 indexed hyperlaneDomain, uint32 circleDomain);

    constructor(
        address _erc20,
        address _mailbox,
        IMessageTransmitter _messageTransmitter,
        ITokenMessenger _tokenMessenger
    ) TokenRouter(_SCALE, _mailbox) {
        require(
            _messageTransmitter.version() == _getCCTPVersion(),
            "Invalid messageTransmitter CCTP version"
        );
        messageTransmitter = _messageTransmitter;

        require(
            _tokenMessenger.messageBodyVersion() == _getCCTPVersion(),
            "Invalid TokenMessenger CCTP version"
        );
        tokenMessenger = _tokenMessenger;

        wrappedToken = IERC20(_erc20);

        _disableInitializers();
    }

    /**
     * @inheritdoc TokenRouter
     */
    function token() public view override returns (address) {
        return address(wrappedToken);
    }

    function initialize(
        address _hook,
        address _owner,
        string[] memory __urls
    ) external initializer {
        // ISM should not be set
        _MailboxClient_initialize(_hook, address(0), _owner);

        // Setup urls for offchain lookup and do token approval
        setUrls(__urls);
        wrappedToken.approve(address(tokenMessenger), type(uint256).max);
    }

    /**
     * @inheritdoc TokenRouter
     * @dev Overrides to bridge the tokens via Circle.
     */
    function transferRemote(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount
    ) public payable override returns (bytes32 messageId) {
        // 1. Calculate the fee amounts, charge the sender and distribute to feeRecipient if necessary
        (
            uint256 externalFee,
            uint256 remainingNativeValue
        ) = _calculateFeesAndCharge(
                _destination,
                _recipient,
                _amount,
                msg.value
            );

        // 2. Prepare the token message with the recipient, amount, and any additional metadata in overrides
        uint32 circleDomain = hyperlaneDomainToCircleDomain(_destination);
        uint256 burnAmount = _amount + externalFee;
        _bridgeViaCircle(circleDomain, _recipient, burnAmount, externalFee);

        bytes memory _message = TokenMessage.format(_recipient, burnAmount);
        // 3. Emit the SentTransferRemote event and 4. dispatch the message
        return
            _emitAndDispatch(
                _destination,
                _recipient,
                _amount, // no scaling needed for CCTP
                remainingNativeValue,
                _message
            );
    }

    function interchainSecurityModule()
        external
        view
        override
        returns (IInterchainSecurityModule)
    {
        return IInterchainSecurityModule(address(this));
    }

    /**
     * @notice Adds a new mapping between a Hyperlane domain and a Circle domain.
     * @param _hyperlaneDomain The Hyperlane domain.
     * @param _circleDomain The Circle domain.
     */
    function addDomain(
        uint32 _hyperlaneDomain,
        uint32 _circleDomain
    ) public onlyOwner {
        _hyperlaneDomainMap[_hyperlaneDomain] = Domain(
            _hyperlaneDomain,
            _circleDomain
        );
        _circleDomainMap[_circleDomain] = Domain(
            _hyperlaneDomain,
            _circleDomain
        );

        emit DomainAdded(_hyperlaneDomain, _circleDomain);
    }

    function addDomains(Domain[] memory domains) external onlyOwner {
        for (uint32 i = 0; i < domains.length; i++) {
            addDomain(domains[i].hyperlane, domains[i].circle);
        }
    }

    function hyperlaneDomainToCircleDomain(
        uint32 _hyperlaneDomain
    ) public view returns (uint32) {
        Domain memory domain = _hyperlaneDomainMap[_hyperlaneDomain];
        require(
            domain.hyperlane == _hyperlaneDomain,
            "Circle domain not configured"
        );

        return domain.circle;
    }

    function circleDomainToHyperlaneDomain(
        uint32 _circleDomain
    ) public view returns (uint32) {
        Domain memory domain = _circleDomainMap[_circleDomain];
        require(
            domain.circle == _circleDomain,
            "Hyperlane domain not configured"
        );

        return domain.hyperlane;
    }

    function _getCCTPVersion() internal pure virtual returns (uint32);

    function _getCircleRecipient(
        bytes29 cctpMessage
    ) internal pure virtual returns (address);

    function _validateTokenMessage(
        bytes calldata hyperlaneMessage,
        bytes29 cctpMessage
    ) internal pure virtual;

    function _validateHookMessage(
        bytes calldata hyperlaneMessage,
        bytes29 cctpMessage
    ) internal pure virtual;

    function _sendMessageIdToIsm(
        uint32 destinationDomain,
        bytes32 ism,
        bytes32 messageId
    ) internal virtual;

    /**
     * @dev Verifies that the CCTP message matches the Hyperlane message.
     */
    function verify(
        bytes calldata _metadata,
        bytes calldata _hyperlaneMessage
    ) external returns (bool) {
        // check if hyperlane message has already been verified by CCTP
        if (isVerified[_hyperlaneMessage.id()]) {
            return true;
        }

        // decode return type of CctpService.getCCTPAttestation
        (bytes memory cctpMessageBytes, bytes memory attestation) = abi.decode(
            _metadata,
            (bytes, bytes)
        );

        bytes29 cctpMessage = TypedMemView.ref(cctpMessageBytes, 0);
        address circleRecipient = _getCircleRecipient(cctpMessage);
        // check if CCTP message is a USDC burn message
        if (circleRecipient == address(tokenMessenger)) {
            // prevent hyperlane message recipient configured with CCTP ISM
            // from verifying and handling token messages
            require(
                _hyperlaneMessage.recipientAddress() == address(this),
                "Invalid token message recipient"
            );
            _validateTokenMessage(_hyperlaneMessage, cctpMessage);
        }
        // check if CCTP message is a GMP message to this contract
        else if (circleRecipient == address(this)) {
            _validateHookMessage(_hyperlaneMessage, cctpMessage);
        }
        // disallow other CCTP message destinations
        else {
            revert("Invalid circle recipient");
        }

        // for GMP messages, this.verifiedMessages[hyperlaneMessage.id()] will be set
        // for token messages, hyperlaneMessage.body().amount() tokens will be delivered to hyperlaneMessage.body().recipient()
        return messageTransmitter.receiveMessage(cctpMessageBytes, attestation);
    }

    function _receiveMessageId(
        uint32 circleSource,
        bytes32 circleSender,
        bytes32 messageId
    ) internal returns (bool) {
        require(
            msg.sender == address(messageTransmitter),
            "Not message transmitter"
        );

        // ensure that the message was sent from the hook on the origin chain
        uint32 origin = circleDomainToHyperlaneDomain(circleSource);
        require(
            _mustHaveRemoteRouter(origin) == circleSender,
            "Unauthorized circle sender"
        );

        isVerified[messageId] = true;

        return true;
    }

    function _offchainLookupCalldata(
        bytes calldata _message
    ) internal pure override returns (bytes memory) {
        return abi.encodeCall(CctpService.getCCTPAttestation, (_message));
    }

    /// @inheritdoc IPostDispatchHook
    function hookType() external pure override returns (uint8) {
        return uint8(IPostDispatchHook.HookTypes.CCTP);
    }

    /// @inheritdoc AbstractPostDispatchHook
    function _quoteDispatch(
        bytes calldata /*metadata*/,
        bytes calldata /*message*/
    ) internal pure override returns (uint256) {
        return 0;
    }

    /// @inheritdoc AbstractPostDispatchHook
    /// @dev Mirrors the logic in AbstractMessageIdAuthHook._postDispatch
    // but using Router table instead of hook <> ISM coupling
    function _postDispatch(
        bytes calldata metadata,
        bytes calldata message
    ) internal override {
        bytes32 id = message.id();
        require(_isLatestDispatched(id), "Message not dispatched");

        uint32 destination = message.destination();
        bytes32 ism = _mustHaveRemoteRouter(destination);
        uint32 circleDestination = hyperlaneDomainToCircleDomain(destination);

        _sendMessageIdToIsm(circleDestination, ism, id);

        _refund(metadata, message, address(this).balance);
    }

    /**
     * @inheritdoc TokenRouter
     * @dev Overrides to transfer the tokens from the sender to this contract (like HypERC20Collateral).
     */
    function _transferFromSender(uint256 _amount) internal override {
        wrappedToken.safeTransferFrom(msg.sender, address(this), _amount);
    }

    /**
     * @inheritdoc TokenRouter
     * @dev Overrides to not transfer the tokens to the recipient, as the CCTP transfer will do it.
     */
    function _transferTo(
        address _recipient,
        uint256 _amount
    ) internal override {
        // do not transfer to recipient as the CCTP transfer will do it
    }

    /**
     * @inheritdoc TokenRouter
     * @dev Overrides to transfer fees directly from the router balance since CCTP handles token delivery.
     */
    function _transferFee(
        address _recipient,
        uint256 _amount
    ) internal override {
        wrappedToken.safeTransfer(_recipient, _amount);
    }

    function _bridgeViaCircle(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount,
        uint256 _maxFee
    ) internal virtual;
}
"
    },
    "contracts/token/libs/TokenRouter.sol": {
      "content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;

// ============ Internal Imports ============
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {GasRouter} from "../../client/GasRouter.sol";
import {TokenMessage} from "./TokenMessage.sol";
import {Quote, ITokenBridge, ITokenFee} from "../../interfaces/ITokenBridge.sol";
import {Quotes} from "./Quotes.sol";
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";

/**
 * @title Hyperlane Token Router that extends Router with abstract token (ERC20/ERC721) remote transfer functionality.
 * @dev Overridable functions:
 *  - token(): specify the managed token address
 *  - _transferFromSender(uint256): pull tokens/ETH from msg.sender
 *  - _transferTo(address,uint256): send tokens/ETH to the recipient
 *  - _externalFeeAmount(uint32,bytes32,uint256): compute external fees (default returns 0)
 * @dev Override transferRemote only to implement custom logic that can't be accomplished with the above functions.
 *
 * @author Abacus Works
 */
abstract contract TokenRouter is GasRouter, ITokenBridge {
    using TypeCasts for bytes32;
    using TypeCasts for address;
    using TokenMessage for bytes;
    using StorageSlot for bytes32;
    using Quotes for Quote[];

    /**
     * @dev Emitted on `transferRemote` when a transfer message is dispatched.
     * @param destination The identifier of the destination chain.
     * @param recipient The address of the recipient on the destination chain.
     * @param amountOrId The amount or ID of tokens sent in to the remote recipient.
     */
    event SentTransferRemote(
        uint32 indexed destination,
        bytes32 indexed recipient,
        uint256 amountOrId
    );

    /**
     * @dev Emitted on `_handle` when a transfer message is processed.
     * @param origin The identifier of the origin chain.
     * @param recipient The address of the recipient on the destination chain.
     * @param amountOrId The amount or ID of tokens received from the remote sender.
     */
    event ReceivedTransferRemote(
        uint32 indexed origin,
        bytes32 indexed recipient,
        uint256 amountOrId
    );

    uint256 public immutable scale;

    // cannot use compiler assigned slot without
    // breaking backwards compatibility of storage layout
    bytes32 private constant FEE_RECIPIENT_SLOT =
        keccak256("FungibleTokenRouter.feeRecipient");

    event FeeRecipientSet(address feeRecipient);

    constructor(uint256 _scale, address _mailbox) GasRouter(_mailbox) {
        scale = _scale;
    }

    // ===========================
    // ========== Main API ==========
    // ===========================

    /**
     * @notice Returns the address of the token managed by this router. It can be one of three options:
     * - ERC20 token address for fungible tokens that are being collateralized (HypERC20Collateral, HypERC4626, etc.)
     * - 0x0 address for native tokens (ETH, MATIC, etc.) (HypNative, etc.)
     * - address(this) for synthetic ERC20 tokens (HypERC20, etc.)
     * It is being used for quotes and fees from the fee recipient and pulling/push the tokens from the sender/receipient.
     * @dev This function must be implemented by derived contracts to specify the token address.
     * @return The address of the token contract.
     */
    function token() public view virtual returns (address);

    /**
     * @inheritdoc ITokenFee
     * @notice Implements the standardized fee quoting interface for token transfers based on
     * overridable internal functions of _quoteGasPayment, _feeRecipientAndAmount, and _externalFeeAmount.
     * @param _destination The identifier of the destination chain.
     * @param _recipient The address of the recipient on the destination chain.
     * @param _amount The amount or identifier of tokens to be sent to the remote recipient
     * @return quotes An array of Quote structs representing the fees in different tokens.
     * @dev This function may return multiple quotes with the same denomination. Convention is to return:
     *  [index 0] native fees charged by the mailbox dispatch
     *  [index 1] then any internal warp route fees (amount bridged plus fee recipient)
     *  [index 2] then any external bridging fees (if any, else 0)
     * These are surfaced as separate elements to enable clients to interpret/render fees independently.
     * There is a Quotes library with an extract function for onchain quoting in a specific denomination,
     * but we discourage onchain quoting in favor of offchain quoting and overpaying with refunds.
     */
    function quoteTransferRemote(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount
    ) external view override returns (Quote[] memory quotes) {
        quotes = new Quote[](3);
        quotes[0] = Quote({
            token: address(0),
            amount: _quoteGasPayment(_destination, _recipient, _amount)
        });
        (, uint256 feeAmount) = _feeRecipientAndAmount(
            _destination,
            _recipient,
            _amount
        );
        quotes[1] = Quote({token: token(), amount: _amount + feeAmount});
        quotes[2] = Quote({
            token: token(),
            amount: _externalFeeAmount(_destination, _recipient, _amount)
        });
    }

    /**
     * @notice Transfers `_amount` token to `_recipient` on the `_destination` domain.
     * @dev Delegates transfer logic to `_transferFromSender` implementation.
     * Emits `SentTransferRemote` event on the origin chain.
     * Override with custom behavior for storing or forwarding tokens.
     * Known overrides:
     * - OPL2ToL1TokenBridgeNative: adds hook metadata for message dispatch.
     * - EverclearTokenBridge: creates Everclear intent for cross-chain token transfer.
     * - TokenBridgeCctpBase: adds CCTP-specific metadata for message dispatch.
     * - HypERC4626Collateral: deposits into vault and handles shares.
     * When overriding, mirror the general flow of this function for consistency:
     * 1. Calculate fees and charge the sender.
     * 2. Prepare the token message with recipient, amount, and any additional metadata.
     * 3. Emit `SentTransferRemote` event.
     * 4. Dispatch the message.
     * @param _destination The identifier of the destination chain.
     * @param _recipient The address of the recipient on the destination chain.
     * @param _amount The amount or identifier of tokens to be sent to the remote recipient.
     * @return messageId The identifier of the dispatched message.
     */
    function transferRemote(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount
    ) public payable virtual returns (bytes32 messageId) {
        // 1. Calculate the fee amounts, charge the sender and distribute to feeRecipient if necessary
        (, uint256 remainingNativeValue) = _calculateFeesAndCharge(
            _destination,
            _recipient,
            _amount,
            msg.value
        );

        uint256 scaledAmount = _outboundAmount(_amount);

        // 2. Prepare the token message with the recipient and amount
        bytes memory _tokenMessage = TokenMessage.format(
            _recipient,
            scaledAmount
        );

        // 3. Emit the SentTransferRemote event and 4. dispatch the message
        return
            _emitAndDispatch(
                _destination,
                _recipient,
                scaledAmount,
                remainingNativeValue,
                _tokenMessage
            );
    }

    // ===========================
    // ========== Internal convenience functions for readability ==========
    // ==========================
    function _calculateFeesAndCharge(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount,
        uint256 _msgValue
    ) internal returns (uint256 externalFee, uint256 remainingNativeValue) {
        (address _feeRecipient, uint256 feeAmount) = _feeRecipientAndAmount(
            _destination,
            _recipient,
            _amount
        );
        externalFee = _externalFeeAmount(_destination, _recipient, _amount);
        uint256 charge = _amount + feeAmount + externalFee;
        _transferFromSender(charge);
        if (feeAmount > 0) {
            // transfer atomically so we don't need to keep track of collateral
            // and fee balances separately
            _transferFee(_feeRecipient, feeAmount);
        }
        remainingNativeValue = token() != address(0)
            ? _msgValue
            : _msgValue - charge;
    }

    // Emits the SentTransferRemote event and dispatches the message.
    function _emitAndDispatch(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount,
        uint256 _messageDispatchValue,
        bytes memory _tokenMessage
    ) internal returns (bytes32 messageId) {
        // effects
        emit SentTransferRemote(_destination, _recipient, _amount);

        // interactions
        messageId = _Router_dispatch(
            _destination,
            _messageDispatchValue,
            _tokenMessage,
            _GasRouter_hookMetadata(_destination),
            address(hook)
        );
    }

    // ===========================
    // ========== Fees & Quoting ==========
    // ===========================

    /**
     * @notice Sets the fee recipient for the router.
     * @dev Allows for address(0) to be set, which disables fees.
     * @param recipient The address that receives fees.
     */
    function setFeeRecipient(address recipient) public onlyOwner {
        require(recipient != address(this), "Fee recipient cannot be self");
        FEE_RECIPIENT_SLOT.getAddressSlot().value = recipient;
        emit FeeRecipientSet(recipient);
    }

    /**
     * @notice Returns the address of the fee recipient.
     * @dev Returns address(0) if no fee recipient is set.
     * @dev Can be overridden with address(0) to disable fees entirely.
     * @return address of the fee recipient.
     */
    function feeRecipient() public view virtual returns (address) {
        return FEE_RECIPIENT_SLOT.getAddressSlot().value;
    }

    // To be overridden by derived contracts if they have additional fees
    /**
     * @notice Returns the external fee amount for the given parameters.
     * param _destination The identifier of the destination chain.
     * param _recipient The address of the recipient on the destination chain.
     * param _amount The amount or identifier of tokens to be sent to the remote recipient
     * @return feeAmount The external fee amount.
     * @dev This fee must be denominated in the `token()` defined by this router.
     * @dev The default implementation returns 0, meaning no external fees are charged.
     * This function is intended to be overridden by derived contracts that have additional fees.
     * Known overrides:
     * - TokenBridgeCctpBase: for CCTP-specific fees
     * - EverclearTokenBridge: for Everclear-specific fees
     */
    function _externalFeeAmount(
        uint32, // _destination,
        bytes32, // _recipient,
        uint256 // _amount
    ) internal view virtual returns (uint256 feeAmount) {
        return 0;
    }

    /**
     * @notice Returns the fee recipient amount for the given parameters.
     * @param _destination The identifier of the destination chain.
     * @param _recipient The address of the recipient on the destination chain.
     * @param _amount The amount or identifier of tokens to be sent to the remote recipient
     * @return _feeRecipient The address of the fee recipient.
     * @return feeAmount The fee recipient amount.
     * @dev This function is is not intended to be overridden as storage and logic is contained in TokenRouter.
     */
    function _feeRecipientAndAmount(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount
    ) internal view returns (address _feeRecipient, uint256 feeAmount) {
        _feeRecipient = feeRecipient();
        if (_feeRecipient == address(0)) {
            return (_feeRecipient, 0);
        }

        Quote[] memory quotes = ITokenFee(_feeRecipient).quoteTransferRemote(
            _destination,
            _recipient,
            _amount
        );
        if (quotes.length == 0) {
            return (_feeRecipient, 0);
        }

        require(
            quotes.length == 1 && quotes[0].token == token(),
            "FungibleTokenRouter: fee must match token"
        );
        feeAmount = quotes[0].amount;
    }

    /**
     * @notice Returns the gas payment required to dispatch a message to the given domain's router.
     * @param _destination The identifier of the destination chain.
     * @param _recipient The address of the recipient on the destination chain.
     * @param _amount The amount or identifier of tokens to be sent to the remote recipient
     * @return payment How much native value to send in transferRemote call.
     * @dev This function is intended to be overridden by derived contracts that trigger multiple messages.
     * Known overrides:
     * - OPL2ToL1TokenBridgeNative: Quote for two messages (prove and finalize).
     */
    function _quoteGasPayment(
        uint32 _destination,
        bytes32 _recipient,
        uint256 _amount
    ) internal view virtual returns (uint256) {
        return
            _Router_quoteDispatch(
                _destination,
                TokenMessage.format(_recipient, _amount),
                _GasRouter_hookMetadata(_destination),
                address(hook)
            );
    }

    // ===========================
    // ========== Internal virtual functions for token handling ==========
    // ===========================

    /**
     * @dev Should transfer `_amount` of tokens from `msg.sender` to this token router.
     * Called by `transferRemote` before message dispatch.
     * Known overrides:
     * - HypERC20: Burns the tokens from the sender.
     * - HypERC20Collateral: Pulls the tokens from the sender.
     * - HypNative: Asserts msg.value >= _amount
     * - TokenBridgeCctpBase: (like HypERC20Collateral) Pulls the tokens from the sender.
     * - EverclearEthTokenBridge: Wraps the native token (ETH) to WETH
     * - HypERC4626: Converts the amounts to shares and burns from the User (via HypERC20 implementation)
     * - HypFiatToken: Pulls the tokens from the sender and burns them on the FiatToken contract.
     * - HypXERC20: Burns the tokens from the sender.
     * - HypXERC20Lockbox: Pulls the tokens from the sender, locks them in the XERC20Lockbox contract and burns the resulting xERC20 tokens.
     */
    function _transferFromSender(uint256 _amountOrId) internal virtual;

    /**
     * @dev Should transfer `_amountOrId` of tokens from this token router to `_recipient`.
     * @dev Called by `handle` after message decoding.
     * Known overrides:
     * - HypERC20: Mints the tokens to the recipient.
     * - HypERC20Collateral: Releases the tokens to the recipient.
     * - HypNative: Releases native tokens to the recipient.
     * - TokenBridgeCctpBase: Do nothing (CCTP transfers tokens to the recipient directly).
     * - EverclearEthTokenBridge: Unwraps WETH to ETH and sends to the recipient.
     * - HypERC4626: Converts the amount to shares and mints to the User (via HypERC20 implementation)
     * - HypFiatToken: Mints the tokens to the recipient on the FiatToken contract.
     * - HypXERC20: Mints the tokens to the recipient.
     * - HypXERC20Lockbox: Withdraws the underlying tokens from the Lockbox and sends to the recipient.
     * - OpL1NativeTokenBridge: Do nothing (the L2 bridge transfers the native tokens to the recipient directly).
     */
    function _transferTo(
        address _recipient,
        uint256 _amountOrId
    ) internal virtual;

    /**
     * @dev Should transfer `_amount` of tokens from this token router to the fee recipient.
     * @dev Called by `_calculateFeesAndCharge` when fee recipient is set and feeAmount > 0.
     * @dev The default implementation delegates to `_transferTo`, which works for most token routers
     * where tokens are held by the router (e.g., collateral routers, synthetic token routers).
     * @dev Override this function for bridges where tokens are NOT held by the router but fees still
     * need to be paid (e.g., CCTP, Everclear). In those cases, use direct token transfers from the
     * router's balance collected via `_transferFromSender`.
     * Known overrides:
     * - TokenBridgeCctpBase: Directly transfers tokens from router balance.
     * - EverclearTokenBridge: Directly transfers tokens from router balance.
     */
    function _transferFee(
        address _recipient,
        uint256 _amount
    ) internal virtual {
        _transferTo(_recipient, _amount);
    }

    /**
     * @dev Scales local amount to message amount (up by scale factor).
     * Known overrides:
     * - HypERC4626: Scales by exchange rate
     */
    function _outboundAmount(
        uint256 _localAmount
    ) internal view virtual returns (uint256 _messageAmount) {
        _messageAmount = _localAmount * scale;
    }

    /**
     * @dev Scales message amount to local amount (down by scale factor).
     * Known overrides:
     * - HypERC4626: Scales by exchange rate
     */
    function _inboundAmount(
        uint256 _messageAmount
    ) internal view virtual returns (uint256 _localAmount) {
        _localAmount = _messageAmount / scale;
    }

    /**
     * @notice Handles the incoming transfer message.
     * It decodes the message, emits the ReceivedTransferRemote event, and transfers tokens to the recipient.
     * @param _origin The identifier of the origin chain.
     * @dev param _sender The address of the sender router on the origin chain.
     * @param _message The message data containing recipient and amount.
     * @dev Override this function if custom logic is required for sending out the tokens.
     * Known overrides:
     * - EverclearTokenBridge: Receives the tokens and sends them to the recipient.
     * - EverclearEthBridge: Receives WETH, unwraps it and sends native ETH to the recipient.
     * - HypERC4626: Updates the exchange rate from the metadata
     */
    // solhint-disable-next-line hyperlane/no-virtual-override
    function _handle(
        uint32 _origin,
        bytes32,
        bytes calldata _message
    ) internal virtual override {
        bytes32 recipient = _message.recipient();
        uint256 amount = _message.amount();

        // effects
        emit ReceivedTransferRemote(_origin, recipient, amount);

        // interactions
        _transferTo(recipient.bytes32ToAddress(), _inboundAmount(amount));
    }
}
"
    },
    "contracts/libs/TypedMemView.sol": {
      "content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.12;

library TypedMemView {
    // Why does this exist?
    // the solidity `bytes memory` type has a few weaknesses.
    // 1. You can't index ranges effectively
    // 2. You can't slice without copying
    // 3. The underlying data may represent any type
    // 4. Solidity never deallocates memory, and memory costs grow
    //    superlinearly

    // By using a memory view instead of a `bytes memory` we get the following
    // advantages:
    // 1. Slices are done on the stack, by manipulating the pointer
    // 2. We can index arbitrary ranges and quickly convert them to stack types
    // 3. We can insert type info into the pointer, and typecheck at runtime

    // This makes `TypedMemView` a useful tool for efficient zero-copy
    // algorithms.

    // Why bytes29?
    // We want to avoid confusion between views, digests, and other common
    // types so we chose a large and uncommonly used odd number of bytes
    //
    // Note that while bytes are left-aligned in a word, integers and addresses
    // are right-aligned. This means when working in assembly we have to
    // account for the 3 unused bytes on the righthand side
    //
    // First 5 bytes are a type flag.
    // - ff_ffff_fffe is reserved for unknown type.
    // - ff_ffff_ffff is reserved for invalid types/errors.
    // next 12 are memory address
    // next 12 are len
    // bottom 3 bytes are empty

    // Assumptions:
    // - non-modification of memory.
    // - No Solidity updates
    // - - wrt free mem point
    // - - wrt bytes representation in memory
    // - - wrt memory addressing in general

    // Usage:
    // - create type constants
    // - use `assertType` for runtime type assertions
    // - - unfortunately we can't do this at compile time yet :(
    // - recommended: implement modifiers that perform type checking
    // - - e.g.
    // - - `uint40 constant MY_TYPE = 3;`
    // - - ` modifier onlyMyType(bytes29 myView) { myView.assertType(MY_TYPE); }`
    // - instantiate a typed view from a bytearray using `ref`
    // - use `index` to inspect the contents of the view
    // - use `slice` to create smaller views into the same memory
    // - - `slice` can increase the offset
    // - - `slice can decrease the length`
    // - - must specify the output type of `slice`
    // - - `slice` will return a null view if you try to overrun
    // - - make sure to explicitly check for this with `notNull` or `assertType`
    // - use `equal` for typed comparisons.

    // The null view
    bytes29 public constant NULL =
        hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
    // Mask a low uint96
    uint256 constant LOW_12_MASK = 0xffffffffffffffffffffffff;
    // Shift constants
    uint8 constant SHIFT_TO_LEN = 24;
    uint8 constant SHIFT_TO_LOC = 96 + 24;
    uint8 constant SHIFT_TO_TYPE = 96 + 96 + 24;
    // For nibble encoding
    bytes private constant NIBBLE_LOOKUP = "0123456789abcdef";

    /**
     * @notice Returns the encoded hex character that represents the lower 4 bits of the argument.
     * @param _byte The byte
     * @return _char The encoded hex character
     */
    function nibbleHex(uint8 _byte) internal pure returns (uint8 _char) {
        uint8 _nibble = _byte & 0x0f; // keep bottom 4, 0 top 4
        _char = uint8(NIBBLE_LOOKUP[_nibble]);
    }

    /**
     * @notice      Returns a uint16 containing the hex-encoded byte.
     * @param _b    The byte
     * @return      encoded - The hex-encoded byte
     */
    function byteHex(uint8 _b) internal pure returns (uint16 encoded) {
        encoded |= nibbleHex(_b >> 4); // top 4 bits
        encoded <<= 8;
        encoded |= nibbleHex(_b); // lower 4 bits
    }

    /**
     * @notice      Encodes the uint256 to hex. `first` contains the encoded top 16 bytes.
     *              `second` contains the encoded lower 16 bytes.
     *
     * @param _b    The 32 bytes as uint256
     * @return      first - The top 16 bytes
     * @return      second - The bottom 16 bytes
     */
    function encodeHex(
        uint256 _b
    ) internal pure returns (uint256 first, uint256 second) {
        for (uint8 i = 31; i > 15; ) {
            uint8 _byte = uint8(_b >> (i * 8));
            first |= byteHex(_byte);
            if (i != 16) {
                first <<= 16;
            }
            unchecked {
                i -= 1;
            }
        }

        // abusing underflow here =_=
        for (uint8 i = 15; i < 255; ) {
            uint8 _byte = uint8(_b >> (i * 8));
            second |= byteHex(_byte);
            if (i != 0) {
                second <<= 16;
            }
            unchecked {
                i -= 1;
            }
        }
    }

    /**
     * @notice          Changes the endianness of a uint256.
     * @dev             https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
     * @param _b        The unsigned integer to reverse
     * @return          v - The reversed value
     */
    function reverseUint256(uint256 _b) internal pure returns (uint256 v) {
        v = _b;

        // swap bytes
        v =
            ((v >> 8) &
                0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |
            ((v &
                0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) <<
                8);
        // swap 2-byte long pairs
        v =
            ((v >> 16) &
                0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |
            ((v &
                0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) <<
                16);
        // swap 4-byte long pairs
        v =
            ((v >> 32) &
                0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |
            ((v &
                0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) <<
                32);
        // swap 8-byte long pairs
        v =
            ((v >> 64) &
                0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |
            ((v &
                0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) <<
                64);
        // swap 16-byte long pairs
        v = (v >> 128) | (v << 128);
    }

    /**
     * @notice      Create a mask with the highest `_len` bits set.
     * @param _len  The length
     * @return      mask - The mask
     */
    function leftMask(uint8 _len) private pure returns (uint256 mask) {
        // ugly. redo without assembly?
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            mask := sar(
                sub(_len, 1),
                0x8000000000000000000000000000000000000000000000000000000000000000
            )
        }
    }

    /**
     * @notice      Return the null view.
     * @return      bytes29 - The null view
     */
    function nullView() internal pure returns (bytes29) {
        return NULL;
    }

    /**
     * @notice      Check if the view is null.
     * @return      bool - True if the view is null
     */
    function isNull(bytes29 memView) internal pure returns (bool) {
        return memView == NULL;
    }

    /**
     * @notice      Check if the view is not null.
     * @return      bool - True if the view is not null
     */
    function notNull(bytes29 memView) internal pure returns (bool) {
        return !isNull(memView);
    }

    /**
     * @notice          Check if the view is of a valid type and points to a valid location
     *                  in memory.
     * @dev             We perform this check by examining solidity's unallocated memory
     *                  pointer and ensuring that the view's upper bound is less than that.
     * @param memView   The view
     * @return          ret - True if the view is valid
     */
    function isValid(bytes29 memView) internal pure returns (bool ret) {
        if (typeOf(memView) == 0xffffffffff) return false;
        uint256 _end = end(memView);
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            ret := iszero(gt(_end, mload(0x40)))
        }
    }

    /**
     * @notice          Require that a typed memory view be valid.
     * @dev             Returns the view for easy chaining.
     * @param memView   The view
     * @return          bytes29 - The validated view
     */
    function assertValid(bytes29 memView) internal pure returns (bytes29) {
        require(isValid(memView), "Validity assertion failed");
        return memView;
    }

    /**
     * @notice          Return true if the memview is of the expected type. Otherwise false.
     * @param memView   The view
     * @param _expected The expected type
     * @return          bool - True if the memview is of the expected type
     */
    function isType(
        bytes29 memView,
        uint40 _expected
    ) internal pure returns (bool) {
        return typeOf(memView) == _expected;
    }

    /**
     * @notice          Require that a typed memory view has a specific type.
     * @dev             Returns the view for easy chaining.
     * @param memView   The view
     * @param _expected The expected type
     * @return          bytes29 - The view with validated type
     */
    function assertType(
        bytes29 memView,
        uint40 _expected
    ) internal pure returns (bytes29) {
        if (!isType(memView, _expected)) {
            (, uint256 g) = encodeHex(uint256(typeOf(memView)));
            (, uint256 e) = encodeHex(uint256(_expected));
            string memory err = string(
                abi.encodePacked(
                    "Type assertion failed. Got 0x",
                    uint80(g),
                    ". Expected 0x",
                    uint80(e)
                )
            );
            revert(err);
        }
        return memView;
    }

    /**
     * @notice          Return an identical view with a different type.
     * @param memView   The view
     * @param _newType  The new type
     * @return          newView - The new view with the specified type
     */
    function castTo(
        bytes29 memView,
        uint40 _newType
    ) internal pure returns (bytes29 newView) {
        // then | in the new type
        uint256 _typeShift = SHIFT_TO_TYPE;
        uint256 _typeBits = 40;
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            // shift off the top 5 bytes
            newView := or(newView, shr(_typeBits, shl(_typeBits, memView)))
            newView := or(newView, shl(_typeShift, _newType))
        }
    }

    /**
     * @notice          Unsafe raw pointer construction. This should generally not be called
     *                  directly. Prefer `ref` wherever possible.
     * @dev             Unsafe raw pointer construction. This should generally not be called
     *                  directly. Prefer `ref` wherever possible.
     * @param _type     The type
     * @param _loc      The memory address
     * @param _len      The length
     * @return          newView - The new view with the specified type, location and length
     */
    function unsafeBuildUnchecked(
        uint256 _type,
        uint256 _loc,
        uint256 _len
    ) private pure returns (bytes29 newView) {
        uint256 _uint96Bits = 96;
        uint256 _emptyBits = 24;
        assembly {
            // solium-disable-previous-line security/no-inline-assembly
            newView := shl(_uint96Bits, or(newView, _type)) // insert type
            newView := shl(_uint96Bits, or(newView, _loc)) // insert loc
            newView := shl(_emptyBits, or(newView, _len)) // empty bottom 3 bytes
        }
    }

    /**
     * @notice          Instantiate a new memory view. This should generally not be called
     *                  directly. Prefer `ref` wherever possible.
     * @dev             Instantiate a new memory view. This should generally not be called
     *                  directly. Prefer `ref` wherever possible.
     * @param _type     The type
     * @param _loc      The memory address
     * @param _len      The length
     * @return          newView - The new view with the specified type, location and length
     */
    function build(
        uint256 _type,
        uint256 _loc,
        uint256 _len
    ) internal pure returns (bytes29 newView) {
        uint256 _end = _loc + _len;
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            if gt(_end, mload(0x40)) {
                _end := 0
            }
        }
        if (_end == 0) {
            return NULL;
        }
        newView = unsafeBuildUnchecked(_type, _loc, _len);
    }

    /**
     * @notice          Instantiate a memory view from a byte array.
     * @dev             Note that due to Solidity memory representation, it is not possible to
     *                  implement a deref, as the `bytes` type stores its len in memory.
     * @param arr       The byte array
     * @param newType   The type
     * @return          bytes29 - The memory view
     */
    function ref(
        bytes memory arr,
        uint40 newType
    ) internal pure returns (bytes29) {
        uint256 _len = arr.length;

        uint256 _loc;
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            _loc := add(arr, 0x20) // our view is of the data, not the struct
        }

        return build(newType, _loc, _len);
    }

    /**
     * @notice          Return the associated type information.
     * @param memView   The memory view
     * @return          _type - The type associated with the view
     */
    function typeOf(bytes29 memView) internal pure returns (uint40 _type) {
        uint256 _shift = SHIFT_TO_TYPE;
        assembly {
            // solium-disable-previous-line security/no-inline-assembly
            _type := shr(_shift, memView) // shift out lower 27 bytes
        }
    }

    /**
     * @notice          Optimized type comparison. Checks that the 5-byte type flag is equal.
     * @param left      The first view
     * @param right     The second view
     * @return          bool - True if the 5-byte type flag is equal
     */
    function sameType(
        bytes29 left,
        bytes29 right
    ) internal pure returns (bool) {
        return (left ^ right) >> SHIFT_TO_TYPE == 0;
    }

    /**
     * @notice          Return the memory address of the underlying bytes.
     * @param memView   The view
     * @return          _loc - The memory address
     */
    function loc(bytes29 memView) internal pure returns (uint96 _loc) {
        uint256 _mask = LOW_12_MASK; // assembly can't use globals
        uint256 _shift = SHIFT_TO_LOC;
        assembly {
            // solium-disable-previous-line security/no-inline-assembly
            _loc := and(shr(_shift, memView), _mask)
        }
    }

    /**
     * @notice          The number of memory words this memory view occupies, rounded up.
     * @param memView   The view
     * @return          uint256 - The number of memory words
     */
    function words(bytes29 memView) internal pure returns (uint256) {
        return (uint256(len(memView)) + 31) / 32;
    }

    /**
     * @notice          The in-memory footprint of a fresh copy of the view.
     * @param memView   The view
     * @return          uint256 - The in-memory footprint of a fresh copy of the view.
     */
    function footprint(bytes29 memView) internal pure returns (uint256) {
        return words(memView) * 32;
    }

    /**
     * @notice          The number of bytes of the view.
     * @param memView   The view
     * @return          _len - The length of the view
     */
    function len(bytes29 memView) internal pure returns (uint96 _len) {
        uint256 _mask = LOW_12_MASK; // assembly can't use globals
        uint256 _emptyBits = 24;
        assembly {
            // solium-disable-previous-line security/no-inline-assembly
            _len := and(shr(_emptyBits, memView), _mask)
        }
    }

    /**
     * @notice          Returns the endpoint of `memView`.
     * @param memView   The view
     * @return          uint256 - The endpoint of `memView`
     */
    function end(bytes29 memView) internal pure returns (uint256) {
        unchecked {
            return loc(memView) + len(memView);
        }
    }

    /**
     * @notice          Safe slicing without memory modification.
     * @param memView   The view
     * @param _index    The start index
     * @param _len      The length
     * @param newType   The new type
     * @return          bytes29 - The new view
     */
    function slice(
        bytes29 memView,
        uint256 _index,
        uint256 _len,
        uint40 newType
    ) internal pure returns (bytes29) {
        uint256 _loc = loc(memView);

        // Ensure it doesn't overrun the view
        if (_loc + _index + _len > end(memView)) {
            return NULL;
        }

        _loc = _loc + _index;
        return build(newType, _loc, _len);
    }

    /**
     * @notice          Shortcut to `slice`. Gets a view representing the first `_len` bytes.
     * @param memView   The view
     * @param _len      The length
     * @param newType   The new type
     * @return          bytes29 - The new view
     */
    function prefix(
        bytes29 memView,
        uint256 _len,
        uint40 newType
    ) internal pure returns (bytes29) {
        return slice(memView, 0, _len, newType);
    }

    /**
     * @notice          Shortcut to `slice`. Gets a view representing the last `_len` byte.
     * @param memView   The view
     * @param _len      The length
     * @param newType   The new type
     * @return          bytes29 - The new view
     */
    function postfix(
        bytes29 memView,
        uint256 _len,
        uint40 newType
    ) internal pure returns (bytes29) {
        return slice(memView, uint256(len(memView)) - _len, _len, newType);
    }

    /**
     * @notice          Construct an error message for an indexing overrun.
     * @param _loc      The memory address
     * @param _len      The length
     * @param _index    The index
     * @param _slice    The slice where the overrun occurred
     * @return          err - The err
     */
    function indexErrOverrun(
        uint256 _loc,
        uint256 _len,
        uint256 _index,
        uint256 _slice
    ) internal pure returns (string memory err) {
        (, uint256 a) = encodeHex(_loc);
        (, uint256 b) = encodeHex(_len);
        (, uint256 c) = encodeHex(_index);
        (, uint256 d) = encodeHex(_slice);
        err = string(
            abi.encodePacked(
                "TypedMemView/index - Overran the view. Slice is at 0x",
                uint48(a),
                " with length 0x",
                uint48(b),
                ". Attempted to index at offset 0x",
                uint48(c),
                " with length 0x",
                uint48(d),
                "."
            )
        );
    }

    /**
     * @notice          Load up to 32 bytes from the view onto the stack.
     * @dev             Returns a bytes32 with only the `_bytes` highest bytes set.
     *                  This can be immediately cast to a smaller fixed-length byte array.
     *                  To automatically cast to an integer, use `indexUint`.
     * @param memView   The view
     * @param _index    The index
     * @param _bytes    The bytes
     * @return          result - The 32 byte result
     */
    function index(
        bytes29 memView,
        uint256 _index,
        uint8 _bytes
    ) internal pure returns (bytes32 result) {
        if (_bytes == 0) return bytes32(0);
        if (_index + _bytes > len(memView)) {
            revert(
                indexErrOverrun(
                    loc(memView),
                    len(memView),
                    _index,
                    uint256(_bytes)
                )
            );
        }
        require(
            _bytes <= 32,
            "TypedMemView/index - Attempted to index more than 32 bytes"
        );

        uint8 bitLength;
        unchecked {
            bitLength = _bytes * 8;
        }
        uint256 _loc = loc(memView);
        uint256 _mask = leftMask(bitLength);
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            result := and(mload(add(_loc, _index)), _mask)
        }
    }

    /**
     * @notice          Parse an unsigned integer from the view at `_index`.
     * @dev             Requires that the view have >= `_bytes` bytes following that index.
     * @param memView   The view
     * @param _index    The index
     * @param _bytes    The bytes
     * @return          result - The unsigned integer
     */
    function indexUint(
        bytes29 memView,
        uint256 _index,
        uint8 _bytes
    ) internal pure returns (uint256 result) {
        return uint256(index(memView, _index, _bytes)) >> ((32 - _bytes) * 8);
    }

    /**
     * @notice          Parse an unsigned integer from LE bytes.
     * @param memView   The view
     * @param _index    The index
     * @param _bytes    The bytes
     * @return          result - The unsigned integer
     */
    function indexLEUint(
        bytes29 memView,
        uint256 _index,
        uint8 _bytes
    ) internal pure returns (uint256 result) {
        return reverseUint256(uint256(index(memView, _index, _bytes)));
    }

    /**
     * @notice          Parse an address from the view at `_index`. Requires that the view have >= 20 bytes
     *                  following that index.
     * @param memView   The view
     * @param _index    The index
     * @return          address - The address
     */
    function indexAddress(
        bytes29 memView,
        uint256 _index
    ) internal pure returns (address) {
        return address(uint160(indexUint(memView, _index, 20)));
    }

    /**
     * @notice          Return the keccak256 hash of the underlying memory
     * @param memView   The view
     * @return          digest - The keccak256 hash of the underlying memory
     */
    function keccak(bytes29 memView) internal pure returns (bytes32 digest) {
        uint256 _loc = loc(memView);
        uint256 _len = len(memView);
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            digest := keccak256(_loc, _len)
        }
    }

    /**
     * @notice          Return the sha2 digest of the underlying memory.
     * @dev             We explicitly deallocate memory afterwards.
     * @param memView   The view
     * @return          digest - The sha2 hash of the underlying memory
     */
    function sha2(bytes29 memView) internal view returns (bytes32 digest) {
        uint256 _loc = loc(memView);
        uint256 _len = len(memView);

        bool res;
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            let ptr := mload(0x40)
            res := staticcall(gas(), 2, _loc, _len, ptr, 0x20) // sha2 #1
            digest := mload(ptr)
        }
        require(res, "sha2 OOG");
    }

    /**
     * @notice          Implements bitcoin's hash160 (rmd160(sha2()))
     * @param memView   The pre-image
     * @return          digest - the Digest
     */
    function hash160(bytes29 memView) internal view returns (bytes20 digest) {
        uint256 _loc = loc(memView);
        uint256 _len = len(memView);
        bool res;
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            let ptr := mload(0x40)
            res := staticcall(gas(), 2, _loc, _len, ptr, 0x20) // sha2
            res := and(res, staticcall(gas(), 3, ptr, 0x20, ptr, 0x20)) // rmd160
            digest := mload(add(ptr, 0xc)) // return value is 0-prefixed.
        }
        require(res, "hash160 OOG");
    }

    /**
     * @notice          Implements bitcoin's hash256 (double sha2)
     * @param memView   A view of the preimage
     * @return          digest - the Digest
     */
    function hash256(bytes29 memView) internal view returns (bytes32 digest) {
        uint256 _loc = loc(memView);
        uint256 _len = len(memView);
        bool res;
        assembly {
            // solhint-disable-previous-line no-inline-assembly
            let ptr := mload(0x40)
            res := staticcall(gas(), 2, _loc, _len, ptr, 0x20) // sha2 #1
            res := and(res, staticcall(gas(), 2, ptr, 0x20, ptr, 0x20)) // sha2 #2
            digest := mload(ptr)
        }
        require(res, "hash256 OOG");
    }

    /**
     * @notice          Return true if the underlying memory is equal. Else false.
     * @param left      The first view
     * @param right     The second view
     * @return          bool - True if the underlying memory is equal
     */
    function untypedEqual(
        bytes29 left,
        bytes29 right
    ) internal pure returns (bool) {
        return
            (loc(left) == loc(right) && len(left) == len(right)) ||
            keccak(left) ==

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Non-Fungible, Swap, Yield, Upgradeable, Multi-Signature, Factory|addr:0x1f9ed301f845925bcaf3d7c8090aa3f32fbf2a3a|verified:true|block:23722082|tx:0x2b36d98103ac817b2bbde8e483e7bd97fdfe4652df9fe9406dcb5622924bc304|first_check:1762247750

Submitted on: 2025-11-04 10:15:53

Comments

Log in to comment.

No comments yet.