// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {BytesLib} from "./BytesLib.sol"; import {IHandler} from "../../interfaces/IHandler.sol"; /// @dev A 1-byte constant '0xbb' indicative of a message. This NEVER /// changes in future versions. uint8 constant MAGIC_BYTE = 0xbb; /// @dev A 1-byte version identifier. Deployments MUST increment this /// version number. uint8 constant MESSAGE_VERSION = 0x0; /// @dev Details for the Wormhole leading byte. uint256 constant MAGIC_BYTE_OFFSET = 0; uint256 constant MAGIC_BYTE_SIZE = 1; /// @dev Details for the version offset. uint256 constant VERSION_OFFSET = MAGIC_BYTE_OFFSET + MAGIC_BYTE_SIZE; uint256 constant VERSION_SIZE = 1; /// @dev Details for the message type. uint256 constant MESSAGE_TYPE_OFFSET = VERSION_OFFSET + VERSION_SIZE; uint256 constant MESSAGE_TYPE_SIZE = 1; /// @notice Got a mismatch for the leading byte during message decoding. /// Expected the value of `MAGIC_BYTE` but got `unexpected`. /// @dev Selector 0x981b4e39. error MagicByteMismatch(uint8 unexpected); /// @notice Got a version mismatch during message decoding. Expected the /// value of `MESSAGE_VERSION` but got `unexpected`. /// @dev Selector 0xc6147f41. error VersionMismatch(uint8 unexpected); /// @notice Got a message type mismatch during message decoding. Expected the /// value of `expected` but got `unexpected`. /// @dev Selector 0x6cf016d3. error TypeMismatch(uint8 expected, uint8 unexpected); /// @notice An error indicating that the targetAddress is too large /// to encode OR the address is empty. /// @dev Selector 0x2a2a06b2. error AddressLength(); /// @notice An error indicating that the sender address is too large to /// encode. /// @dev Selector 0x496d8313. error SenderLength(); /// @notice An error indicating that the message contents are too large to /// encode. /// @dev Selector 0xbe3cbd10. error ContentsLength(); /// @notice Failed message decode because there were not enough bytes in /// the input. /// @dev Selector 0xb8d8f0f4. error Overrun(); /// @notice Failed message decode because there were leftover bytes in the /// input after decodig. /// @dev Selector 0x94701070. error LeftoverBytes(); /// @notice Target address is not a valid EVM address. The app that /// emitted the message may have been misconfigured or /// misimplemented. /// @dev Selector 0x3e95d7cb. error InvalidTargetAddress(bytes targetAddress); /// @notice Encode the message header. /// @dev The header follows a simple specification: /// HEADER /// - magic - 1 byte /// - version - 1 byte /// - messageType - 1 byte /// @param messageType The message type. 0 for Message, 1 for MessageWithTokens. /// @return header The encoded header. function encodeHeader(uint8 messageType) pure returns (bytes memory header) { header = abi.encodePacked(MAGIC_BYTE, MESSAGE_VERSION, messageType); } /// @notice Check that an encoded message has the expected header. /// @dev Checks the leading byte, version, and message type. /// @custom:reverts MagicByteMismatch() If the leading byte is not /// "0xbb" /// @custom:reverts VersionMistmatch() If the version is not expected. /// MessageLib supports any equal-or-lower version. /// @custom:reverts TypeMismatch() If the message type is not expected. /// Type supported varies by decoding function. function expectHeader(uint8 messageType, bytes memory encoded) pure { uint8 magic = BytesLib.toUint8(encoded, MAGIC_BYTE_OFFSET); if (magic != MAGIC_BYTE) revert MagicByteMismatch(magic); // 1 byte at position 1 uint8 version = BytesLib.toUint8(encoded, VERSION_OFFSET); if (version > MESSAGE_VERSION) revert VersionMismatch(version); // 1 byte at position 2 uint8 ty = BytesLib.toUint8(encoded, MESSAGE_TYPE_OFFSET); if (ty != messageType) revert TypeMismatch(messageType, ty); } /// @notice Convert a 20-byte array to an EVM address. /// @dev This function loads the 20 bytes of address data from the /// provided array, and converts it to an EVM address by shifting /// it right by 96 bits. /// @dev We chose not to check extcode here, as that will be handled /// by the compiler when the address is used to make a call. /// @param encoded The 20-byte array containing the address data. /// @return decoded An IHandler interface at the decoded EVM address. /// @custom:reverts InvalidTargetAddress(encoded) if the encoded array is /// not exactly 20-bytes long. function extractAddress(bytes memory encoded) pure returns (IHandler decoded) { if (encoded.length != 20) revert InvalidTargetAddress(encoded); uint256 word; assembly ("memory-safe") { // This assemply loads 1 word from the body of the encoded array. // This word is known to consist of 20 bytes of address data, // followed by 12 bytes of anything. `bytes` are 0-padded to the // next word by Solidity convention, but the contents must be // shifted out regardless. word := mload(add(encoded, 0x20)) } // We shift the word right by 96 bits, to remove the 12 low bytes word = word >> 96; decoded = IHandler(address(uint160(word))); } /// @notice Calculates a hash of a message used to check for replay. /// @param emitterChainId emitterChainId The chain id of the remote Outbox. /// @param emitterAddress The address of the remote Outbox. /// @param index The index of the message in the remote Outbox. /// @return A protocol-specific unique identifier of the message function calculateReplayHash(uint16 emitterChainId, bytes32 emitterAddress, uint64 index) pure returns (bytes32) { return keccak256(abi.encodePacked(emitterChainId, emitterAddress, index)); } /// @notice Read a length-prefixed bytearray from the encoded message. The /// bytearray is prefixed with a uint16 that specifies its length. /// @dev First reads the length prefix, and then reads that many bytes /// after the prefix. Performs bounds checking before each read. /// @param encoded The encoded message as a bytearray. /// @param offset The offset in the encoded messages to start reading from. /// @return contents The contents of the byte array (without the length /// prefix). /// @custom:reverts Overrun() If the encoded message is not long enough to /// read the length-prefixed bytearray. function readU16PrefixedBytes(bytes memory encoded, uint256 offset) pure returns (bytes memory contents) { // ensure that the input is long enough to read a length-prefix from if (encoded.length < offset + 2) revert Overrun(); uint16 len = BytesLib.toUint16(encoded, offset); uint256 contentsOffset = offset + 2; // ensure that the input is long enough to read from if (encoded.length < contentsOffset + len) revert Overrun(); contents = BytesLib.slice(encoded, contentsOffset, len); }