synapsecns/sanguine

View on GitHub
packages/contracts-core/contracts/libs/memory/BaseMessage.sol

Summary

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

import {MemView, MemViewLib} from "./MemView.sol";
import {REQUEST_LENGTH, TIPS_LENGTH} from "../Constants.sol";
import {UnformattedBaseMessage} from "../Errors.sol";
import {MerkleMath} from "../merkle/MerkleMath.sol";
import {Request, RequestLib} from "../stack/Request.sol";
import {Tips, TipsLib} from "../stack/Tips.sol";

/// BaseMessage is a memory view over the base message supported by Origin-Destination
type BaseMessage is uint256;

using BaseMessageLib for BaseMessage global;

/// BaseMessage structure represents a base message sent via the Origin-Destination contracts.
/// - It only contains data relevant to the base message, the rest of data is encoded in the message header.
/// - `sender` and `recipient` for EVM chains are EVM addresses casted to bytes32, while preserving left-alignment.
/// - `tips` and `request` parameters are specified by a message sender
/// > Origin will calculate minimum tips for given request and content length, and will reject messages with tips
/// lower than that.
///
/// # Memory layout of BaseMessage fields
///
/// | Position   | Field     | Type    | Bytes | Description                            |
/// | ---------- | --------- | ------- | ----- | -------------------------------------- |
/// | [000..032) | tips      | uint256 | 32    | Encoded tips paid on origin chain      |
/// | [032..064) | sender    | bytes32 | 32    | Sender address on origin chain         |
/// | [064..096) | recipient | bytes32 | 32    | Recipient address on destination chain |
/// | [096..116) | request   | uint160 | 20    | Encoded request for message execution  |
/// | [104..AAA) | content   | bytes   | ??    | Content to be passed to recipient      |
library BaseMessageLib {
    using MemViewLib for bytes;

    /// @dev The variables below are not supposed to be used outside of the library directly.
    uint256 private constant OFFSET_TIPS = 0;
    uint256 private constant OFFSET_SENDER = 32;
    uint256 private constant OFFSET_RECIPIENT = 64;
    uint256 private constant OFFSET_REQUEST = OFFSET_RECIPIENT + TIPS_LENGTH;
    uint256 private constant OFFSET_CONTENT = OFFSET_REQUEST + REQUEST_LENGTH;

    // ═══════════════════════════════════════════════ BASE MESSAGE ════════════════════════════════════════════════════

    /**
     * @notice Returns a formatted BaseMessage payload with provided fields.
     * @param tips_         Encoded tips information
     * @param sender_       Sender address on origin chain
     * @param recipient_    Recipient address on destination chain
     * @param request_      Encoded request for message execution
     * @param content_      Raw content to be passed to recipient on destination chain
     * @return Formatted base message
     */
    function formatBaseMessage(Tips tips_, bytes32 sender_, bytes32 recipient_, Request request_, bytes memory content_)
        internal
        pure
        returns (bytes memory)
    {
        return abi.encodePacked(tips_, sender_, recipient_, request_, content_);
    }

    /**
     * @notice Returns a BaseMessage view over the given payload.
     * @dev Will revert if the payload is not a base message.
     */
    function castToBaseMessage(bytes memory payload) internal pure returns (BaseMessage) {
        return castToBaseMessage(payload.ref());
    }

    /**
     * @notice Casts a memory view to a BaseMessage view.
     * @dev Will revert if the memory view is not over a base message payload.
     */
    function castToBaseMessage(MemView memView) internal pure returns (BaseMessage) {
        if (!isBaseMessage(memView)) revert UnformattedBaseMessage();
        return BaseMessage.wrap(MemView.unwrap(memView));
    }

    /// @notice Checks that a payload is a formatted BaseMessage.
    function isBaseMessage(MemView memView) internal pure returns (bool) {
        // Check if sender, recipient, tips fields exist
        return (memView.len() >= OFFSET_CONTENT);
        // Content could be empty, so we don't check that
    }

    /// @notice Convenience shortcut for unwrapping a view.
    function unwrap(BaseMessage baseMessage) internal pure returns (MemView) {
        return MemView.wrap(BaseMessage.unwrap(baseMessage));
    }

    /// @notice Returns baseMessage's hash: a leaf to be inserted in the "Message mini-Merkle tree".
    function leaf(BaseMessage baseMessage) internal pure returns (bytes32) {
        // We hash "tips" and "everything but tips" to make tips proofs easier to verify
        return MerkleMath.getParent(baseMessage.tips().leaf(), baseMessage.bodyLeaf());
    }

    /// @notice Returns hash for the "everything but tips" part of the base message.
    function bodyLeaf(BaseMessage baseMessage) internal pure returns (bytes32) {
        return baseMessage.unwrap().sliceFrom({index_: OFFSET_SENDER}).keccak();
    }

    // ═══════════════════════════════════════════ BASE MESSAGE SLICING ════════════════════════════════════════════════

    /// @notice Returns encoded tips paid on origin chain.
    function tips(BaseMessage baseMessage) internal pure returns (Tips) {
        return TipsLib.wrapPadded((baseMessage.unwrap().indexUint({index_: OFFSET_TIPS, bytes_: TIPS_LENGTH})));
    }

    /// @notice Returns sender address on origin chain.
    function sender(BaseMessage baseMessage) internal pure returns (bytes32) {
        return baseMessage.unwrap().index({index_: OFFSET_SENDER, bytes_: 32});
    }

    /// @notice Returns recipient address on destination chain.
    function recipient(BaseMessage baseMessage) internal pure returns (bytes32) {
        return baseMessage.unwrap().index({index_: OFFSET_RECIPIENT, bytes_: 32});
    }

    /// @notice Returns an encoded request for message execution on destination chain.
    function request(BaseMessage baseMessage) internal pure returns (Request) {
        return RequestLib.wrapPadded((baseMessage.unwrap().indexUint({index_: OFFSET_REQUEST, bytes_: REQUEST_LENGTH})));
    }

    /// @notice Returns an untyped memory view over the content to be passed to recipient.
    function content(BaseMessage baseMessage) internal pure returns (MemView) {
        return baseMessage.unwrap().sliceFrom({index_: OFFSET_CONTENT});
    }
}