// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import {MemView, MemViewLib} from "./MemView.sol"; import {RECEIPT_LENGTH, RECEIPT_VALID_SALT, RECEIPT_INVALID_SALT} from "../Constants.sol"; import {UnformattedReceipt} from "../Errors.sol"; /// Receipt is a memory view over a formatted "full receipt" payload. type Receipt is uint256; using ReceiptLib for Receipt global; /// Receipt structure represents a Notary statement that a certain message has been executed in `ExecutionHub`. /// - It is possible to prove the correctness of the tips payload using the message hash, therefore tips are not /// included in the receipt. /// - Receipt is signed by a Notary and submitted to `Summit` in order to initiate the tips distribution for an /// executed message. /// - If a message execution fails the first time, the `finalExecutor` field will be set to zero address. In this /// case, when the message is finally executed successfully, the `finalExecutor` field will be updated. Both /// receipts will be considered valid. /// # Memory layout of Receipt fields /// /// | Position | Field | Type | Bytes | Description | /// | ---------- | ------------- | ------- | ----- | ------------------------------------------------ | /// | [000..004) | origin | uint32 | 4 | Domain where message originated | /// | [004..008) | destination | uint32 | 4 | Domain where message was executed | /// | [008..040) | messageHash | bytes32 | 32 | Hash of the message | /// | [040..072) | snapshotRoot | bytes32 | 32 | Snapshot root used for proving the message | /// | [072..073) | stateIndex | uint8 | 1 | Index of state used for the snapshot proof | /// | [073..093) | attNotary | address | 20 | Notary who posted attestation with snapshot root | /// | [093..113) | firstExecutor | address | 20 | Executor who performed first valid execution | /// | [113..133) | finalExecutor | address | 20 | Executor who successfully executed the message | library ReceiptLib { using MemViewLib for bytes; /// @dev The variables below are not supposed to be used outside of the library directly. uint256 private constant OFFSET_ORIGIN = 0; uint256 private constant OFFSET_DESTINATION = 4; uint256 private constant OFFSET_MESSAGE_HASH = 8; uint256 private constant OFFSET_SNAPSHOT_ROOT = 40; uint256 private constant OFFSET_STATE_INDEX = 72; uint256 private constant OFFSET_ATT_NOTARY = 73; uint256 private constant OFFSET_FIRST_EXECUTOR = 93; uint256 private constant OFFSET_FINAL_EXECUTOR = 113; // ═════════════════════════════════════════════════ RECEIPT ═════════════════════════════════════════════════════ /** * @notice Returns a formatted Receipt payload with provided fields. * @param origin_ Domain where message originated * @param destination_ Domain where message was executed * @param messageHash_ Hash of the message * @param snapshotRoot_ Snapshot root used for proving the message * @param stateIndex_ Index of state used for the snapshot proof * @param attNotary_ Notary who posted attestation with snapshot root * @param firstExecutor_ Executor who performed first valid execution attempt * @param finalExecutor_ Executor who successfully executed the message * @return Formatted receipt */ function formatReceipt( uint32 origin_, uint32 destination_, bytes32 messageHash_, bytes32 snapshotRoot_, uint8 stateIndex_, address attNotary_, address firstExecutor_, address finalExecutor_ ) internal pure returns (bytes memory) { return abi.encodePacked( origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_ ); } /** * @notice Returns a Receipt view over the given payload. * @dev Will revert if the payload is not a receipt. */ function castToReceipt(bytes memory payload) internal pure returns (Receipt) { return castToReceipt(payload.ref()); } /** * @notice Casts a memory view to a Receipt view. * @dev Will revert if the memory view is not over a receipt. */ function castToReceipt(MemView memView) internal pure returns (Receipt) { if (!isReceipt(memView)) revert UnformattedReceipt(); return Receipt.wrap(MemView.unwrap(memView)); } /// @notice Checks that a payload is a formatted Receipt. function isReceipt(MemView memView) internal pure returns (bool) { // Check payload length return memView.len() == RECEIPT_LENGTH; } /// @notice Returns the hash of an Receipt, that could be later signed by a Notary to signal /// that the receipt is valid. function hashValid(Receipt receipt) internal pure returns (bytes32) { // The final hash to sign is keccak(receiptSalt, keccak(receipt)) return receipt.unwrap().keccakSalted(RECEIPT_VALID_SALT); } /// @notice Returns the hash of a Receipt, that could be later signed by a Guard to signal /// that the receipt is invalid. function hashInvalid(Receipt receipt) internal pure returns (bytes32) { // The final hash to sign is keccak(receiptBodyInvalidSalt, keccak(receipt)) return receipt.unwrap().keccakSalted(RECEIPT_INVALID_SALT); } /// @notice Convenience shortcut for unwrapping a view. function unwrap(Receipt receipt) internal pure returns (MemView) { return MemView.wrap(Receipt.unwrap(receipt)); } /// @notice Compares two Receipt structures. function equals(Receipt a, Receipt b) internal pure returns (bool) { // Length of a Receipt payload is fixed, so we just need to compare the hashes return a.unwrap().keccak() == b.unwrap().keccak(); } // ═════════════════════════════════════════════ RECEIPT SLICING ═════════════════════════════════════════════════ /// @notice Returns receipt's origin field function origin(Receipt receipt) internal pure returns (uint32) { // Can be safely casted to uint32, since we index 4 bytes return uint32(receipt.unwrap().indexUint({index_: OFFSET_ORIGIN, bytes_: 4})); } /// @notice Returns receipt's destination field function destination(Receipt receipt) internal pure returns (uint32) { // Can be safely casted to uint32, since we index 4 bytes return uint32(receipt.unwrap().indexUint({index_: OFFSET_DESTINATION, bytes_: 4})); } /// @notice Returns receipt's "message hash" field function messageHash(Receipt receipt) internal pure returns (bytes32) { return receipt.unwrap().index({index_: OFFSET_MESSAGE_HASH, bytes_: 32}); } /// @notice Returns receipt's "snapshot root" field function snapshotRoot(Receipt receipt) internal pure returns (bytes32) { return receipt.unwrap().index({index_: OFFSET_SNAPSHOT_ROOT, bytes_: 32}); } /// @notice Returns receipt's "state index" field function stateIndex(Receipt receipt) internal pure returns (uint8) { // Can be safely casted to uint8, since we index a single byte return uint8(receipt.unwrap().indexUint({index_: OFFSET_STATE_INDEX, bytes_: 1})); } /// @notice Returns receipt's "attestation notary" field function attNotary(Receipt receipt) internal pure returns (address) { return receipt.unwrap().indexAddress({index_: OFFSET_ATT_NOTARY}); } /// @notice Returns receipt's "first executor" field function firstExecutor(Receipt receipt) internal pure returns (address) { return receipt.unwrap().indexAddress({index_: OFFSET_FIRST_EXECUTOR}); } /// @notice Returns receipt's "final executor" field function finalExecutor(Receipt receipt) internal pure returns (address) { return receipt.unwrap().indexAddress({index_: OFFSET_FINAL_EXECUTOR}); } }