// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.13; import {BytesLib} from "./BytesLib.sol"; import {MessageLib} from "./MessageLib.sol"; import "./BaseMessage.sol"; /// @title ExtendedMessageLib /// @author The Warp Team /// @notice A library for encoding and decoding cross-chain messages /// meant to be delivered with extra data /// according to the Better Abstract Messaging specification. library ExtendedMessageLib { using BytesLib for bytes; /// @notice A cross-chain message meant to be delivered with extra data. /// @dev The fields are as follows. /// - index - The index of the message in its Outbox's message history. /// - targetChain - The chain ID of the destination chain. /// - targetAddress - The address of the recipient on the targetChain. /// - sender - The address of the sender on the source chain. /// - contents - The message contents. /// - relaySignal - Application-defined bytes meant to signal /// to relayers what type of ExtendedMessage this is, /// so that relayers can determine appropriate contents for /// `unverifiedRelayerData` during delivery. May also be used by /// applications to verify `unverifiedRelayerData.` struct ExtendedMessage { uint64 index; uint16 targetChain; bytes targetAddress; bytes sender; bytes contents; bytes relaySignal; } /// @notice A 1-byte message type indicator. uint8 public constant MESSAGE_TYPE = 2; /// @notice The minimum length of an encoded message. /// @dev Composed of: /// - MessageLib.MINIMUM_ENCODED_LEN - 20 bytes. /// - relaySignalLen - 2 bytes. /// - smallest possible relaySignal - 1 byte. /// @dev Equals 56 bytes. uint256 private constant MINIMUM_ENCODED_LEN = MessageLib.MINIMUM_ENCODED_LEN + 2 + 1; /// @notice An error indicating that the relaySignal is too large to /// encode OR the relaySignal is empty. /// @dev Selector 0xd8d0c518. error RelaySignalLength(); /// @notice Encode a message into a bytes representation. /// @dev Because `MessageLib.Message` is a prefix of `ExtendedMessage`, /// this function simply calls `MessageLib.encodeBody` to encode /// shared message fields. /// @dev The ExtendedMessage follows a simple specification: /// HEADER /// - magicByte - 1 byte /// - version - 1 byte /// - messageType - 1 byte /// BODY /// - index - 8 bytes /// - targetChain - 2 bytes /// - targetAddress - 2 bytes length + targetAddress bytearray /// - sender - 2 bytes length + sender bytearray /// - contents - 2 bytes length + contents bytearray /// EXTENSION /// - relaySignal - 2 bytes + relaySignal bytearray /// @custom:reverts As MessageLib.encodeBody. /// @custom:reverts As encodeSignal /// @return The encoded message as a bytearray. function encode(ExtendedMessage memory message) internal pure returns (bytes memory) { return abi.encodePacked( encodeHeader(MESSAGE_TYPE), MessageLib.encodeBody(toMessage(message)), encodeSignal(message) ); } /// @notice Encode the signal portion of a message into bytes. /// @dev The signal portion of a message follows a simple specification: /// EXTENSION /// - signal - 2 bytes + signal bytearray /// @custom:reverts RelaySignalLength() if the relaySignal is // too large or empty. /// @return signal Application-defined bytes meant to signal to relayers /// what type of ExtendedMessage this is, so that relayers can /// determine appropriate contents for `unverifiedRelayerData` /// during delivery. function encodeSignal(ExtendedMessage memory message) internal pure returns (bytes memory signal) { if (message.relaySignal.length > type(uint16).max || message.relaySignal.length == 0) { revert RelaySignalLength(); } // length-prepend the relaySignal bytearray signal = abi.encodePacked(uint16(message.relaySignal.length), message.relaySignal); } /// @notice Decode a message from a bytes representation, without checking /// the header. /// @dev Because `MessageLib.Message` is a prefix of `ExtendedMessage`, /// this function simply calls `MessageLib.decodeBody` to parse the shared fields. /// @param encoded The encoded message as a bytearray. /// @return decoded The decoded message. /// @return consumed The number of bytes consumed during decoding. /// @custom:reverts Errors if the input is not a valid encoded /// ExtendedMessage struct. function decodeBody(bytes memory encoded) internal pure returns (ExtendedMessage memory decoded, uint256 consumed) { // decode the shared fields using MessageLib MessageLib.Message memory message; (message, consumed) = MessageLib.decodeBody(encoded); decoded = fromMessage(message); // Decode the relaySignal field added on the end decoded.relaySignal = readU16PrefixedBytes(encoded, consumed); consumed += decoded.relaySignal.length + 2; } /// @notice Decode a message from a bytes representation, /// checking header and erroring if any bytes are left over. /// @param encoded The encoded message as a bytearray. /// @return decoded The decoded message. /// @custom:reverts as expectHeader() /// @custom:reverts as decodeBody() /// @custom:reverts Overrun() If the encoded message is too short. /// @custom:reverts LeftoverBytes() If decoding consumes less than the /// entire input. function decode(bytes memory encoded) internal pure returns (ExtendedMessage memory decoded) { if (encoded.length < MINIMUM_ENCODED_LEN) revert Overrun(); expectHeader(MESSAGE_TYPE, encoded); uint256 consumed; (decoded, consumed) = decodeBody(encoded); if (consumed != encoded.length) revert LeftoverBytes(); } /// @notice Extract the target address from a message, reverting if it is /// not an EVM address. /// @dev Wrapper for `extractAddress(bytes)` /// @param message The message from which to extract the target address. /// @return decoded An IHandler interface at the decoded EVM address. /// @custom:reverts as `extractAddress(bytes)` function extractEvmTarget(ExtendedMessage memory message) internal pure returns (IHandler) { return extractAddress(message.targetAddress); } /// @notice Transcribe shared fields from a Message into an ExtendedMessage /// @param message The Message. /// @return extMessage The transcribed ExtendedMessage. /// @dev In the returned ExtendedMessage, the relaySignal field will be empty function fromMessage(MessageLib.Message memory message) private pure returns (ExtendedMessage memory extMessage) { extMessage.index = message.index; extMessage.targetChain = message.targetChain; extMessage.targetAddress = message.targetAddress; extMessage.sender = message.sender; extMessage.contents = message.contents; } /// @notice Transcribe shared fields from a ExtendedMessage into a Message /// @param extMessage The ExtendedMessage. /// @return message The transcribed Message. /// @dev In the returned Message, the relaySignal field will have been erased function toMessage(ExtendedMessage memory extMessage) private pure returns (MessageLib.Message memory message) { message.index = extMessage.index; message.targetChain = extMessage.targetChain; message.targetAddress = extMessage.targetAddress; message.sender = extMessage.sender; message.contents = extMessage.contents; } }