GmpManager

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/GmpManager/GmpManager.sol": {
      "content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "../interfaces/IGmpManager.sol";
import "../libraries/GmpStructs.sol";
import "./GmpIntegration.sol";
import {ManagerBase} from "../NttManager/ManagerBase.sol";
import "wormhole-solidity-sdk/Utils.sol";

contract GmpManager is IGmpManager, ManagerBase {
    string public constant GMP_MANAGER_VERSION = "1.0.0";

    constructor(
        uint16 _chainId
    ) ManagerBase(_chainId) {}

    struct GmpPeer {
        bytes32 peerAddress;
    }

    function __GmpManager_init() internal onlyInitializing {
        // check if the owner is the deployer of this contract
        if (msg.sender != deployer) {
            revert UnexpectedDeployer(deployer, msg.sender);
        }
        if (msg.value != 0) {
            revert UnexpectedMsgValue();
        }
        __PausedOwnable_init(msg.sender, msg.sender);
        __ReentrancyGuard_init();
        // NOTE: we bump the message counter to start from 1
        // this is so we can use '0' as a sentinel value for unreserved sequences
        _useMessageSequence();
    }

    function _initialize() internal virtual override {
        super._initialize();
        __GmpManager_init();
        // Note: _checkThresholdInvariants() removed since we don't maintain global thresholds
        _checkTransceiversInvariants();
    }

    // =============== Storage ==============================================================

    bytes32 private constant PEERS_SLOT = bytes32(uint256(keccak256("gmp.peers")) - 1);
    bytes32 private constant RESERVED_SEQUENCES_SLOT =
        bytes32(uint256(keccak256("gmp.reservedSequences")) - 1);

    // =============== Storage Getters/Setters ==============================================

    function _getPeersStorage() internal pure returns (mapping(uint16 => GmpPeer) storage $) {
        uint256 slot = uint256(PEERS_SLOT);
        assembly ("memory-safe") {
            $.slot := slot
        }
    }

    function _getReservedSequencesStorage()
        internal
        pure
        returns (mapping(uint64 => address) storage $)
    {
        uint256 slot = uint256(RESERVED_SEQUENCES_SLOT);
        assembly ("memory-safe") {
            $.slot := slot
        }
    }

    // =============== Public Getters ========================================================

    function getPeer(
        uint16 chainId_
    ) external view returns (GmpPeer memory) {
        return _getPeersStorage()[chainId_];
    }

    function _verifyPeer(uint16 sourceChainId, bytes32 peerAddress) internal view override {
        if (sourceChainId == 0) {
            revert InvalidPeerChainIdZero();
        }
        if (peerAddress == bytes32(0)) {
            revert InvalidPeerZeroAddress();
        }
        if (_getPeersStorage()[sourceChainId].peerAddress != peerAddress) {
            revert InvalidPeer(sourceChainId, peerAddress);
        }
    }

    // =============== External Interface =========================================================

    /**
     * @notice Reserve a message sequence number for later use
     * @dev This function allows users to reserve sequence numbers ahead of time,
     *      which can be useful for applications that need deterministic sequence numbers
     *      or want to guarantee a specific ordering of messages.
     *
     * @dev Sequence numbers start at 1 (not 0) to allow 0 to be used as a sentinel value
     *      to indicate "no reserved sequence" in sendMessage calls.
     *
     * @dev Reserved sequences are per-sender, meaning only the address that reserved
     *      a sequence can use it in a subsequent sendMessage call.
     *
     * @dev Reserved sequences are single-use - once consumed in a sendMessage call,
     *      they cannot be reused.
     *
     * @return sequence The reserved sequence number
     */
    function reserveMessageSequence() external override returns (uint64 sequence) {
        sequence = _useMessageSequence();
        _getReservedSequencesStorage()[sequence] = msg.sender;
    }

    function _verifyAndConsumeReservedMessageSequence(
        uint64 sequence
    ) internal {
        if (_getReservedSequencesStorage()[sequence] != msg.sender) {
            revert SequenceNotReservedBySender(sequence, msg.sender);
        }
        _getReservedSequencesStorage()[sequence] = address(0);
    }

    // this exists just to minimise the number of local variable assignments :(
    struct PreparedTransfer {
        address[] enabledTransceivers;
        TransceiverStructs.TransceiverInstruction[] instructions;
        uint256[] priceQuotes;
        uint256 totalPriceQuote;
    }

    /**
     * @notice Send a cross-chain message to a target contract
     * @dev This function supports both immediate sequence allocation and pre-reserved sequences.
     *
     * ## Sequence Reservation Flow:
     *
     * ### Option 1: Immediate Sequence Allocation
     * - Pass `reservedSequence = 0` to allocate a new sequence immediately
     * - The function will automatically assign the next available sequence number
     * - This is the default behavior for most use cases
     *
     * ### Option 2: Pre-Reserved Sequence Usage
     * - First call `reserveMessageSequence()` to obtain a reserved sequence number
     * - Then pass that sequence number as `reservedSequence` parameter
     * - The function will validate that the sequence was reserved by the caller
     * - Once used, the reserved sequence is consumed and cannot be reused
     *
     * @param targetChain The Wormhole chain ID of the target chain
     * @param callee The address of the contract to call on the target chain (32-byte format)
     * @param refundAddress The address to refund excess fees to on the target chain (32-byte format)
     * @param reservedSequence The pre-reserved sequence to use, or 0 for immediate allocation
     * @param data The calldata to execute on the target chain
     * @param transceiverInstructions Instructions for transceivers (e.g., gas limits, relayer settings)
     *
     * @return actualSequence The sequence number assigned to this message
     *
     */
    function sendMessage(
        uint16 targetChain,
        bytes32 callee,
        bytes32 refundAddress,
        uint64 reservedSequence,
        bytes calldata data,
        bytes calldata transceiverInstructions
    ) external payable override nonReentrant whenNotPaused returns (uint64 actualSequence) {
        return _sendMessage(
            targetChain, callee, refundAddress, reservedSequence, data, transceiverInstructions
        );
    }

    function _sendMessage(
        uint16 targetChain,
        bytes32 callee,
        bytes32 refundAddress,
        uint64 reservedSequence,
        bytes calldata data,
        bytes calldata transceiverInstructions
    ) internal returns (uint64 sequence) {
        if (callee == bytes32(0)) {
            revert InvalidCallee();
        }

        if (refundAddress == bytes32(0)) {
            revert InvalidRefundAddress();
        }

        // Handle sequence allocation/reservation
        if (reservedSequence == 0) {
            // No sequence provided, allocate a new one
            sequence = _useMessageSequence();
        } else {
            _verifyAndConsumeReservedMessageSequence(reservedSequence);
            sequence = reservedSequence;
        }

        bytes memory encodedGmpManagerPayload;

        {
            GmpStructs.GenericMessage memory message = GmpStructs.GenericMessage({
                toChain: targetChain,
                callee: callee,
                sender: toWormholeFormat(msg.sender),
                data: data
            });

            encodedGmpManagerPayload = TransceiverStructs.encodeNttManagerMessage(
                TransceiverStructs.NttManagerMessage(
                    bytes32(uint256(sequence)),
                    toWormholeFormat(msg.sender),
                    GmpStructs.encodeGenericMessage(message)
                )
            );
        }

        PreparedTransfer memory preparedTransfer;
        {
            (
                address[] memory enabledTransceivers,
                TransceiverStructs.TransceiverInstruction[] memory instructions,
                uint256[] memory priceQuotes,
                uint256 totalPriceQuote
            ) = _prepareForTransfer(targetChain, transceiverInstructions);

            preparedTransfer = PreparedTransfer({
                enabledTransceivers: enabledTransceivers,
                instructions: instructions,
                priceQuotes: priceQuotes,
                totalPriceQuote: totalPriceQuote
            });
        }

        bytes32 peerAddress = _getPeersStorage()[targetChain].peerAddress;
        if (peerAddress == bytes32(0)) {
            revert InvalidPeer(targetChain, peerAddress);
        }

        _sendMessageToTransceivers(
            targetChain,
            refundAddress,
            peerAddress,
            preparedTransfer.priceQuotes,
            preparedTransfer.instructions,
            preparedTransfer.enabledTransceivers,
            encodedGmpManagerPayload
        );

        emit MessageSent(
            sequence, msg.sender, targetChain, callee, data, preparedTransfer.totalPriceQuote
        );
    }

    function executeMsg(
        uint16 sourceChainId,
        bytes32 sourceGmpManagerAddress,
        TransceiverStructs.NttManagerMessage memory message
    ) public override nonReentrant whenNotPaused {
        (bytes32 digest, bool alreadyExecuted) =
            _isMessageExecuted(sourceChainId, sourceGmpManagerAddress, message);

        if (alreadyExecuted) {
            return;
        }

        GmpStructs.GenericMessage memory gmp = GmpStructs.parseGenericMessage(message.payload);

        if (gmp.toChain != chainId) {
            revert InvalidTargetChain(gmp.toChain, chainId);
        }

        address callee = fromWormholeFormat(gmp.callee);
        GmpIntegration(callee).receiveMessage(digest, sourceChainId, gmp.sender, gmp.data);

        emit MessageExecuted(digest, sourceChainId, message.sender, callee, gmp.data);
    }

    // =============== Admin ==============================================================

    function setPeer(uint16 peerChainId, bytes32 peerAddress) public onlyOwner {
        if (peerChainId == 0) {
            revert InvalidPeerChainIdZero();
        }
        if (peerAddress == bytes32(0)) {
            revert InvalidPeerZeroAddress();
        }
        if (peerChainId == chainId) {
            revert InvalidPeerSameChainId();
        }

        GmpPeer memory oldPeer = _getPeersStorage()[peerChainId];

        _getPeersStorage()[peerChainId].peerAddress = peerAddress;

        _addToKnownChains(peerChainId);

        emit PeerUpdated(peerChainId, oldPeer.peerAddress, peerAddress);
    }
}
"
    },
    "src/interfaces/IGmpManager.sol": {
      "content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "./IManagerBase.sol";

interface IGmpManager is IManagerBase {
    /// @notice The caller is not the deployer.
    error UnexpectedDeployer(address expectedOwner, address owner);

    /// @notice An unexpected msg.value was passed with the call
    /// @dev Selector 0xbd28e889.
    error UnexpectedMsgValue();

    error InvalidTargetChain(uint16 targetChain, uint16 chainId);
    error CallFailed(bytes returnData);
    error InvalidCallee();
    error SequenceNotReserved(uint64 sequence);
    error SequenceNotReservedBySender(uint64 sequence, address sender);

    /// @notice Emitted when a message is sent to another chain
    /// @param sequence The sequence number of the message
    /// @param sender The address of the message sender
    /// @param targetChain The chain ID of the target chain
    /// @param callee The address of the contract to call on the target chain
    /// @param data The calldata to be executed on the target chain
    /// @param fee The total fee paid for sending the message
    event MessageSent(
        uint64 indexed sequence,
        address indexed sender,
        uint16 targetChain,
        bytes32 callee,
        bytes data,
        uint256 fee
    );

    /// @notice Emitted when a message is executed on this chain
    /// @param messageHash The hash of the executed message
    /// @param sourceChain The chain ID of the source chain
    /// @param sender The address of the message sender on the source chain
    /// @param callee The address of the contract called on this chain
    /// @param data The calldata executed on this chain
    event MessageExecuted(
        bytes32 indexed messageHash,
        uint16 indexed sourceChain,
        bytes32 indexed sender,
        address callee,
        bytes data
    );

    /// @notice Emitted when a peer is updated
    /// @param chainId The chain ID of the updated peer
    /// @param oldPeerAddress The previous address of the peer
    /// @param newPeerAddress The new address of the peer
    event PeerUpdated(uint16 indexed chainId, bytes32 oldPeerAddress, bytes32 newPeerAddress);

    /**
     * @notice Reserve a message sequence number for later use
     * @dev Sequence numbers start at 1 to allow 0 as sentinel value for "no reserved sequence"
     * @dev Reserved sequences are per-sender and single-use
     * @return sequence The reserved sequence number
     */
    function reserveMessageSequence() external returns (uint64 sequence);

    /**
     * @notice Send a cross-chain message with optional sequence reservation
     * @dev Pass reservedSequence = 0 for immediate allocation, or a pre-reserved sequence number
     * @param targetChain The Wormhole chain ID of the target chain
     * @param callee The target contract address (32-byte format)
     * @param refundAddress The refund address (32-byte format)
     * @param reservedSequence Pre-reserved sequence (0 for immediate allocation)
     * @param data The calldata to execute on target chain
     * @param transceiverInstructions Instructions for transceivers
     * @return sequence The sequence number assigned to this message
     */
    function sendMessage(
        uint16 targetChain,
        bytes32 callee,
        bytes32 refundAddress,
        uint64 reservedSequence,
        bytes calldata data,
        bytes calldata transceiverInstructions
    ) external payable returns (uint64 sequence);
}
"
    },
    "src/libraries/GmpStructs.sol": {
      "content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "wormhole-solidity-sdk/libraries/BytesParsing.sol";

library GmpStructs {
    using BytesParsing for bytes;

    error PayloadTooLong(uint256 length);
    error InvalidPrefix();

    /// @dev Prefix for all GenericMesage payloads
    ///      This is 0x99'G''M''P'
    bytes4 constant GMP_PREFIX = 0x99474D50;

    struct GenericMessage {
        /// @notice target chain
        uint16 toChain;
        /// @notice contract to deliver the payload to
        bytes32 callee;
        /// @notice sender of the message
        bytes32 sender;
        /// @notice calldata to pass to the recipient contract
        bytes data;
    }

    function encodeGenericMessage(
        GenericMessage memory message
    ) internal pure returns (bytes memory) {
        if (message.data.length > type(uint16).max) {
            revert PayloadTooLong(message.data.length);
        }

        return abi.encodePacked(
            GMP_PREFIX,
            message.toChain,
            message.callee,
            message.sender,
            uint16(message.data.length),
            message.data
        );
    }

    function parseGenericMessage(
        bytes memory encoded
    ) internal pure returns (GenericMessage memory message) {
        uint256 offset = 0;
        bytes4 prefix;
        (prefix, offset) = encoded.asBytes4Unchecked(offset);
        if (prefix != GMP_PREFIX) revert InvalidPrefix();

        (message.toChain, offset) = encoded.asUint16Unchecked(offset);
        (message.callee, offset) = encoded.asBytes32Unchecked(offset);
        (message.sender, offset) = encoded.asBytes32Unchecked(offset);
        uint16 dataLength;
        (dataLength, offset) = encoded.asUint16Unchecked(offset);
        (message.data, offset) = encoded.sliceUnchecked(offset, dataLength);
        encoded.checkLength(offset);
    }
}
"
    },
    "src/GmpManager/GmpIntegration.sol": {
      "content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "../interfaces/IGmpManager.sol";

abstract contract GmpIntegration {
    IGmpManager public immutable gmpManager;

    error OnlyGmpManagerAllowed();

    constructor(
        IGmpManager _gmpManager
    ) {
        gmpManager = _gmpManager;
    }

    modifier onlyGmpManager() {
        if (msg.sender != address(gmpManager)) revert OnlyGmpManagerAllowed();
        _;
    }

    /// @notice Receive a message via the GMP manager.
    /// @dev The GMP manager performs verification and replay protection.
    /// @dev `data` is the payload of the message, which is not necessarily
    ///       unique. `digest` is a unique identifier for the message, which commits
    ///       to metadata not directly included in `data`.
    ///       When an integrator wants to uniquely identify a message, they should
    ///       use `digest` instead of `data`.
    function receiveMessage(
        bytes32 digest,
        uint16 sourceChainId,
        bytes32 sender,
        bytes calldata data
    ) external onlyGmpManager {
        _receiveMessage(digest, sourceChainId, sender, data);
    }

    function _receiveMessage(
        bytes32 digest,
        uint16 sourceChainId,
        bytes32 sender,
        bytes calldata data
    ) internal virtual;

    function _sendMessage(
        uint256 msgValue,
        uint16 targetChain,
        bytes32 callee,
        bytes32 refundAddress,
        uint64 reservedSequence,
        bytes memory data,
        bytes memory transceiverInstructions
    ) internal returns (uint64 sequence) {
        return gmpManager.sendMessage{value: msgValue}(
            targetChain, callee, refundAddress, reservedSequence, data, transceiverInstructions
        );
    }
}
"
    },
    "src/NttManager/ManagerBase.sol": {
      "content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "wormhole-solidity-sdk/Utils.sol";
import "wormhole-solidity-sdk/libraries/BytesParsing.sol";

import "../libraries/external/OwnableUpgradeable.sol";
import "../libraries/external/ReentrancyGuardUpgradeable.sol";
import "../libraries/TransceiverStructs.sol";
import "../libraries/TransceiverHelpers.sol";
import "../libraries/PausableOwnable.sol";
import "../libraries/Implementation.sol";

import "../interfaces/ITransceiver.sol";
import "../interfaces/IManagerBase.sol";

import "./TransceiverRegistry.sol";

abstract contract ManagerBase is
    IManagerBase,
    TransceiverRegistry,
    PausableOwnable,
    ReentrancyGuardUpgradeable,
    Implementation
{
    // =============== Immutables ============================================================

    /// @dev Contract deployer address
    address immutable deployer;
    /// @dev Wormhole chain ID that the NTT Manager is deployed on.
    /// This chain ID is formatted Wormhole Chain IDs -- https://docs.wormhole.com/wormhole/reference/constants
    uint16 public immutable chainId;
    /// @dev EVM chain ID that the NTT Manager is deployed on.
    /// This chain ID is formatted based on standardized chain IDs, e.g. Ethereum mainnet is 1, Sepolia is 11155111, etc.
    uint256 immutable evmChainId;

    // =============== Setup =================================================================

    constructor(
        uint16 _chainId
    ) {
        chainId = _chainId;
        evmChainId = block.chainid;
        // save the deployer (check this on initialization)
        deployer = msg.sender;
    }

    function _migrate() internal virtual override {
        // Note: _checkThresholdInvariants() removed since we don't maintain global thresholds
        _checkTransceiversInvariants();
    }

    // =============== Storage ==============================================================

    bytes32 private constant MESSAGE_ATTESTATIONS_SLOT =
        bytes32(uint256(keccak256("ntt.messageAttestations")) - 1);

    bytes32 private constant MESSAGE_SEQUENCE_SLOT =
        bytes32(uint256(keccak256("ntt.messageSequence")) - 1);

    // Note: THRESHOLD_SLOT removed - we now use per-chain thresholds only
    // TODO: come up with a backwards compatible way so this contract can be
    // merged upstream (probably some simple abstract class hierarchy)

    // =============== Storage Getters/Setters ==============================================

    // Note: _getThresholdStorage() removed - we now use per-chain thresholds only

    function _getMessageAttestationsStorage()
        internal
        pure
        returns (mapping(bytes32 => AttestationInfo) storage $)
    {
        uint256 slot = uint256(MESSAGE_ATTESTATIONS_SLOT);
        assembly ("memory-safe") {
            $.slot := slot
        }
    }

    function _getMessageSequenceStorage() internal pure returns (_Sequence storage $) {
        uint256 slot = uint256(MESSAGE_SEQUENCE_SLOT);
        assembly ("memory-safe") {
            $.slot := slot
        }
    }

    // =============== External Logic =============================================================

    function attestationReceived(
        uint16 sourceChainId,
        bytes32 sourceNttManagerAddress,
        TransceiverStructs.NttManagerMessage memory payload
    ) external onlyTransceiver whenNotPaused {
        _verifyPeer(sourceChainId, sourceNttManagerAddress);

        // Compute manager message digest and record transceiver attestation.
        bytes32 nttManagerMessageHash = _recordTransceiverAttestation(sourceChainId, payload);

        if (isMessageApprovedForChain(sourceChainId, nttManagerMessageHash)) {
            this.executeMsg(sourceChainId, sourceNttManagerAddress, payload);
        }
    }

    /// @inheritdoc IManagerBase
    function quoteDeliveryPrice(
        uint16 recipientChain,
        bytes memory transceiverInstructions
    ) public view returns (uint256[] memory, uint256) {
        uint256 numRegisteredTransceivers = _getRegisteredTransceiversStorage().length;
        address[] memory enabledTransceivers = getSendTransceiversForChain(recipientChain);

        TransceiverStructs.TransceiverInstruction[] memory instructions = TransceiverStructs
            .parseTransceiverInstructions(transceiverInstructions, numRegisteredTransceivers);

        return _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers);
    }

    // =============== Internal Logic ===========================================================

    function _quoteDeliveryPrice(
        uint16 recipientChain,
        TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions,
        address[] memory enabledTransceivers
    ) internal view returns (uint256[] memory, uint256) {
        uint256 numEnabledTransceivers = enabledTransceivers.length;
        mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage();

        uint256[] memory priceQuotes = new uint256[](numEnabledTransceivers);
        uint256 totalPriceQuote = 0;
        for (uint256 i = 0; i < numEnabledTransceivers; i++) {
            address transceiverAddr = enabledTransceivers[i];
            uint8 registeredTransceiverIndex = transceiverInfos[transceiverAddr].index;
            uint256 transceiverPriceQuote = ITransceiver(transceiverAddr).quoteDeliveryPrice(
                recipientChain, transceiverInstructions[registeredTransceiverIndex]
            );
            priceQuotes[i] = transceiverPriceQuote;
            totalPriceQuote += transceiverPriceQuote;
        }
        return (priceQuotes, totalPriceQuote);
    }

    function _recordTransceiverAttestation(
        uint16 sourceChainId,
        TransceiverStructs.NttManagerMessage memory payload
    ) internal returns (bytes32) {
        bytes32 nttManagerMessageHash =
            TransceiverStructs.nttManagerMessageDigest(sourceChainId, payload);

        // set the attested flag for this transceiver.
        // NOTE: Attestation is idempotent (bitwise or 1), but we revert
        // anyway to ensure that the client does not continue to initiate calls
        // to receive the same message through the same transceiver.
        if (
            transceiverAttestedToMessage(
                nttManagerMessageHash, _getTransceiverInfosStorage()[msg.sender].index
            )
        ) {
            revert TransceiverAlreadyAttestedToMessage(nttManagerMessageHash);
        }
        _setTransceiverAttestedToMessage(nttManagerMessageHash, msg.sender);

        return nttManagerMessageHash;
    }

    function _isMessageExecuted(
        uint16 sourceChainId,
        bytes32 sourceNttManagerAddress,
        TransceiverStructs.NttManagerMessage memory message
    ) internal returns (bytes32, bool) {
        bytes32 digest = TransceiverStructs.nttManagerMessageDigest(sourceChainId, message);

        if (!isMessageApprovedForChain(sourceChainId, digest)) {
            revert MessageNotApproved(digest);
        }

        bool msgAlreadyExecuted = _replayProtect(digest);
        if (msgAlreadyExecuted) {
            // end execution early to mitigate the possibility of race conditions from transceivers
            // attempting to deliver the same message when (threshold < number of transceiver messages)
            // notify client (off-chain process) so they don't attempt redundant msg delivery
            emit MessageAlreadyExecuted(sourceNttManagerAddress, digest);
            return (bytes32(0), msgAlreadyExecuted);
        }

        return (digest, msgAlreadyExecuted);
    }

    function _sendMessageToTransceivers(
        uint16 recipientChain,
        bytes32 refundAddress,
        bytes32 peerAddress,
        uint256[] memory priceQuotes,
        TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions,
        address[] memory enabledTransceivers,
        bytes memory nttManagerMessage
    ) internal {
        uint256 numEnabledTransceivers = enabledTransceivers.length;
        mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage();

        if (peerAddress == bytes32(0)) {
            revert PeerNotRegistered(recipientChain);
        }

        // push onto the stack again to avoid stack too deep error
        bytes32 refundRecipient = refundAddress;

        // call into transceiver contracts to send the message
        for (uint256 i = 0; i < numEnabledTransceivers; i++) {
            address transceiverAddr = enabledTransceivers[i];

            // send it to the recipient nttManager based on the chain
            ITransceiver(transceiverAddr).sendMessage{value: priceQuotes[i]}(
                recipientChain,
                transceiverInstructions[transceiverInfos[transceiverAddr].index],
                nttManagerMessage,
                peerAddress,
                refundRecipient
            );
        }
    }

    function _prepareForTransfer(
        uint16 recipientChain,
        bytes memory transceiverInstructions
    )
        internal
        returns (
            address[] memory,
            TransceiverStructs.TransceiverInstruction[] memory,
            uint256[] memory,
            uint256
        )
    {
        address[] memory enabledTransceivers = getSendTransceiversForChain(recipientChain);

        TransceiverStructs.TransceiverInstruction[] memory instructions;

        {
            uint256 numRegisteredTransceivers = _getRegisteredTransceiversStorage().length;
            uint256 numEnabledTransceivers = enabledTransceivers.length;

            if (numEnabledTransceivers == 0) {
                revert NoEnabledTransceivers();
            }

            instructions = TransceiverStructs.parseTransceiverInstructions(
                transceiverInstructions, numRegisteredTransceivers
            );
        }

        (uint256[] memory priceQuotes, uint256 totalPriceQuote) =
            _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers);
        {
            // check up front that msg.value will cover the delivery price
            if (msg.value < totalPriceQuote) {
                revert DeliveryPaymentTooLow(totalPriceQuote, msg.value);
            }

            // refund user extra excess value from msg.value
            uint256 excessValue = msg.value - totalPriceQuote;
            if (excessValue > 0) {
                _refundToSender(excessValue);
            }
        }

        return (enabledTransceivers, instructions, priceQuotes, totalPriceQuote);
    }

    function _refundToSender(
        uint256 refundAmount
    ) internal {
        // refund the price quote back to sender
        (bool refundSuccessful,) = payable(msg.sender).call{value: refundAmount}("");

        // check success
        if (!refundSuccessful) {
            revert RefundFailed(refundAmount);
        }
    }

    // =============== Public Getters ========================================================

    /// @notice Check if a message has enough attestations for a specific source chain
    function isMessageApprovedForChain(
        uint16 sourceChain,
        bytes32 digest
    ) public view returns (bool) {
        uint8 threshold = _getThresholdForChain(sourceChain);
        uint8 attestations = messageAttestationsForChain(sourceChain, digest);
        return attestations >= threshold && threshold > 0;
    }

    /// @inheritdoc IManagerBase
    function getThreshold(
        uint16 sourceChain
    ) external view returns (uint8) {
        return _getThresholdForChain(sourceChain);
    }

    /// @inheritdoc IManagerBase
    function nextMessageSequence() external view returns (uint64) {
        return _getMessageSequenceStorage().num;
    }

    /// @inheritdoc IManagerBase
    function isMessageExecuted(
        bytes32 digest
    ) public view returns (bool) {
        return _getMessageAttestationsStorage()[digest].executed;
    }

    /// @inheritdoc IManagerBase
    function transceiverAttestedToMessage(bytes32 digest, uint8 index) public view returns (bool) {
        return
            _getMessageAttestationsStorage()[digest].attestedTransceivers & uint64(1 << index) > 0;
    }

    /// @inheritdoc IManagerBase
    function messageAttestations(
        bytes32 digest
    ) public view returns (uint8 count) {
        return countSetBits(_getMessageAttestations(digest));
    }

    /// @notice Get the number of attestations for a message from a specific source chain
    function messageAttestationsForChain(
        uint16 sourceChain,
        bytes32 digest
    ) public view returns (uint8 count) {
        return countSetBits(_getMessageAttestationsForChain(sourceChain, digest));
    }

    // =============== Admin ==============================================================

    /// @inheritdoc IManagerBase
    function upgrade(
        address newImplementation
    ) external onlyOwner {
        _upgrade(newImplementation);
    }

    /// @inheritdoc IManagerBase
    function pause() public onlyOwnerOrPauser {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    /// @notice Transfer ownership of the Manager contract and all Transceiver contracts to a new owner.
    function transferOwnership(
        address newOwner
    ) public override onlyOwner {
        super.transferOwnership(newOwner);
        // loop through all the registered transceivers and set the new owner of each transceiver to the newOwner
        address[] storage _registeredTransceivers = _getRegisteredTransceiversStorage();
        _checkRegisteredTransceiversInvariants();

        for (uint256 i = 0; i < _registeredTransceivers.length; i++) {
            address transceiver = _registeredTransceivers[i];
            try ITransceiver(transceiver).transferTransceiverOwnership(newOwner) {
                // Success - ownership transferred
            } catch (bytes memory reason) {
                // Failed to transfer ownership - emit event and continue with other transceivers
                // This prevents disabled or malfunctioning transceivers from blocking the entire ownership transfer
                emit TransceiverOwnershipTransferFailed(transceiver, reason);
            }
        }
    }

    /// @inheritdoc IManagerBase
    function setTransceiver(
        address transceiver
    ) external onlyOwner {
        _setTransceiver(transceiver);

        // Note: Global threshold is no longer maintained since we use per-chain thresholds.
        // Per-chain thresholds must be configured separately using setThreshold(uint16, uint8).

        emit TransceiverAdded(transceiver, _getNumTransceiversStorage().enabled, 0);

        // Note: _checkThresholdInvariants() removed since we don't maintain global thresholds
    }

    /// @inheritdoc IManagerBase
    function removeTransceiver(
        address transceiver
    ) external onlyOwner {
        uint8 numEnabledTransceivers = _getNumTransceiversStorage().enabled;

        // Prevent removing the last transceiver - you need at least one for the system to work
        if (numEnabledTransceivers <= 1) {
            revert ZeroThreshold(); // Reusing this error since it's about threshold requirements
        }

        // remove from all per-chain configurations first
        _removeTransceiverFromAllChains(transceiver);

        // then remove globally
        _removeTransceiver(transceiver);

        // Note: Global threshold is no longer maintained since we use per-chain thresholds.
        // Per-chain thresholds are automatically adjusted in _removeReceiveTransceiverForChain().

        emit TransceiverRemoved(transceiver, 0);
    }

    /// @notice Add a transceiver for sending to a specific chain
    /// @param targetChain The chain ID to send to
    /// @param transceiver The transceiver to enable for sending to this chain
    function setSendTransceiverForChain(
        uint16 targetChain,
        address transceiver
    ) external onlyOwner {
        _setSendTransceiverForChain(targetChain, transceiver);
        emit SendTransceiverUpdatedForChain(targetChain, transceiver, true);
    }

    /// @notice Remove a transceiver for sending to a specific chain
    /// @param targetChain The chain ID
    /// @param transceiver The transceiver to disable for sending to this chain
    function removeSendTransceiverForChain(
        uint16 targetChain,
        address transceiver
    ) external onlyOwner {
        _removeSendTransceiverForChain(targetChain, transceiver);
        emit SendTransceiverUpdatedForChain(targetChain, transceiver, false);
    }

    /// @notice Add a transceiver for receiving from a specific chain
    /// @param sourceChain The chain ID to receive from
    /// @param transceiver The transceiver to enable for receiving from this chain
    function setReceiveTransceiverForChain(
        uint16 sourceChain,
        address transceiver
    ) external onlyOwner {
        _setReceiveTransceiverForChain(sourceChain, transceiver);
        emit ReceiveTransceiverUpdatedForChain(sourceChain, transceiver, true);
    }

    /// @notice Remove a transceiver for receiving from a specific chain
    /// @param sourceChain The chain ID
    /// @param transceiver The transceiver to disable for receiving from this chain
    function removeReceiveTransceiverForChain(
        uint16 sourceChain,
        address transceiver
    ) external onlyOwner {
        _removeReceiveTransceiverForChain(sourceChain, transceiver);
        emit ReceiveTransceiverUpdatedForChain(sourceChain, transceiver, false);
    }

    /// @notice Set the threshold for receiving from a specific chain
    /// @param sourceChain The chain ID
    /// @param threshold The threshold for receiving from this chain
    function setThreshold(uint16 sourceChain, uint8 threshold) external onlyOwner {
        _setThresholdForChain(sourceChain, threshold);
        emit ThresholdUpdatedForChain(sourceChain, threshold);
    }

    /// @notice Register a known chain for migration purposes
    /// @dev This function is used to populate the known chains list for existing deployments
    ///      that were created before the chain registry was introduced. It verifies the peer
    ///      relationship before adding the chain to ensure only valid chains are registered.
    ///      This function can be called by anyone since it only adds valid peer chains.
    /// @param peerChainId The chain ID to register
    /// @param peerAddress The peer address on that chain (used for verification)
    function registerKnownChain(uint16 peerChainId, bytes32 peerAddress) external {
        if (peerAddress == bytes32(0)) {
            revert InvalidPeerZeroAddress();
        }

        // Verify this is a valid peer relationship
        _verifyPeer(peerChainId, peerAddress);

        // If verification passes, add to known chains
        _addToKnownChains(peerChainId);
    }

    // =============== Internal ==============================================================

    function _verifyPeer(uint16 sourceChainId, bytes32 peerAddress) internal virtual;

    function _setTransceiverAttestedToMessage(bytes32 digest, uint8 index) internal {
        _getMessageAttestationsStorage()[digest].attestedTransceivers |= uint64(1 << index);
    }

    function _setTransceiverAttestedToMessage(bytes32 digest, address transceiver) internal {
        _setTransceiverAttestedToMessage(digest, _getTransceiverInfosStorage()[transceiver].index);

        emit MessageAttestedTo(
            digest, transceiver, _getTransceiverInfosStorage()[transceiver].index
        );
    }

    /// @dev Returns the bitmap of attestations from enabled transceivers for a given message.
    function _getMessageAttestations(
        bytes32 digest
    ) internal view returns (uint64) {
        uint64 enabledTransceiverBitmap = _getEnabledTransceiversBitmap();
        return
            _getMessageAttestationsStorage()[digest].attestedTransceivers & enabledTransceiverBitmap;
    }

    /// @dev Returns the bitmap of attestations from enabled transceivers for a given message and source chain.
    function _getMessageAttestationsForChain(
        uint16 sourceChain,
        bytes32 digest
    ) internal view returns (uint64) {
        uint64 enabledTransceiverBitmap = _getReceiveTransceiversBitmapForChain(sourceChain);
        return
            _getMessageAttestationsStorage()[digest].attestedTransceivers & enabledTransceiverBitmap;
    }

    function _getEnabledTransceiverAttestedToMessage(
        bytes32 digest,
        uint8 index
    ) internal view returns (bool) {
        return _getMessageAttestations(digest) & uint64(1 << index) != 0;
    }

    // @dev Mark a message as executed.
    // This function will retuns `true` if the message has already been executed.
    function _replayProtect(
        bytes32 digest
    ) internal returns (bool) {
        // check if this message has already been executed
        if (isMessageExecuted(digest)) {
            return true;
        }

        // mark this message as executed
        _getMessageAttestationsStorage()[digest].executed = true;

        return false;
    }

    function _useMessageSequence() internal returns (uint64 currentSequence) {
        currentSequence = _getMessageSequenceStorage().num++;
    }

    /// ============== Invariants =============================================

    /// @dev When we add new immutables, this function should be updated
    function _checkImmutables() internal view virtual override {
        super._checkImmutables();
        assert(this.chainId() == chainId);
    }

    function _checkRegisteredTransceiversInvariants() internal view {
        if (_getRegisteredTransceiversStorage().length != _getNumTransceiversStorage().registered) {
            revert RetrievedIncorrectRegisteredTransceivers(
                _getRegisteredTransceiversStorage().length, _getNumTransceiversStorage().registered
            );
        }
    }

    // Note: _checkThresholdInvariants() function removed since we don't maintain global thresholds
    // Per-chain threshold validation is handled in _setThresholdForChain()
}
"
    },
    "lib/wormhole-solidity-sdk/src/Utils.sol": {
      "content": "
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;

import "./interfaces/IWormholeRelayer.sol";

function toWormholeFormat(address addr) pure returns (bytes32) {
    return bytes32(uint256(uint160(addr)));
}

function fromWormholeFormat(bytes32 whFormatAddress) pure returns (address) {
    if (uint256(whFormatAddress) >> 160 != 0) {
        revert NotAnEvmAddress(whFormatAddress);
    }
    return address(uint160(uint256(whFormatAddress)));
}
"
    },
    "src/interfaces/IManagerBase.sol": {
      "content": "// SPDX-License-Identifier: Apache 2
pragma solidity >=0.8.8 <0.9.0;

import "../libraries/TransceiverStructs.sol";

interface IManagerBase {
    /// @notice Information about attestations for a given message.
    /// @dev The fields are as follows:
    ///      - executed: whether the message has been executed.
    ///      - attested: bitmap of transceivers that have attested to this message.
    ///                  (NOTE: might contain disabled transceivers)
    struct AttestationInfo {
        bool executed;
        uint64 attestedTransceivers;
    }

    struct _Sequence {
        uint64 num;
    }

    struct _Threshold {
        uint8 num;
    }

    /// @notice Emitted when a message has been attested to.
    /// @dev Topic0
    ///      0x35a2101eaac94b493e0dfca061f9a7f087913fde8678e7cde0aca9897edba0e5.
    /// @param digest The digest of the message.
    /// @param transceiver The address of the transceiver.
    /// @param index The index of the transceiver in the bitmap.
    event MessageAttestedTo(bytes32 digest, address transceiver, uint8 index);

    /// @notice Emmitted when the threshold required transceivers is changed.
    /// @dev Topic0
    ///      0x2a855b929b9a53c6fb5b5ed248b27e502b709c088e036a5aa17620c8fc5085a9.
    /// @param oldThreshold The old threshold.
    /// @param threshold The new threshold.
    event ThresholdChanged(uint8 oldThreshold, uint8 threshold);

    /// @notice Emitted when an transceiver is removed from the nttManager.
    /// @dev Topic0
    ///      0xf05962b5774c658e85ed80c91a75af9d66d2af2253dda480f90bce78aff5eda5.
    /// @param transceiver The address of the transceiver.
    /// @param transceiversNum The current number of transceivers.
    /// @param threshold The current threshold of transceivers.
    event TransceiverAdded(address transceiver, uint256 transceiversNum, uint8 threshold);

    /// @notice Emitted when an transceiver is removed from the nttManager.
    /// @dev Topic0
    ///     0x697a3853515b88013ad432f29f53d406debc9509ed6d9313dcfe115250fcd18f.
    /// @param transceiver The address of the transceiver.
    /// @param threshold The current threshold of transceivers.
    event TransceiverRemoved(address transceiver, uint8 threshold);

    /// @notice Emitted when a send transceiver is updated for a specific chain.
    /// @param targetChain The chain ID.
    /// @param transceiver The transceiver address.
    /// @param enabled Whether the transceiver is enabled or disabled.
    event SendTransceiverUpdatedForChain(uint16 targetChain, address transceiver, bool enabled);

    /// @notice Emitted when a receive transceiver is updated for a specific chain.
    /// @param sourceChain The chain ID.
    /// @param transceiver The transceiver address.
    /// @param enabled Whether the transceiver is enabled or disabled.
    event ReceiveTransceiverUpdatedForChain(uint16 sourceChain, address transceiver, bool enabled);

    /// @notice Emitted when the threshold is updated for a specific chain.
    /// @param sourceChain The chain ID.
    /// @param threshold The new threshold.
    event ThresholdUpdatedForChain(uint16 sourceChain, uint8 threshold);

    /// @notice Emitted when a transceiver ownership transfer fails.
    /// @param transceiver The transceiver address.
    /// @param reason The reason for the failure.
    event TransceiverOwnershipTransferFailed(address transceiver, bytes reason);

    /// @notice payment for a transfer is too low.
    /// @param requiredPayment The required payment.
    /// @param providedPayment The provided payment.
    error DeliveryPaymentTooLow(uint256 requiredPayment, uint256 providedPayment);

    /// @notice Error when the refund to the sender fails.
    /// @dev Selector 0x2ca23714.
    /// @param refundAmount The refund amount.
    error RefundFailed(uint256 refundAmount);

    /// @notice The number of thresholds should not be zero.
    error ZeroThreshold();

    error RetrievedIncorrectRegisteredTransceivers(uint256 retrieved, uint256 registered);

    /// @notice The threshold for transceiver attestations is too high.
    /// @param threshold The threshold.
    /// @param transceivers The number of transceivers.
    error ThresholdTooHigh(uint256 threshold, uint256 transceivers);

    /// @notice Error when the tranceiver already attested to the message.
    ///         To ensure the client does not continue to initiate calls to the attestationReceived function.
    /// @dev Selector 0x2113894.
    /// @param nttManagerMessageHash The hash of the message.
    error TransceiverAlreadyAttestedToMessage(bytes32 nttManagerMessageHash);

    /// @notice Error when the message is not approved.
    /// @dev Selector 0x451c4fb0.
    /// @param msgHash The hash of the message.
    error MessageNotApproved(bytes32 msgHash);

    /// @notice Emitted when a message has already been executed to
    ///         notify client of against retries.
    /// @dev Topic0
    ///      0x4069dff8c9df7e38d2867c0910bd96fd61787695e5380281148c04932d02bef2.
    /// @param sourceNttManager The address of the source nttManager.
    /// @param msgHash The keccak-256 hash of the message.
    event MessageAlreadyExecuted(bytes32 indexed sourceNttManager, bytes32 indexed msgHash);

    /// @notice There are no transceivers enabled with the Manager
    /// @dev Selector 0x69cf632a
    error NoEnabledTransceivers();

    /// @notice Error when the recipient is invalid.
    /// @dev Selector 0xe2fe2726.
    error InvalidRefundAddress();

    /// @notice Peer chain ID cannot be zero.
    error InvalidPeerChainIdZero();

    /// @notice Peer cannot be the zero address.
    error InvalidPeerZeroAddress();

    /// @notice Peer cannot be on the same chain
    /// @dev Selector 0x20371f2a.
    error InvalidPeerSameChainId();

    /// @notice Peer for the chain does not match the configuration.
    /// @param chainId ChainId of the source chain.
    /// @param peerAddress Address of the peer nttManager contract.
    error InvalidPeer(uint16 chainId, bytes32 peerAddress);

    /// @notice Error when the manager doesn't have a peer registered for the destination chain
    /// @dev Selector 0x3af256bc.
    /// @param chainId The target Wormhole chain id
    error PeerNotRegistered(uint16 chainId);

    /// @notice Called by a Transceiver contract to deliver a verified attestation.
    /// @dev This function enforces attestation threshold and replay logic for messages. Once all
    ///      validations are complete, this function calls `executeMsg` to execute the command specified
    ///      by the message.
    /// @param sourceChainId The Wormhole chain id of the sender.
    /// @param sourceNttManagerAddress The address of the sender's NTT Manager contract.
    /// @param payload The VAA payload.
    function attestationReceived(
        uint16 sourceChainId,
        bytes32 sourceNttManagerAddress,
        TransceiverStructs.NttManagerMessage memory payload
    ) external;

    /// @notice Called after a message has been sufficiently verified to execute
    ///         the command in the message.
    /// @dev This function is exposed as a fallback for when an `Transceiver` is deregistered
    ///      when a message is in flight.
    /// @param sourceChainId The Wormhole chain id of the sender.
    /// @param sourceNttManagerAddress The address of the sender's nttManager contract.
    /// @param message The message to execute.
    function executeMsg(
        uint16 sourceChainId,
        bytes32 sourceNttManagerAddress,
        TransceiverStructs.NttManagerMessage memory message
    ) external;

    /// @notice Fetch the delivery price for a given recipient chain transfer.
    /// @param recipientChain The Wormhole chain ID of the transfer destination.
    /// @param transceiverInstructions The transceiver specific instructions for quoting and sending
    /// @return - The delivery prices associated with each enabled endpoint and the total price.
    function quoteDeliveryPrice(
        uint16 recipientChain,
        bytes memory transceiverInstructions
    ) external view returns (uint256[] memory, uint256);

    /// @notice Sets the threshold for the number of attestations required for a message
    /// from a specific source chain to be considered valid.
    /// @param sourceChain The chain ID to set the threshold for.
    /// @param threshold The new threshold (number of attestations).
    /// @dev This method can only be executed by the `owner`.
    function setThreshold(uint16 sourceChain, uint8 threshold) external;

    /// @notice Sets the transceiver.
    /// @param transceiver The address of the transceiver.
    /// @dev This method can only be executed by the `owner`.
    function setTransceiver(
        address transceiver
    ) external;

    /// @notice Removes the transceiver.
    /// @param transceiver The address of the transceiver.
    /// @dev This method can only be executed by the `owner`.
    function removeTransceiver(
        address transceiver
    ) external;

    /// @param sourceChain The chain ID of the message source.
    /// @param digest The digest of the message.
    /// @return approved Boolean indicating if the message is approved for execution.
    function isMessageApprovedForChain(
        uint16 sourceChain,
        bytes32 digest
    ) external view returns (bool approved);

    /// @notice Checks if a message has been executed.
    /// @param digest The digest of the message.
    /// @return - Boolean indicating if message has been executed.
    function isMessageExecuted(
        bytes32 digest
    ) external view returns (bool);

    /// @notice Returns the next message sequence.
    function nextMessageSequence() external view returns (uint64);

    /// @notice Upgrades to a new manager implementation.
    /// @dev This is upgraded via a proxy, and can only be executed
    /// by the `owner`.
    /// @param newImplementation The address of the new implementation.
    function upgrade(
        address newImplementation
    ) external;

    /// @notice Pauses the manager.
    function pause() external;

    /// @notice Returns the number of Transceivers that must attest to a msgId for
    /// it to be considered valid and acted upon from a particular source chain.
    /// @param sourceChain The chain ID.
    /// @return threshold The threshold for this chain.
    function getThreshold(
        uint16 sourceChain
    ) external view returns (uint8 threshold);

    /// @notice Returns a boolean indicating if the transceiver has attested to the message.
    /// @param digest The digest of the message.
    /// @param index The index of the transceiver
    /// @return - Boolean indicating whether the transceiver at index `index` attested to a message digest
    function transceiverAttestedToMessage(
        bytes32 digest,
        uint8 index
    ) external view returns (bool);

    /// @notice Returns the number of attestations for a given message.
    /// @param digest The digest of the message.
    /// @return count The number of attestations received for the given message digest
    function messageAttestations(
        bytes32 digest
    ) external view returns (uint8 count);

    /// @notice Returns the chain ID.
    function chainId() external view returns (uint16);
}
"
    },
    "lib/wormhole-solidity-sdk/src/libraries/BytesParsing.sol": {
      "content": "// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;

library BytesParsing {
  uint256 private constant freeMemoryPtr = 0x40;
  uint256 private constant wordSize = 32;

  error OutOfBounds(uint256 offset, uint256 length);
  error LengthMismatch(uint256 encodedLength, uint256 expectedLength);
  error InvalidBoolVal(uint8 val);

  function checkBound(uint offset, uint length) internal pure {
    if (offset > length)
      revert OutOfBounds(offset, length);
  }

  function checkLength(bytes memory encoded, uint256 expected) internal pure {
    if (encoded.length != expected)
      revert LengthMismatch(encoded.length, expected);
  }

  function sliceUnchecked(
    bytes memory encoded,
    uint offset,
    uint length
  ) internal pure returns (bytes memory ret, uint nextOffset) {
    //bail early for degenerate case
    if (length == 0)
      return (new bytes(0), offset);

    assembly ("memory-safe") {
      nextOffset := add(offset, length)
      ret := mload(freeMemoryPtr)

      //Explanation on how we copy data here:
      //  The bytes type has the following layout in memory:
      //    [length: 32 bytes, data: length bytes]
      //  So if we allocate `bytes memory foo = new bytes(1);` then `foo` will be a pointer to 33
      //    bytes where the first 32 bytes contain the length and the last byte is the actual data.
      //  Since mload always loads 32 bytes of memory at once, we use our shift variable to align
      //    our reads so that our last read lines up exactly with the last 32 bytes of `encoded`.
      //  However this also means that if the length of `encoded` is not a multiple of 32 bytes, our
      //    first read will necessarily partly contain bytes from `encoded`'s 32 length bytes that
      //    will be written into the length part of our `ret` slice.
      //  We remedy this issue by writing the length of our `ret` slice at the end, thus
      //    overwritting those garbage bytes.
      let shift := and(length, 31) //equivalent to `mod(length, 32)` but 2 gas cheaper
      if iszero(shift) {
        shift := wordSize
      }

      let dest := add(ret, shift)
      let end := add(dest, length)
      for {
        let src := add(add(encoded, shift), offset)
      } lt(dest, end) {
        src := add(src, wordSize)
        dest := add(dest, wordSize)
      } {
        mstore(dest, mload(src))
      }

      mstore(ret, length)
      //When compiling with --via-ir then normally allocated memory (i.e. via new) will have 32 byte
      //  memory alignment and so we enforce the same memory alignment here.
      mstore(freeMemoryPtr, and(add(dest, 31), not(31)))
    }
  }

  function slice(
    bytes memory encoded,
    uint offset,
    uint length
  ) internal pure returns (bytes memory ret, uint nextOffset) {
    (ret, nextOffset) = sliceUnchecked(encoded, offset, length);
    checkBound(nextOffset, encoded.length);
  }

  function asAddressUnchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (address, uint) {
    (uint160 ret, uint nextOffset) = asUint160Unchecked(encoded, offset);
    return (address(ret), nextOffset);
  }

  function asAddress(
    bytes memory encoded,
    uint offset
  ) internal pure returns (address ret, uint nextOffset) {
    (ret, nextOffset) = asAddressUnchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

  function asBoolUnchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bool, uint) {
    (uint8 val, uint nextOffset) = asUint8Unchecked(encoded, offset);
    if (val & 0xfe != 0)
      revert InvalidBoolVal(val);

    uint cleanedVal = uint(val);
    bool ret;
    //skip 2x iszero opcode
    assembly ("memory-safe") {
      ret := cleanedVal
    }
    return (ret, nextOffset);
  }

  function asBool(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bool ret, uint nextOffset) {
    (ret, nextOffset) = asBoolUnchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

/* -------------------------------------------------------------------------------------------------
Remaining library code below was auto-generated by via the following js/node code:

for (let bytes = 1; bytes <= 32; ++bytes) {
  const bits = bytes*8;
  console.log(
`function asUint${bits}Unchecked(
  bytes memory encoded,
  uint offset
) internal pure returns (uint${bits} ret, uint nextOffset) {
  assembly ("memory-safe") {
    nextOffset := add(offset, ${bytes})
    ret := mload(add(encoded, nextOffset))
  }
  return (ret, nextOffset);
}

function asUint${bits}(
  bytes memory encoded,
  uint offset
) internal pure returns (uint${bits} ret, uint nextOffset) {
  (ret, nextOffset) = asUint${bits}Unchecked(encoded, offset);
  checkBound(nextOffset, encoded.length);
}

function asBytes${bytes}Unchecked(
  bytes memory encoded,
  uint offset
) internal pure returns (bytes${bytes}, uint) {
  (uint${bits} ret, uint nextOffset) = asUint${bits}Unchecked(encoded, offset);
  return (bytes${bytes}(ret), nextOffset);
}

function asBytes${bytes}(
  bytes memory encoded,
  uint offset
) internal pure returns (bytes${bytes}, uint) {
  (uint${bits} ret, uint nextOffset) = asUint${bits}(encoded, offset);
  return (bytes${bytes}(ret), nextOffset);
}
`
  );
}
------------------------------------------------------------------------------------------------- */

  function asUint8Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint8 ret, uint nextOffset) {
    assembly ("memory-safe") {
      nextOffset := add(offset, 1)
      ret := mload(add(encoded, nextOffset))
    }
    return (ret, nextOffset);
  }

  function asUint8(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint8 ret, uint nextOffset) {
    (ret, nextOffset) = asUint8Unchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

  function asBytes1Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes1, uint) {
    (uint8 ret, uint nextOffset) = asUint8Unchecked(encoded, offset);
    return (bytes1(ret), nextOffset);
  }

  function asBytes1(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes1, uint) {
    (uint8 ret, uint nextOffset) = asUint8(encoded, offset);
    return (bytes1(ret), nextOffset);
  }

  function asUint16Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint16 ret, uint nextOffset) {
    assembly ("memory-safe") {
      nextOffset := add(offset, 2)
      ret := mload(add(encoded, nextOffset))
    }
    return (ret, nextOffset);
  }

  function asUint16(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint16 ret, uint nextOffset) {
    (ret, nextOffset) = asUint16Unchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

  function asBytes2Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes2, uint) {
    (uint16 ret, uint nextOffset) = asUint16Unchecked(encoded, offset);
    return (bytes2(ret), nextOffset);
  }

  function asBytes2(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes2, uint) {
    (uint16 ret, uint nextOffset) = asUint16(encoded, offset);
    return (bytes2(ret), nextOffset);
  }

  function asUint24Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint24 ret, uint nextOffset) {
    assembly ("memory-safe") {
      nextOffset := add(offset, 3)
      ret := mload(add(encoded, nextOffset))
    }
    return (ret, nextOffset);
  }

  function asUint24(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint24 ret, uint nextOffset) {
    (ret, nextOffset) = asUint24Unchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

  function asBytes3Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes3, uint) {
    (uint24 ret, uint nextOffset) = asUint24Unchecked(encoded, offset);
    return (bytes3(ret), nextOffset);
  }

  function asBytes3(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes3, uint) {
    (uint24 ret, uint nextOffset) = asUint24(encoded, offset);
    return (bytes3(ret), nextOffset);
  }

  function asUint32Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint32 ret, uint nextOffset) {
    assembly ("memory-safe") {
      nextOffset := add(offset, 4)
      ret := mload(add(encoded, nextOffset))
    }
    return (ret, nextOffset);
  }

  function asUint32(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint32 ret, uint nextOffset) {
    (ret, nextOffset) = asUint32Unchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

  function asBytes4Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes4, uint) {
    (uint32 ret, uint nextOffset) = asUint32Unchecked(encoded, offset);
    return (bytes4(ret), nextOffset);
  }

  function asBytes4(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes4, uint) {
    (uint32 ret, uint nextOffset) = asUint32(encoded, offset);
    return (bytes4(ret), nextOffset);
  }

  function asUint40Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint40 ret, uint nextOffset) {
    assembly ("memory-safe") {
      nextOffset := add(offset, 5)
      ret := mload(add(encoded, nextOffset))
    }
    return (ret, nextOffset);
  }

  function asUint40(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint40 ret, uint nextOffset) {
    (ret, nextOffset) = asUint40Unchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

  function asBytes5Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes5, uint) {
    (uint40 ret, uint nextOffset) = asUint40Unchecked(encoded, offset);
    return (bytes5(ret), nextOffset);
  }

  function asBytes5(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes5, uint) {
    (uint40 ret, uint nextOffset) = asUint40(encoded, offset);
    return (bytes5(ret), nextOffset);
  }

  function asUint48Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint48 ret, uint nextOffset) {
    assembly ("memory-safe") {
      nextOffset := add(offset, 6)
      ret := mload(add(encoded, nextOffset))
    }
    return (ret, nextOffset);
  }

  function asUint48(
    bytes memory encoded,
    uint offset
  ) internal pure returns (uint48 ret, uint nextOffset) {
    (ret, nextOffset) = asUint48Unchecked(encoded, offset);
    checkBound(nextOffset, encoded.length);
  }

  function asBytes6Unchecked(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes6, uint) {
    (uint48 ret, uint nextOffset) = asUint48Unchecked(encoded, offset);
    return (bytes6(ret), nextOffset);
  }

  function asBytes6(
    bytes memory encoded,
    uint offset
  ) internal pure returns (bytes6, uint) {
    (uint48 ret, uint nextOffset) = asUint48(encoded, offset);
    return (bytes6(ret), nextOffset);
  }

  function asUint56Unchecked(
    bytes memory encoded,
    uint offset\

Tags:
Multisig, Voting, Upgradeable, Multi-Signature, Factory|addr:0x9e16fd97570e5367f3583ddf3a3b5c36521469e2|verified:true|block:23721714|tx:0xec9c4ae86defd67e4795fdbbccaf98cb8171d69113be3b64e4f3b721bde5b619|first_check:1762246626

Submitted on: 2025-11-04 09:57:08

Comments

Log in to comment.

No comments yet.