synapsecns/sanguine

View on GitHub
packages/contracts-core/contracts/libs/stack/Header.sol

Summary

Maintainability
Test Coverage
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import {FlagOutOfRange} from "../Errors.sol";

/// Header is encoded data with "general routing information".
type Header is uint136;

using HeaderLib for Header global;

/// Types of messages supported by Origin-Destination
/// - Base: message sent by protocol user, contains tips
/// - Manager: message sent between AgentManager contracts located on different chains, no tips
enum MessageFlag {
    Base,
    Manager
}

using HeaderLib for MessageFlag global;

/// Library for formatting _the header part_ of _the messages used by Origin and Destination_.
/// - Header represents general information for routing a Message for Origin and Destination.
/// - Header occupies a single storage word, and thus is stored on stack instead of being stored in memory.
///
/// # Header stack layout (from highest bits to lowest)
///
/// | Position   | Field            | Type   | Bytes | Description                             |
/// | ---------- | ---------------- | ------ | ----- | --------------------------------------- |
/// | (017..016] | flag             | uint8  | 1     | Flag specifying the type of message     |
/// | (016..012] | origin           | uint32 | 4     | Domain where message originated         |
/// | (012..008] | nonce            | uint32 | 4     | Message nonce on the origin domain      |
/// | (008..004] | destination      | uint32 | 4     | Domain where message will be executed   |
/// | (004..000] | optimisticPeriod | uint32 | 4     | Optimistic period that will be enforced |
library HeaderLib {
    /// @dev Amount of bits to shift to flag field
    uint136 private constant SHIFT_FLAG = 16 * 8;
    /// @dev Amount of bits to shift to origin field
    uint136 private constant SHIFT_ORIGIN = 12 * 8;
    /// @dev Amount of bits to shift to nonce field
    uint136 private constant SHIFT_NONCE = 8 * 8;
    /// @dev Amount of bits to shift to destination field
    uint136 private constant SHIFT_DESTINATION = 4 * 8;

    /// @notice Returns an encoded header with provided fields
    /// @param origin_              Domain of origin chain
    /// @param nonce_               Message nonce on origin chain
    /// @param destination_         Domain of destination chain
    /// @param optimisticPeriod_    Optimistic period for message execution
    function encodeHeader(
        MessageFlag flag_,
        uint32 origin_,
        uint32 nonce_,
        uint32 destination_,
        uint32 optimisticPeriod_
    ) internal pure returns (Header) {
        // All casts below are upcasts, so they are safe
        // forgefmt: disable-next-item
        return Header.wrap(
            uint136(uint8(flag_)) << SHIFT_FLAG |
            uint136(origin_) << SHIFT_ORIGIN |
            uint136(nonce_) << SHIFT_NONCE |
            uint136(destination_) << SHIFT_DESTINATION |
            uint136(optimisticPeriod_)
        );
    }

    /// @notice Checks that the header is a valid encoded header.
    function isHeader(uint256 paddedHeader) internal pure returns (bool) {
        // Check that flag is within range
        return _flag(paddedHeader) <= uint8(type(MessageFlag).max);
    }

    /// @notice Wraps the padded encoded request into a Header-typed value.
    /// @dev The "padded" header is simply an encoded header casted to uint256 (highest bits are set to zero).
    /// Casting to uint256 is done automatically in Solidity, so no extra actions from consumers are needed.
    /// The highest bits are discarded, so that the contracts dealing with encoded headers
    /// don't need to be updated, if a new field is added.
    function wrapPadded(uint256 paddedHeader) internal pure returns (Header) {
        // Check that flag is within range
        if (!isHeader(paddedHeader)) revert FlagOutOfRange();
        // Casting to uint136 will truncate the highest bits, which is the behavior we want
        return Header.wrap(uint136(paddedHeader));
    }

    /// @notice Returns header's hash: a leaf to be inserted in the "Message mini-Merkle tree".
    function leaf(Header header) internal pure returns (bytes32 hashedHeader) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            // Store header in scratch space
            mstore(0, header)
            // Compute hash of header padded to 32 bytes
            hashedHeader := keccak256(0, 32)
        }
    }

    // ══════════════════════════════════════════════ HEADER SLICING ═══════════════════════════════════════════════════

    /// @notice Returns header's flag field
    function flag(Header header) internal pure returns (MessageFlag) {
        // We check that flag is within range when wrapping the header, so this cast is safe
        return MessageFlag(_flag(Header.unwrap(header)));
    }

    /// @notice Returns header's origin field
    function origin(Header header) internal pure returns (uint32) {
        // Casting to uint32 will truncate the highest bits, which is the behavior we want
        return uint32(Header.unwrap(header) >> SHIFT_ORIGIN);
    }

    /// @notice Returns header's nonce field
    function nonce(Header header) internal pure returns (uint32) {
        // Casting to uint32 will truncate the highest bits, which is the behavior we want
        return uint32(Header.unwrap(header) >> SHIFT_NONCE);
    }

    /// @notice Returns header's destination field
    function destination(Header header) internal pure returns (uint32) {
        // Casting to uint32 will truncate the highest bits, which is the behavior we want
        return uint32(Header.unwrap(header) >> SHIFT_DESTINATION);
    }

    /// @notice Returns header's optimistic seconds field
    function optimisticPeriod(Header header) internal pure returns (uint32) {
        // Casting to uint32 will truncate the highest bits, which is the behavior we want
        return uint32(Header.unwrap(header));
    }

    // ══════════════════════════════════════════════ PRIVATE HELPERS ══════════════════════════════════════════════════

    /// @dev Returns header's flag field without casting to MessageFlag
    function _flag(uint256 paddedHeader) private pure returns (uint8) {
        // Casting to uint8 will truncate the highest bits, which is the behavior we want
        return uint8(paddedHeader >> SHIFT_FLAG);
    }
}