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

import {RingBufferStorage} from "./RingBufferStorage.sol";
import {UsesCore} from "../shared/UsesCore.sol";
import {MessageLib} from "../shared/message/MessageLib.sol";
import {TokenMessageLib} from "../shared/message/TokenMessageLib.sol";
import {ExtendedMessageLib} from "../shared/message/ExtendedMessageLib.sol";

/// @title Outbox
/// @author The Warp Team <contact@warp.inc>
/// @notice This contract implements a generic cross-chain Outbox.
///
/// @dev The Outbox is a transport-agnostic message sender. It allows
///      applications to send messages 2 ways:
///        1. By publishing a message via the Wormhole core contract.
///        2. By storing the message hash in the Outbox contract state.
///      This ensures that messages may always be signed by the Wormhole
///      guardians, AND may be proven to any light client or oracle-based
///      verifier via Merkle-Patricia trie proofs retrieved via the
///      eth_getProof RPC method.
///
/// @dev Message hashes are stored in a ring buffer, and given an index in the
///      history of all messages. The message hash may be proven via MPT proof
///      while it is in the ring buffer. Once the message is removed from the
///      ring buffer, it may no longer be proven via MPT proof under a current
///      contract storage proof, but any archive node may still generate the
///      historical proof.
abstract contract Outbox is UsesCore, RingBufferStorage(2 ** 15) {
    using MessageLib for MessageLib.Message;
    using TokenMessageLib for TokenMessageLib.TokenMessage;
    using ExtendedMessageLib for ExtendedMessageLib.ExtendedMessage;

    /// @notice CONSISTENCY is passed as consistencyLevel to Wormhole Core
    ///         consistencyLevel 202 indicates final messages on all chains
    uint8 public constant CONSISTENCY = 202;

    /// @notice Emitted when a message is sent
    /// @dev Topic 0
    ///      0x02dee5df1fa0e8120f2e774312add7033b258f9c15fcb2c66c4f559bb507fa55
    /// @param index The index of the message in the history of all messages.
    /// @param wormholeSequence The sequence number of the message in the
    ///        Wormhole core send response.
    /// @param targetChain The Wormhole chain ID of the destination chain.
    /// @param targetAddress The address of the recipient on the targetChain.
    /// @param message The full encoded message.
    event Sent(
        uint64 indexed index,
        uint64 wormholeSequence,
        uint16 targetChain,
        bytes indexed targetAddress,
        bytes indexed message
    );

    /// @notice Emitted when a message is sent
    ///         that is meant to be delivered with extra un-verified data
    /// @dev Topic 0
    ///      0xbc107fad92d8ed23e5f0b2abe135332ab9bf80e68e9b6a25c0834fe38ced089b
    /// @param index The index of the message in the history of all messages.
    /// @param wormholeSequence The sequence number of the message in the
    ///        Wormhole core send response.
    /// @param targetChain The Wormhole chain ID of the destination chain.
    /// @param targetAddress The address of the recipient on the targetChain.
    /// @param message The full encoded message.
    event SentExt(
        uint64 indexed index,
        uint64 wormholeSequence,
        uint16 targetChain,
        bytes indexed targetAddress,
        bytes indexed message
    );

    /// @notice Emitted when a message with tokens is sent.
    /// @dev Identical body to Sent, but with a different topic0.
    /// @dev Topic 0
    ///      0xec6a9cdec02327573aacebdab9311dc8dbd12d36cd56d585081142d9f19aabb1
    /// @param index The index of the message in the history of all messages.
    /// @param wormholeSequence The sequence number of the message in the
    ///        Wormhole core send response.
    /// @param targetChain The Wormhole chain ID of the destination chain.
    /// @param targetAddress The address of the recipient on the targetChain.
    /// @param message The full encoded message.
    event SentWithTokens(
        uint64 indexed index,
        uint64 wormholeSequence,
        uint16 targetChain,
        bytes indexed targetAddress,
        bytes indexed message
    );

    /// @notice Send a message to the the target on the target chain.
    /// @dev Emits the message via Wormhole core, and stores the message
    ///      hash in the ring buffer.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As MessageLib.encode may revert.
    /// @custom:emits Sent(index, sequence, targetChain, targetAddress, encoded)
    function send(uint16 targetChain, bytes calldata targetAddress, bytes calldata contents)
        external
        returns (uint64 index, uint64 sequence)
    {
        return send(pack(targetChain, targetAddress, contents));
    }

    /// @notice Send a message to the the target address on the target chain.
    /// @dev Alias for `send` when sending to EVM chains specifically.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As MessageLib.encode may revert.
    /// @custom:emits Sent(index, sequence, targetChain, targetAddress, encoded)
    function send(uint16 targetChain, address targetAddress, bytes calldata contents)
        external
        returns (uint64 index, uint64 sequence)
    {
        return send(pack(targetChain, abi.encodePacked(targetAddress), contents));
    }

    /// @notice Send a message to the the target on the target chain.
    /// @dev Emits the message via Wormhole core, and stores the message
    ///      hash in the ring buffer.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @param relaySignal Meant to signal to off-chain relayers what type of ExtendedMessage this is.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As ExtendedMessageLib.encode may revert.
    /// @custom:emits SentExt(index, sequence, targetChain, targetAddress, encoded)
    function sendExt(
        uint16 targetChain,
        bytes calldata targetAddress,
        bytes calldata contents,
        bytes calldata relaySignal
    ) external returns (uint64 index, uint64 sequence) {
        return sendExt(pack(targetChain, targetAddress, contents, relaySignal));
    }

    /// @notice Send a message to the the target address on the target chain.
    /// @dev Alias for `sendExt` when sending to EVM chains specifically.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @param relaySignal Meant to signal to off-chain relayers what type of ExtendedMessage this is.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As ExtendedMessageLib.encode may revert.
    /// @custom:emits SentExt(index, sequence, targetChain, targetAddress, encoded)
    function sendExt(uint16 targetChain, address targetAddress, bytes calldata contents, bytes calldata relaySignal)
        external
        returns (uint64 index, uint64 sequence)
    {
        return sendExt(pack(targetChain, abi.encodePacked(targetAddress), contents, relaySignal));
    }

    /// @notice Send a message to the the target on the target chain, along with
    ///         a header indicating that the message should be delivered
    ///         alongside some asset transfer. This feature is intended to be
    ///         used alongside CCTP or other asset transfer protocols. This
    ///         message type is used to ensure that a message arrives WITH the
    ///         asset transfer, and is not delivered before or after. The
    ///         application must ensure that the message is delivered alongside
    ///         a transfer of the specified asset and amount via the specified
    ///         asset bridge.
    /// @dev The Messenger does NOT handle sending the assets or verifying
    ///      receipt. The application MUST coordinate both of these actions.
    /// @dev Emits the message via Wormhole core, and stores the message
    ///      hash in the ring buffer.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @param bridge The bridge used for asset transfer.
    /// @param assetIdentifier The asset identifier for the message.
    /// @param amount The amount of value to expect with the message.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As TokenMessageLib.encode may revert.
    /// @custom:emits SentWithTokens(index, sequence, targetChain, targetAddress, encoded)
    function sendWithTokens(
        uint16 targetChain,
        bytes calldata targetAddress,
        bytes calldata contents,
        uint8 bridge,
        bytes calldata assetIdentifier,
        uint256 amount
    ) external returns (uint64 index, uint64 sequence) {
        return sendWithTokens(pack(targetChain, targetAddress, contents, bridge, assetIdentifier, amount));
    }

    /// @notice Send a message to the the target on the target chain.
    /// @dev Alias for `sendWithTokens` when sending to EVM chains specifically.
    /// @dev Emits the message via Wormhole core, and stores the message
    ///      hash in the ring buffer.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @param bridge The bridge used for asset transfer.
    /// @param assetIdentifier The asset identifier for the message.
    /// @param amount The amount of value to expect with the message.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As TokenMessageLib.encode may revert.
    /// @custom:emits SentWithTokens(index, sequence, targetChain, targetAddress, encoded)
    function sendWithTokens(
        uint16 targetChain,
        address targetAddress,
        bytes calldata contents,
        uint8 bridge,
        bytes calldata assetIdentifier,
        uint256 amount
    ) external returns (uint64 index, uint64 sequence) {
        return sendWithTokens(
            pack(targetChain, abi.encodePacked(targetAddress), contents, bridge, assetIdentifier, amount)
        );
    }

    /// @notice Send a message by publishing it via the Wormhole core and also
    ///         storing the hash.
    /// @dev This internal send function MUST operate on a packed message.
    /// @param encoded The packed message.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    function sendBytes(bytes memory encoded) internal returns (uint64 index, uint64 sequence) {
        index = pushMessage(keccak256(encoded));
        sequence = core().publishMessage(0, encoded, CONSISTENCY);
    }

    /// @notice Send a message by publishing it via the Wormhole core and also
    ///         storing the hash.
    /// @dev This internal send function MUST operate on a packed message.
    /// @param message The message.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As MessageLib.encode may revert.
    /// @custom:emits Sent(index, sequence, targetChain, targetAddress, encoded)
    function send(MessageLib.Message memory message) internal returns (uint64 index, uint64 sequence) {
        bytes memory encoded = message.encode();
        (index, sequence) = sendBytes(encoded);
        emit Sent(index, sequence, message.targetChain, message.targetAddress, encoded);
    }

    /// @notice Send a message by publishing it via the Wormhole core and also
    ///         storing the hash.
    /// @dev This internal send function MUST operate on a packed message.
    /// @param message The message.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As ExtendedMessageLib.encode may revert.
    /// @custom:emits SentExt(index, sequence, targetChain, targetAddress, encoded)
    function sendExt(ExtendedMessageLib.ExtendedMessage memory message)
        internal
        returns (uint64 index, uint64 sequence)
    {
        bytes memory encoded = message.encode();
        (index, sequence) = sendBytes(encoded);
        emit SentExt(index, sequence, message.targetChain, message.targetAddress, encoded);
    }

    /// @notice Send a message by publishing it via the Wormhole core and also
    ///         storing the hash.
    /// @dev This internal send function MUST operate on a packed message.
    /// @param message The message.
    /// @return index The index of the message in the history of all messages.
    /// @return sequence The sequence number of the message returned by
    ///         Wormhole core.
    /// @custom:reverts As TokenMessageLib.encode may revert.
    /// @custom:emits SentWithTokens(index, sequence, targetChain, targetAddress, encoded)
    function sendWithTokens(TokenMessageLib.TokenMessage memory message)
        internal
        returns (uint64 index, uint64 sequence)
    {
        bytes memory encoded = message.encode();
        (index, sequence) = sendBytes(encoded);
        emit SentWithTokens(index, sequence, message.targetChain, message.targetAddress, encoded);
    }

    /// @notice Pack a message, prepping it for sending.
    /// @dev Packing a message is the process of associating the target info
    ///      and the message index with the contents. The packed message will be
    ///      encoded before being sent via the Wormhole core contract, and
    ///      hashed  before being stored in the ring buffer.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @return message The packed message.
    function pack(uint16 targetChain, bytes memory targetAddress, bytes calldata contents)
        internal
        view
        returns (MessageLib.Message memory message)
    {
        message = MessageLib.Message({
            index: uint64(nextIndex()),
            targetChain: targetChain,
            targetAddress: targetAddress,
            sender: abi.encodePacked(msg.sender),
            contents: contents
        });
    }

    /// @notice Pack an ExtendedMessage, prepping it for sending.
    /// @dev Packing a message is the process of associating the target info
    ///      and the message index with the contents. The packed message will be
    ///      encoded before being sent via the Wormhole core contract, and
    ///      hashed  before being stored in the ring buffer.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @param relaySignal Extra message data used to signal to relayers.
    /// @return message The packed message.
    function pack(uint16 targetChain, bytes memory targetAddress, bytes calldata contents, bytes calldata relaySignal)
        internal
        view
        returns (ExtendedMessageLib.ExtendedMessage memory message)
    {
        message = ExtendedMessageLib.ExtendedMessage({
            index: uint64(nextIndex()),
            targetChain: targetChain,
            targetAddress: targetAddress,
            sender: abi.encodePacked(msg.sender),
            contents: contents,
            relaySignal: relaySignal
        });
    }

    /// @notice Pack a message with value, prepping it for sending.
    /// @dev Packing a message is the process of associating the target info
    ///      and the message index with the contents. The packed message will be
    ///      encoded before being sent via the Wormhole core contract, and
    ///      hashed  before being stored in the ring buffer.
    /// @param targetChain The target chain.
    /// @param targetAddress The target address on the target chain.
    /// @param contents The message contents.
    /// @param bridge The application-specific bridge identifier.
    /// @param assetIdentifier The asset identifier.
    /// @param amount The amount of the asset expected to be transferred.
    /// @return message The packed message.
    function pack(
        uint16 targetChain,
        bytes memory targetAddress,
        bytes calldata contents,
        uint8 bridge,
        bytes memory assetIdentifier,
        uint256 amount
    ) internal view returns (TokenMessageLib.TokenMessage memory message) {
        message = TokenMessageLib.TokenMessage({
            index: uint64(nextIndex()),
            targetChain: targetChain,
            targetAddress: targetAddress,
            sender: abi.encodePacked(msg.sender),
            contents: contents,
            bridge: bridge,
            assetIdentifier: assetIdentifier,
            amount: amount
        });
    }
}
[{"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"}]