// SPDX-License-Identifier: MIT OR Apache-2.0 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 /// @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 }); } }