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

import {BytesLib} from "./BytesLib.sol";
import {IHandler} from "../../interfaces/IHandler.sol";

import "./BaseMessage.sol";

/// @title MessageLib
/// @author The Warp Team <contact@warp.inc>
/// @notice A library for encoding and decoding cross-chain messages according
///         to the Basic Abstract Messaging specification.
library MessageLib {
    using BytesLib for bytes;

    /// @notice A cross-chain message.
    /// @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.
    struct Message {
        uint64 index;
        uint16 targetChain;
        bytes targetAddress;
        bytes sender;
        bytes contents;
    }

    /// @notice A 1-byte message type indicator.
    uint8 public constant MESSAGE_TYPE = 0;

    /// @notice The minimum length of an encoded message.
    /// @dev Composed of:
    ///      - magicByte - 1 byte.
    ///      - version - 1 byte.
    ///      - messageType - 1 byte.
    ///      - index - 8 bytes.
    ///      - targetChain - 2 bytes.
    ///      - targetAddressLen - 2 bytes.
    ///      - smallest possible targetAddress - 1 byte.
    ///      - senderLen - 2 bytes.
    ///      - contentsLen - 2 bytes.
    /// @dev Equals 20 bytes.
    uint256 public constant MINIMUM_ENCODED_LEN = 1 + 1 + 1 + 8 + 2 + 2 + 1 + 2 + 2;

    /// @notice Details for the index in the encoding scheme.
    uint256 private constant INDEX_OFFSET = MESSAGE_TYPE_OFFSET + MESSAGE_TYPE_SIZE;
    uint256 private constant INDEX_SIZE = 8;

    /// @notice Details for the targetChain in the encoding scheme.
    uint256 private constant TARGET_CHAIN_OFFSET = INDEX_OFFSET + INDEX_SIZE;
    uint256 private constant TARGET_CHAIN_SIZE = 2;

    /// @notice Offset for the target address length-prefixed bytearray in the
    ///         encoding scheme.
    uint256 private constant TARGET_ADDRESS_OFFSET = TARGET_CHAIN_OFFSET + TARGET_CHAIN_SIZE;

    /// @notice Encode a message into bytes.
    /// @dev The message 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
    /// @custom:reverts As encodeBody.
    /// @return The encoded message as a bytearray.
    function encode(Message memory message) internal pure returns (bytes memory) {
        return abi.encodePacked(encodeHeader(MESSAGE_TYPE), encodeBody(message));
    }

    /// @notice Encode a message body into bytes.
    /// @dev The message body follows a simple specification:
    ///      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
    /// @custom:reverts Errors when the message, targetAddress, or sender is too
    ///                 large; or if the targetAddress is empty.
    /// @return The encoded message body as a bytearray.
    function encodeBody(Message memory message) internal pure returns (bytes memory) {
        if (message.targetAddress.length > type(uint16).max || message.targetAddress.length == 0) {
            revert AddressLength();
        }
        if (message.contents.length > type(uint16).max) revert ContentsLength();
        if (message.sender.length > type(uint16).max) revert SenderLength();

        // length-prepend the bytearrays
        bytes memory targetAddress = abi.encodePacked(uint16(message.targetAddress.length), message.targetAddress);
        bytes memory sender = abi.encodePacked(uint16(message.sender.length), message.sender);
        bytes memory contents = abi.encodePacked(uint16(message.contents.length), message.contents);

        return abi.encodePacked(message.index, message.targetChain, targetAddress, sender, contents);
    }

    /// @notice Decode a message from a bytes representation, without checking
    ///         the header.
    /// @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 Message
    ///                 struct.
    function decodeBody(bytes memory encoded) internal pure returns (Message memory decoded, uint256 consumed) {
        // 8 bytes at position 3
        decoded.index = encoded.toUint64(INDEX_OFFSET);
        // 2 bytes at position 11
        decoded.targetChain = encoded.toUint16(TARGET_CHAIN_OFFSET);
        // length-prefixed bytearray at position 13
        decoded.targetAddress = readU16PrefixedBytes(encoded, TARGET_ADDRESS_OFFSET);

        // We are now in the dynamic section, so we start tracking our offset
        // in the encoded data.
        consumed = TARGET_ADDRESS_OFFSET + 2 + decoded.targetAddress.length;

        // computed as the targetOffset + 2 (the size of the length prefix in
        // bytes) + the value of the length prefix (the length of the
        // targetAddress in bytes)
        decoded.sender = readU16PrefixedBytes(encoded, consumed);
        consumed += 2 + decoded.sender.length;

        // computed as the senderOffset + 2 (the size of the length prefix in
        // bytes) + the value of the length prefix (the length of the
        // sender in bytes)
        decoded.contents = readU16PrefixedBytes(encoded, consumed);
        consumed += 2 + decoded.contents.length;
    }

    /// @notice Decode a message from a bytes representation,
    ///         checking the 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 (Message 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(Message memory message) internal pure returns (IHandler) {
        return extractAddress(message.targetAddress);
    }
}
[{"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"}]