// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {BytesLib} from "./BytesLib.sol";
import {MessageLib} from "./MessageLib.sol";

import "./BaseMessage.sol";

/// @title ExtendedMessageLib
/// @author The Warp Team <contact@warp.inc>
/// @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;
    }
}
[{"inputs":[{"internalType":"address","name":"attestor","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ATTESTOR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"core","type":"address"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"deploy","outputs":[],"stateMutability":"nonpayable","type":"function"}]