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

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

import "./BaseMessage.sol";

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

    /// @notice A cross-chain message with expected value.
    /// @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.
    ///      - bridge - An application-specified identifier for a bridge.
    ///      - assetIdentifier - An application-specified identifier for an
    ///        asset, which may be in a bridge-specific format.
    ///      - amount - The amount of the asset to be transferred, which may be
    ///        in a bridge- or asset-specific format.
    struct TokenMessage {
        uint64 index;
        uint16 targetChain;
        bytes targetAddress;
        bytes sender;
        bytes contents;
        uint8 bridge;
        bytes assetIdentifier;
        uint256 amount;
    }

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

    /// @notice The minimum length of an encoded message.
    /// @dev Composed of:
    ///      - MessageLib.MINIMUM_ENCODED_LEN - 20 bytes.
    ///      - bridge - 1 byte.
    ///      - assetIdentifierLen - 2 bytes.
    ///      - smallest possible assetIdentifier - 1 byte.
    ///      - amount - 32 bytes.
    /// @dev Equals 56 bytes.
    uint256 private constant MINIMUM_ENCODED_LEN = MessageLib.MINIMUM_ENCODED_LEN + 1 + 2 + 1 + 32;

    /// @notice An error indicating that the asset identifier is too large to
    /// encode OR the asset identifier is empty.
    /// @dev Selector 0x9b29342f.
    error AssetIdentifierLength();

    /// @notice An error indicating that the amount about to be encoded is zero.
    /// @dev Selector 0x1f2a2005.
    error ZeroAmount();

    /// @notice Encode a message into a bytes representation.
    /// @dev Because `MessageLib.Message` is a prefix of `TokenMessage`,
    ///      this function simply calls `MessageLib.encodeBody` to encode shared message fields.
    /// @dev The message with tokens 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
    ///      WITH TOKENS
    ///      - bridge - 1 byte.
    ///      - assetIdentifier - 2 bytes + assetIdentifier bytearray
    ///      - amount - 32 bytes.
    /// @custom:reverts As MessageLib.encodeBody.
    /// @custom:reverts As encodeWithTokens
    /// @return The encoded message as a bytearray.
    function encode(TokenMessage memory message) internal pure returns (bytes memory) {
        return abi.encodePacked(
            encodeHeader(MESSAGE_TYPE), MessageLib.encodeBody(toMessage(message)), encodeWithTokens(message)
        );
    }

    /// @notice Encode the WithTokens portion of a message into bytes.
    /// @dev The WithTokens portion of a message follows a simple specification:
    ///      WITH TOKENS
    ///      - bridge - 1 byte.
    ///      - assetIdentifier - 2 bytes + assetIdentifier bytearray
    ///      - amount - 32 bytes.
    /// @custom:reverts AssetIdentifierLength() if the asset identifier is
    //                  too large or empty.
    /// @return The WithTokens portion of the message encoded as a bytearray.
    function encodeWithTokens(TokenMessage memory message) internal pure returns (bytes memory) {
        if (message.assetIdentifier.length > type(uint16).max || message.assetIdentifier.length == 0) {
            revert AssetIdentifierLength();
        }
        if (message.amount == 0) {
            revert ZeroAmount();
        }
        // length-prepend the assetIdentifier bytearray
        bytes memory assetIdentifier = abi.encodePacked(uint16(message.assetIdentifier.length), message.assetIdentifier);

        // encode the additional WithTokens fields on the end
        return abi.encodePacked(message.bridge, assetIdentifier, message.amount);
    }

    /// @notice Decode a message from a bytes representation, without checking
    ///         the header.
    /// @dev Because `MessageLib.Message` is a prefix of `TokenMessage`,
    ///      this function simply calls `MessageLib.decodeBody` to parse 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
    ///                 MessageWithValue struct.
    function decodeBody(bytes memory encoded) internal pure returns (TokenMessage memory decoded, uint256 consumed) {
        // decode the shared fields using MessageLib
        MessageLib.Message memory message;
        (message, consumed) = MessageLib.decodeBody(encoded);
        decoded = fromMessage(message);

        // Decode the WithTokens fields added on the end
        // In this segment we use the `consumed` variable to track the offset
        // into the encoded message.
        decoded.bridge = encoded.toUint8(consumed);
        consumed += 1;
        decoded.assetIdentifier = readU16PrefixedBytes(encoded, consumed);
        consumed += decoded.assetIdentifier.length + 2;
        decoded.amount = encoded.toUint256(consumed);
        consumed += 32;
    }

    /// @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 (TokenMessage 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(TokenMessage memory message) internal pure returns (IHandler) {
        return extractAddress(message.targetAddress);
    }

    /// @notice Transcribe shared fields from a Message into a TokenMessage
    /// @param message The Message.
    /// @return tokMessage The transcribed TokenMessage.
    /// @dev In the returned TokenMessage, fields bridge, assetIdentifier, and amount will be empty
    function fromMessage(MessageLib.Message memory message) private pure returns (TokenMessage memory tokMessage) {
        tokMessage.index = message.index;
        tokMessage.targetChain = message.targetChain;
        tokMessage.targetAddress = message.targetAddress;
        tokMessage.sender = message.sender;
        tokMessage.contents = message.contents;
    }

    /// @notice Transcribe shared fields from a TokenMessage into a Message
    /// @param tokMessage The TokenMessage.
    /// @return message The transcribed Message.
    /// @dev In the returned Message, fields bridge, assetIdentifier, and amount will have been erased
    function toMessage(TokenMessage memory tokMessage) private pure returns (MessageLib.Message memory message) {
        message.index = tokMessage.index;
        message.targetChain = tokMessage.targetChain;
        message.targetAddress = tokMessage.targetAddress;
        message.sender = tokMessage.sender;
        message.contents = tokMessage.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"}]