// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {IVerifier} from "../interfaces/IVerifier.sol"; import {IWormhole} from "../interfaces/IWormhole.sol"; import {IHandler} from "../interfaces/IHandler.sol"; import {MessageLib} from "../shared/message/MessageLib.sol"; import {TokenMessageLib} from "../shared/message/TokenMessageLib.sol"; import {ExtendedMessageLib} from "../shared/message/ExtendedMessageLib.sol"; import {calculateReplayHash} from "../shared/message/BaseMessage.sol"; import {UsesCore} from "../shared/UsesCore.sol"; import {NonReentrant} from "../shared/NonReentrant.sol"; /// @title Inbox /// @author The Warp Team /// @notice This contract implements a generic cross-chain Inbox. /// /// @dev The Inbox contract handles receiving messages from an off-chain /// transport agent then delivering them to the correct handler /// application. /// /// @dev The Inbox is a verification-agnostic message deliverer. It allows /// messages to be verified by any abstract Verifier contract, or the /// Wormhole core contract. /// /// @dev The Inbox checks that the message is addressed to this chain, that the /// message has not been delivered already, that it was committed to by a /// known Outbox, and that the message is valid according to the Verifier. /// If all checks pass, the message is delivered to the Handler. /// /// @dev When delivering the message to the Handler, the Inbox passes relevant /// information including: /// - The message itself. /// - The address of the Verifier that validated the message. /// - The address of the sender of the message. /// - The chain id of the sender of the message. abstract contract Inbox is UsesCore, NonReentrant { using MessageLib for MessageLib.Message; using TokenMessageLib for TokenMessageLib.TokenMessage; using ExtendedMessageLib for ExtendedMessageLib.ExtendedMessage; /// @notice Emitted when a message is received and delivered to a handler. /// @dev Topic0 /// 0xb1f9608fc9a04b1041cff0ff42f903f47c60650e2cf5a50591327ce831ea07a5 /// @param index The index of the message in the Outbox on the emitting /// chain. /// @param emitterChainId The Wormhole chain id from which the message was /// emitted. /// @param emitterAddress The Wormhole-formatted address of the contract /// that emitted the message, presumably an Outbox. /// @param handler The address of the handler that processed the message. /// @param verifier The address of the verifier that validated the message. event Received( uint64 indexed index, uint16 indexed emitterChainId, bytes32 emitterAddress, address indexed handler, address verifier ); /// @notice Emitted when a message is received and delivered to /// a handler with extra un-verified data. /// @dev Identical to Received, but with a different topic0. /// @dev Topic0 /// 0xa11231c10ad1681023b505b6836eace2b100967a3047848374598fe02df8a4ae /// @param index The index of the message in the Outbox on the emitting /// chain. /// @param emitterChainId The Wormhole chain id from which the message was /// emitted. /// that emitted this message, presumably an Outbox. /// @param emitterAddress The Wormhole-formatted address of the contract. /// @param handler The address of the handler that processed the message. /// @param verifier The address of the verifier that validated the message. event ReceivedExt( uint64 indexed index, uint16 indexed emitterChainId, bytes32 emitterAddress, address indexed handler, address verifier ); /// @notice Emitted when a message with tokensis received and delivered to /// a handler. /// @dev Identical to Received, but with a different topic0. /// @dev Topic0 /// 0x6a7d9c2416abb712bc96f9eff261297cfccc5781844a77ad2791826e4ba5161d /// @param index The index of the message in the Outbox on the emitting /// chain. /// @param emitterChainId The Wormhole chain id from which the message was /// emitted. /// @param emitterAddress The Wormhole-formatted address of the contract /// that emitted the message, presumably an Outbox. /// @param handler The address of the handler that processed the message. /// @param verifier The address of the verifier that validated the message. event ReceivedWithTokens( uint64 indexed index, uint16 indexed emitterChainId, bytes32 emitterAddress, address indexed handler, address verifier ); /// @notice Core rejected signed VAA. /// @dev Selector 0x8ee2e336. /// @param reason The reason the VAA was rejected, provided by the Wormhole /// core contract. error InvalidVaa(string reason); /// @notice Message is not addressed to this chain. /// @dev Selector 0xf31052b3. /// @param _chainId The chain id to which the message ought to be delivered. error WrongTargetChain(uint16 _chainId); /// @notice Message has been delivered already, and cannot be delivered /// again. /// @dev Selector 0xbf51ac51. error DisallowedDelivery(); /// @notice Handler does not allow this messenger. /// @dev Selector 0xbe18d4f5. error DisallowedMessenger(uint16 chainId, bytes32 outbox); /// @notice Handler does not allow this verifier. /// @dev Selector 0x266fa8b5. error DisallowedVerifier(address verifier); /// @notice Chain has forked and message receipt is currently /// disabled. This condition may be temporary, should /// the guardian set restart service on this chain by /// updating the stored EVM chain ID in Wormhole core. /// @dev Selector 0xf22c96c1. error Forked(); /// @notice Receive a message, and validate it via a specified verifier. /// If the witness is valid, deliver the message to the handler. /// @param verifier The address of the Verifier contract which will be used /// to authenticate the message. /// @param witnessedStatement The witnessed statement to verify and deliver. /// The format of this statement is Verifier-specific. /// @custom:reverts as verify /// @custom:reverts as deliver /// @custom:emits as deliver function recv(address verifier, bytes memory witnessedStatement) external nonReentrant { IVerifier.VerifiedStatement memory statement = verify(verifier, witnessedStatement); deliver(verifier, statement.emitterChainId, statement.emitterAddress, statement.message); } /// @notice Receive a message to be delivered with un-verified extra data. /// Validate the message via the specified verifier. /// While the message and witness are verified, the extra data is /// not. If the witness is valid, deliver the message and the /// un-verified extra data to the handler. /// Applications MUST perform additional verification as necessary /// before trusting the extra data. /// @dev Security notice: `recvExt` is not reentrance protected. /// This choice was made to enable the delivery of /// additional messages contained in `unverifiedRelayerData.` /// App devs are responsible for ensuring that their `handleExt` /// function is secure even if re-entered by this `Messenger` contract. /// @param verifier The address of the Verifier contract which will be used /// to authenticate the message. /// @param witnessedStatement The witnessed statement to verify and deliver. /// The format of this statement is Verifier-specific. /// @param unverifiedRelayerData The un-verified extra data to deliver. /// This data is submitted by the message relayer, /// and may contain any un-trusted data. /// Applications may leverage this field to extend any extra /// functionalitythat requires data not available at the time of /// sending a message, such as (for example) additional signed /// Wormhole VAAs. /// @custom:reverts as verify /// @custom:reverts as deliverExt /// @custom:emits as deliverExt function recvExt(address verifier, bytes memory witnessedStatement, bytes memory unverifiedRelayerData) external { IVerifier.VerifiedStatement memory statement = verify(verifier, witnessedStatement); deliverExt( verifier, statement.emitterChainId, statement.emitterAddress, statement.message, unverifiedRelayerData ); } /// @notice Receive a message to be delivered with tokens. /// Validate the message via the specified verifier. /// If the witness is valid, deliver the message and /// the token transfer data to the handler. /// Applications MUST submit the transfer to the specified bridge /// and verify that it results in a transfer of the expected /// type and amount of tokens. /// @dev Security notice: `recvWithTokens` is not reentrance protected. /// This choice was made to enable the delivery of /// additional messages contained in `transfer` data /// (e.g. a message delivering a Burn-and-Mint token built on this /// contract). /// App devs are responsible for ensuring that their `handleWithTokens` /// function is secure even if re-entered by this `Messenger` contract. /// @param verifier The address of the Verifier contract which will be used /// to authenticate the message. /// @param witnessedStatement The witnessed statement to verify and deliver. /// The format of this statement is Verifier-specific. /// @param transfer The transfer to deliver. This is an opaque array of /// bytes that contains bridge-specific asset transfer data. The /// transfer is not verified. /// @custom:reverts as verify /// @custom:reverts as deliverWithTokens /// @custom:emits as deliverWithTokens function recvWithTokens(address verifier, bytes memory witnessedStatement, bytes memory transfer) external { IVerifier.VerifiedStatement memory statement = verify(verifier, witnessedStatement); deliverWithTokens(verifier, statement.emitterChainId, statement.emitterAddress, statement.message, transfer); } /// @notice Verify a witnessed statement using the specified verifier. /// @dev Verification is outsourced to a Verifier contract, which is /// responsible for validating the witness and parsing the statement. /// This contract is specified by the Relayer when delivering the /// message via the recv function. The address of the Verifier is later /// passed to the handler via the IHandler.acceptableVerifier function. /// @return statement - the parsed statement (iff the witness is valid) function verify(address verifier, bytes memory witnessedStatement) public view returns (IVerifier.VerifiedStatement memory statement) { if (verifier == address(core())) { statement = verifyVaa(witnessedStatement); } else { statement = IVerifier(verifier).verify(witnessedStatement); } } /// @dev This function is called by recv after verify. It implements the /// logic for delivering a message to a handler. /// @param emitterChainId The chain id of the remote Outbox. /// @param emitterAddress The address of the remote Outbox (using Wormhole /// VAA addressing) which emitted the message. /// @param encodedMessage The encoded message to deliver. /// @custom:reverts as MessageLib.decode /// @custom:reverts as extractEvmTarget /// @custom:reverts as checkPreconditions /// @custom:reverts as checkPreflights /// @custom:emits Received(emitterChainId, index, handler, verifier) function deliver(address verifier, uint16 emitterChainId, bytes32 emitterAddress, bytes memory encodedMessage) internal { // Decode MessageLib.Message memory message = MessageLib.decode(encodedMessage); uint64 index = message.index; IHandler handler = message.extractEvmTarget(); // pre-checks checkPreconditions(message.targetChain); bytes32 replayHash = calculateReplayHash(emitterChainId, emitterAddress, index); checkPreflights(handler, verifier, emitterChainId, emitterAddress, replayHash); // emit emit Received(index, emitterChainId, emitterAddress, address(handler), verifier); // deliver handler.handle(emitterChainId, message.sender, replayHash, message.contents); } /// @dev This function is called by recvExt after verify. It /// implements the logic for delivering a message to a handler /// along with some extra un-trusted data. /// @dev Security notice: the handler MUST auth the verifier address and /// MUST auth the (emitter chain, sender). /// @dev Security notice: the handler MUST auth the caller of handle. /// @param verifier The address of the Verifier contract which authenticated /// the message. /// @param emitterChainId The chain id of the remote Outbox. /// @param emitterAddress The address of the remote Outbox (using Wormhole /// VAA addressing) which emitted the message. /// @param encodedMessage The encoded message to deliver. /// @param unverifiedRelayerData The un-verified extra data to deliver. /// This data is submitted by the message relayer, /// and may contain any un-trusted data. /// Applications may leverage this field to extend any extra /// functionality that requires data not available at the time of /// sending a message, such as (for example) additional signed /// Wormhole VAAs. /// @custom:reverts as ExtendedMessageLib.decode /// @custom:reverts as extractEvmTarget /// @custom:reverts as checkPreconditions /// @custom:reverts as checkPreflights /// @custom:emits ReceivedExt(emitterChainId, index, handler, emitterAddress, verifier) function deliverExt( address verifier, uint16 emitterChainId, bytes32 emitterAddress, bytes memory encodedMessage, bytes memory unverifiedRelayerData ) internal { // Decode ExtendedMessageLib.ExtendedMessage memory message = ExtendedMessageLib.decode(encodedMessage); uint64 index = message.index; IHandler handler = message.extractEvmTarget(); // pre-checks checkPreconditions(message.targetChain); bytes32 replayHash = calculateReplayHash(emitterChainId, emitterAddress, index); checkPreflights(handler, verifier, emitterChainId, emitterAddress, replayHash); // emit emit ReceivedExt(index, emitterChainId, emitterAddress, address(handler), verifier); // deliver handler.handleExt( emitterChainId, message.sender, replayHash, message.contents, message.relaySignal, unverifiedRelayerData ); } /// @dev This function is called by recvWithTokens after verify. It /// implements the logic for delivering a message with value to a /// handler. /// @param verifier The address of the Verifier contract which authenticated /// the message. /// @param emitterChainId The chain id of the remote Outbox. /// @param emitterAddress The address of the remote Outbox (using Wormhole /// VAA addressing) which emitted the message. /// @param encodedMessage The encoded message to deliver. /// @param transfer The transfer to deliver. This is an opaque array of /// bytes that contains bridge-specific asset transfer data. The /// transfer is not verified. /// @custom:reverts as TokenMessageLib.decode /// @custom:reverts as extractEvmTarget /// @custom:reverts as checkPreconditions /// @custom:reverts as checkPreflights /// @custom:emits ReceivedWithTokens(emitterChainId, index, handler, emitterAddress, verifier) function deliverWithTokens( address verifier, uint16 emitterChainId, bytes32 emitterAddress, bytes memory encodedMessage, bytes memory transfer ) internal { // Decode TokenMessageLib.TokenMessage memory message = TokenMessageLib.decode(encodedMessage); uint64 index = message.index; IHandler handler = message.extractEvmTarget(); // pre-checks checkPreconditions(message.targetChain); bytes32 replayHash = calculateReplayHash(emitterChainId, emitterAddress, index); checkPreflights(handler, verifier, emitterChainId, emitterAddress, replayHash); // emit emit ReceivedWithTokens(index, emitterChainId, emitterAddress, address(handler), verifier); // deliver handler.handleWithTokens( emitterChainId, message.sender, replayHash, message.contents, message.bridge, message.assetIdentifier, message.amount, transfer ); } /// @notice Revert if the message is not addressed to this chain. /// @dev Ensures that messages are only delivered to the chain to which they /// are addressed. /// @param _chainId The chain id to which the mesage is address, as provided /// in the message itself, and authenticated by some Verifier. /// @custom:reverts WrongTargetChain(_chainId) if the message is not /// addressed to this chain. function checkTargetChain(uint16 _chainId) internal view { if (core().chainId() != _chainId) revert WrongTargetChain(_chainId); } /// @notice Revert if the core has detected a chain fork. /// @dev Core uses a stored EVM chain ID and compares it to the /// current EVM chain ID. If they differ, the chain has forked. /// To prevent uncertainty during forks, the Inbox stops receiving /// messages until the guardian restarts the service by updating /// the stored EVM chain ID in Wormhole core. /// @custom:reverts If the chain has forked. function checkNotForked() internal view { if (core().isFork()) revert Forked(); } /// @notice Check preconditions for a message. The preconditions are /// - This chain is not known to be forked. /// - The target chain is this chain. /// @dev Simple wrapper for above check____ functions. /// @param targetChain The chain id to which the mesage is addressed, as /// provided in the message itself, and authenticated by some /// Verifier. /// @custom:reverts as checkNotForked /// @custom:reverts as checkTargetChain function checkPreconditions(uint16 targetChain) internal view { checkNotForked(); checkTargetChain(targetChain); } /// @notice Check preflights for a message. The Preflight checks are /// - The handler accepts the outbox /// - The handler accepts the verifier /// @dev Preflights are distinct from preconditions because they are /// not required for the message to be valid, but rather are /// required for the message to be delivered to the handler. /// @dev Preflights provide a mechanism for an application to configure /// its trust model in any way it pleases. /// @param handler The handler contract. /// @param verifier The verifier contract address that was used to verify /// the message. /// @param emitterChain The Wormhole chain ID of the emitter. /// @param emitterAddress The address of the emitter on that chain. /// @param replayHash A protocol-specific unique identifier for the message /// @custom:reverts DisallowedMessenger(emitterChain, emitterAddress) If the /// handler does not accept the outbox. /// @custom:reverts DisallowedVerifier(verifier) If the handler does not /// accept the verifier. /// @custom:reverts DisallowedDelivery() If the handler does not accept the delivery due to message having been processed already. function checkPreflights( IHandler handler, address verifier, uint16 emitterChain, bytes32 emitterAddress, bytes32 replayHash ) internal view { if (!handler.acceptableMessenger(emitterChain, emitterAddress)) { revert DisallowedMessenger(emitterChain, emitterAddress); } if (!handler.acceptableVerifier(verifier)) revert DisallowedVerifier(verifier); if (!handler.acceptableDelivery(replayHash)) revert DisallowedDelivery(); } /// @notice Verify a Wormhole core witnessed statement using /// parseAndVerifyVM /// @dev This is a special-case Verifier for Wormhole core contracts. It /// essentially is a local shim to make the core conform to the /// Verifier interface. It is used by the recvVaa function to verify /// input that specifies the core as the Verifier. /// @dev MUST revert if the VM is not valid. /// @param vaa The puported VAA to verify and parse. /// @return statement - the statement parsed from the VM (iff the Wormhole /// witness is valid). /// @custom:reverts InvalidVaa(reason) if the VM is not valid. function verifyVaa(bytes memory vaa) internal view returns (IVerifier.VerifiedStatement memory statement) { // call Wormhole core parseAndVerifyVM IWormhole.VM memory vm; bool result; string memory reason; (vm, result, reason) = core().parseAndVerifyVM(vaa); // revert if VM is not valid if (!result) revert InvalidVaa(reason); // transform VM to VerifiedStatement and return statement.emitterChainId = vm.emitterChainId; statement.emitterAddress = vm.emitterAddress; statement.message = vm.payload; } }