packages/contracts-core/contracts/libs/memory/BaseMessage.sol
// 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});
}
}