From 6a2229b01ab642d243442d37d30e8e586131a573 Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Wed, 10 May 2023 16:01:36 +0000 Subject: [PATCH 1/8] inbox and message box contracts --- .vscode/settings.json | 3 +- l1-contracts/src/core/messagebridge/Inbox.sol | 148 ++++++++++++++++++ .../src/core/messagebridge/MessageBox.sol | 96 ++++++++++++ 3 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 l1-contracts/src/core/messagebridge/Inbox.sol create mode 100644 l1-contracts/src/core/messagebridge/MessageBox.sol diff --git a/.vscode/settings.json b/.vscode/settings.json index 0860c400c38..e4568cdcb29 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -107,7 +107,8 @@ "*.macros": "cpp", "*.tpp": "cpp" }, - "solidity.compileUsingRemoteVersion": "v0.6.10+commit.00c0fcaf", + "solidity.compileUsingRemoteVersion": "v0.8.18", + "solidity.formatter": "forge", "search.exclude": { "**/.yarn": true, "**/.yalc": true, diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol new file mode 100644 index 00000000000..75eadeb403f --- /dev/null +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +import {MessageBox} from "./MessageBox.sol"; + +/** + * @title Inbox + * @author Aztec Labs + * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. + */ +contract Inbox is MessageBox { + error Inbox__NotPastDeadline(); + error Inbox__PastDeadline(); + error Inbox__Unauthorized(); + + /** + * @dev struct for sending messages from L1 to L2 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) + * @param deadline - The deadline to consume a message. Only after it, can a message be cancalled. + * @param fee - The fee provided to sequencer for including the entry + */ + struct L1ToL2Msg { + L1Actor sender; + L2Actor recipient; + bytes32 content; + bytes32 secretHash; + uint256 deadline; + uint256 fee; + } + + event MessageAdded( + bytes32 indexed entryKey, + address indexed sender, + bytes32 indexed recipient, + uint256 senderChainId, + uint256 recipientVersion, + uint256 deadline, + uint256 fee, + bytes32 content + ); + + mapping(address account => uint256 balance) public feesAccrued; + + /// @notice Given a message, computes an entry key for the Inbox + function computeMessageKey(L1ToL2Msg memory message) public pure returns (bytes32) { + return keccak256( + abi.encode( + message.sender, + message.recipient, + message.content, + message.secretHash, + message.deadline, + message.fee + ) + ); + } + + /** + * @notice Inserts an entry into the Inbox + * @dev Will emit `MessageAdded` with data for easy access by the sequencer + * @dev msg.value - The fee provided to sequencer for including the entry + * @param _recipient - The recipient of the entry + * @param _deadline - The deadline to consume a message. Only after it, can a message be cancalled. + * @param _content - The content of the entry (application specific) + * @param _secretHash - The secret hash of the entry (make it possible to hide when a specific entry is consumed on L2) + * @return The key of the entry in the set + */ + function sendL2Message( + L2Actor memory _recipient, + uint256 _deadline, + bytes32 _content, + bytes32 _secretHash + ) external payable returns (bytes32) { + L1ToL2Msg memory message = L1ToL2Msg({ + sender: L1Actor(msg.sender, block.chainid), + recipient: _recipient, + content: _content, + secretHash: _secretHash, + deadline: _deadline, + fee: msg.value + }); + + bytes32 key = computeMessageKey(message); + _insert(key); + + emit MessageAdded( + key, + message.sender.actor, + message.recipient.actor, + message.sender.chainId, + message.recipient.version, + message.deadline, + message.fee, + message.content + ); + + return key; + } + + /** + * @notice Cancel a pending L2 message + * @dev Will revert if the deadline have not been crossed - message only cancellable past the deadline + * so it cannot be yanked away while the sequencer is building a block including it + * @dev Must be called by portal that inserted the entry + * @param _message - The content of the entry (application specific) + * @param _feeCollector - The address to receive the "fee" + * @return The key of the entry removed + */ + function cancelL2Message(L1ToL2Msg memory _message, address _feeCollector) + external + returns (bytes32) + { + if (msg.sender != _message.sender.actor) revert Inbox__Unauthorized(); + if (_message.deadline <= block.timestamp) revert Inbox__NotPastDeadline(); + return _consumeInboxMessage(_message, _feeCollector); + } + + /** + * @notice Consumes an entry from the Inbox + * @dev Only callable by the rollup contract + * @dev Will revert if the message is already past deadline + * @param _message - The content of the entry (application specific) + * @param _feeCollector - The address to receive the "fee" + * @return The key of the entry removed + */ + function consume(L1ToL2Msg memory _message, address _feeCollector) + external + onlyRollup + returns (bytes32) + { + if (_message.deadline > block.timestamp) revert Inbox__PastDeadline(); + + return _consumeInboxMessage(_message, _feeCollector); + } + + function _consumeInboxMessage(L1ToL2Msg memory _message, address _feeCollector) + internal + returns (bytes32 entryKey) + { + entryKey = computeMessageKey(_message); + _consume(entryKey); + feesAccrued[_feeCollector] += _message.fee; + } +} diff --git a/l1-contracts/src/core/messagebridge/MessageBox.sol b/l1-contracts/src/core/messagebridge/MessageBox.sol new file mode 100644 index 00000000000..2b9787eeeed --- /dev/null +++ b/l1-contracts/src/core/messagebridge/MessageBox.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +/** + * @title MessageBox + * @author Aztec Labs + * @notice Data structure used in both Inbox and Outbox for keeping track of entries + * Implements a multi-set storing the multiplicity (count for easy reading) at the entry. + */ +abstract contract MessageBox { + error MessageBox__Unauthorized(); + error MessageBox__NothingToConsume(bytes32 entryKey); + error MessageBox__OversizedContent(); + + uint256 public constant MESSAGE_SIZE = 32; + + /// @dev Message sender on L1. ChainID if multiple L1s tap into the same Aztec instance. + struct L1Actor { + address actor; + uint256 chainId; + } + + /// @dev Message receiver on L2. + struct L2Actor { + bytes32 actor; + uint256 version; + } + + /** + * @dev Entry struct - Done as struct to easily support extensions if needed + * @param count - The occurrence of the entry in the dataset + */ + struct Entry { + uint256 count; + } + + address rollup; + + mapping(bytes32 entryKey => Entry entry) internal entries; + + event RollupUpdated(address indexed newRollup, address indexed oldRollup); + + modifier onlyRollup() { + if (msg.sender != rollup) revert MessageBox__Unauthorized(); + _; + } + + constructor() { + rollup = msg.sender; + } + + function updateRollup(address _newRollup) external onlyRollup { + emit RollupUpdated(_newRollup, rollup); + rollup = _newRollup; + } + + /** + * @notice Inserts an entry into the multi-set + * @param _entryKey - The key to insert + */ + function _insert(bytes32 _entryKey) internal { + entries[_entryKey].count++; + } + + /** + * @notice Consumed an entry if possible, reverts if nothing to consume + * For multiplicity > 1, will consume one element + * @param _entryKey - The key to consume + */ + function _consume(bytes32 _entryKey) internal { + Entry storage entry = entries[_entryKey]; + if (entry.count == 0) revert MessageBox__NothingToConsume(_entryKey); + entry.count--; + } + + /** + * @notice Fetch an entry + * @param _entryKey - The key to lookup + * @return The entry matching the provided key + */ + function get(bytes32 _entryKey) public view returns (Entry memory) { + Entry memory entry = entries[_entryKey]; + if (entry.count == 0) revert MessageBox__NothingToConsume(_entryKey); + return entry; + } + + /** + * @notice Check if entry exists + * @param _entryKey - The key to lookup + * @return True if entry exists, false otherwise + */ + function contains(bytes32 _entryKey) public view returns (bool) { + return entries[_entryKey].count > 0; + } +} From c9eca701f558493175efd093cd16d9337b35e449 Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Wed, 10 May 2023 16:02:00 +0000 Subject: [PATCH 2/8] outbox --- .../src/core/messagebridge/Outbox.sol | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 l1-contracts/src/core/messagebridge/Outbox.sol diff --git a/l1-contracts/src/core/messagebridge/Outbox.sol b/l1-contracts/src/core/messagebridge/Outbox.sol new file mode 100644 index 00000000000..89ed61e2f58 --- /dev/null +++ b/l1-contracts/src/core/messagebridge/Outbox.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +import {MessageBox} from "./MessageBox.sol"; + +/** + * @title Outbox + * @author Aztec Labs + * @notice Lives on L1 and is used to consume L2 -> L1 messages. Messages are inserted by the rollup contract + * and will be consumed by the portal contracts. + */ +contract Outbox is MessageBox { + error Outbox__Unauthorized(); + error Outbox__WrongChainId(); + + /** + * @dev struct for sending messages from L2 to L1 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + */ + struct L2ToL1Msg { + L2Actor sender; + L1Actor recipient; + bytes32 content; + } + + // to make it easier for portal to know when to consume the message. + event MessageAdded( + bytes32 indexed entryKey, + bytes32 indexed sender, + address indexed recipient, + uint256 senderVersion, + uint256 recipientChainId, + bytes32 content + ); + + event MessageConsumed(bytes32 indexed entryKey, address indexed recipient); + + /** + * @notice Computes an entry key for the Outbox + * @param _message - The L2 to L1 message + * @return The key of the entry in the set + */ + function computeEntryKey(L2ToL1Msg memory _message) public pure returns (bytes32) { + return keccak256(abi.encode(_message.sender, _message.recipient, _message.content)); + } + + /** + * @notice Inserts an entry into the Outbox + * @dev Only callable by the rollup contract + * @param _sender - The L2 sender of the message + * @param _recipient - The L1 recipient of the message + * @param _content - The content of the entry (application specific) + * @return The key of the entry in the set + */ + function sendL1Message(L2Actor memory _sender, L1Actor memory _recipient, bytes32 _content) + external + onlyRollup + returns (bytes32) + { + if (block.chainid != _recipient.chainId) revert Outbox__WrongChainId(); + // TODO: Since the rollup contract does it, there isn't anything currently to prevent + // the sequencer from adding random messages from random L2 address. Add signature verification + L2ToL1Msg memory message = L2ToL1Msg(_sender, _recipient, _content); + bytes32 entryKey = computeEntryKey(message); + _insert(entryKey); + emit MessageAdded( + entryKey, _sender.actor, _recipient.actor, _sender.version, _recipient.chainId, _content + ); + return entryKey; + } + + /** + * @notice Consumes an entry from the Outbox + * @dev Only meaningfully callable by portals, otherwise should never hit an entry + * @dev Emits the `MessageConsumed` event when consuming messages + * @param _message - The L2 to L1 message + * @return entryKey - The key of the entry removed + */ + function consume(L2ToL1Msg memory _message) external returns (bytes32 entryKey) { + if (msg.sender != _message.recipient.actor) revert Outbox__Unauthorized(); + if (block.chainid != _message.recipient.chainId) revert Outbox__WrongChainId(); + + entryKey = computeEntryKey(_message); + _consume(entryKey); + emit MessageConsumed(entryKey, _message.recipient.actor); + } +} From f4fa68dfe4f040ecc143f736cf41a9d7d0b7ac97 Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Fri, 12 May 2023 09:33:07 +0000 Subject: [PATCH 3/8] address PR comments --- l1-contracts/src/core/messagebridge/Inbox.sol | 86 +++++++++++-------- .../src/core/messagebridge/MessageBox.sol | 36 +++++--- .../src/core/messagebridge/Outbox.sol | 41 +++------ 3 files changed, 83 insertions(+), 80 deletions(-) diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index 75eadeb403f..d551c3e9346 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -28,34 +28,40 @@ contract Inbox is MessageBox { L2Actor recipient; bytes32 content; bytes32 secretHash; - uint256 deadline; - uint256 fee; + uint32 deadline; + uint64 fee; } + mapping(address account => uint256 balance) public feesAccrued; + event MessageAdded( bytes32 indexed entryKey, address indexed sender, bytes32 indexed recipient, uint256 senderChainId, uint256 recipientVersion, - uint256 deadline, - uint256 fee, + uint32 deadline, + uint64 fee, bytes32 content ); - mapping(address account => uint256 balance) public feesAccrued; + event L1ToL2MessageCancelled(bytes32 indexed entryKey); /// @notice Given a message, computes an entry key for the Inbox function computeMessageKey(L1ToL2Msg memory message) public pure returns (bytes32) { - return keccak256( - abi.encode( - message.sender, - message.recipient, - message.content, - message.secretHash, - message.deadline, - message.fee - ) + return bytes32( + uint256( + keccak256( + abi.encode( + message.sender, + message.recipient, + message.content, + message.secretHash, + message.deadline, + message.fee + ) + ) + ) % P // FIXME: Replace mod P later on when we have a better idea of how to handle Fields. ); } @@ -71,21 +77,22 @@ contract Inbox is MessageBox { */ function sendL2Message( L2Actor memory _recipient, - uint256 _deadline, + uint32 _deadline, bytes32 _content, bytes32 _secretHash ) external payable returns (bytes32) { + uint64 fee = uint64(msg.value); L1ToL2Msg memory message = L1ToL2Msg({ sender: L1Actor(msg.sender, block.chainid), recipient: _recipient, content: _content, secretHash: _secretHash, deadline: _deadline, - fee: msg.value + fee: fee }); bytes32 key = computeMessageKey(message); - _insert(key); + _insert(key, fee, _deadline); emit MessageAdded( key, @@ -108,41 +115,44 @@ contract Inbox is MessageBox { * @dev Must be called by portal that inserted the entry * @param _message - The content of the entry (application specific) * @param _feeCollector - The address to receive the "fee" - * @return The key of the entry removed + * @return entryKey - The key of the entry removed */ function cancelL2Message(L1ToL2Msg memory _message, address _feeCollector) external - returns (bytes32) + returns (bytes32 entryKey) { if (msg.sender != _message.sender.actor) revert Inbox__Unauthorized(); if (_message.deadline <= block.timestamp) revert Inbox__NotPastDeadline(); - return _consumeInboxMessage(_message, _feeCollector); + entryKey = computeMessageKey(_message); + _consume(entryKey); + feesAccrued[_feeCollector] += _message.fee; + emit L1ToL2MessageCancelled(entryKey); } /** - * @notice Consumes an entry from the Inbox + * @notice Batch consumes entries from the Inbox * @dev Only callable by the rollup contract * @dev Will revert if the message is already past deadline - * @param _message - The content of the entry (application specific) + * @param entryKeys - Array of entry keys (hash of the messages) * @param _feeCollector - The address to receive the "fee" - * @return The key of the entry removed */ - function consume(L1ToL2Msg memory _message, address _feeCollector) - external - onlyRollup - returns (bytes32) - { - if (_message.deadline > block.timestamp) revert Inbox__PastDeadline(); - - return _consumeInboxMessage(_message, _feeCollector); + function batchConsume(bytes32[] memory entryKeys, address _feeCollector) external onlyRollup { + uint256 totalFee = 0; + for (uint256 i = 0; i < entryKeys.length; i++) { + // TODO: Combine these to optimise for gas. + Entry memory entry = get(entryKeys[i]); + if (entry.deadline > block.timestamp) revert Inbox__PastDeadline(); + _consume(entryKeys[i]); + totalFee += entry.fee; + } + feesAccrued[_feeCollector] += totalFee; } - function _consumeInboxMessage(L1ToL2Msg memory _message, address _feeCollector) - internal - returns (bytes32 entryKey) - { - entryKey = computeMessageKey(_message); - _consume(entryKey); - feesAccrued[_feeCollector] += _message.fee; + /** + * @notice Withdraws fees accrued by the sequencer + * @param _amount - The amount to withdraw + */ + function withdrawFees(uint256 _amount) external { + // TODO: reentrancy attack, safe eth fees withdrawal. } } diff --git a/l1-contracts/src/core/messagebridge/MessageBox.sol b/l1-contracts/src/core/messagebridge/MessageBox.sol index 2b9787eeeed..ea59fbadf08 100644 --- a/l1-contracts/src/core/messagebridge/MessageBox.sol +++ b/l1-contracts/src/core/messagebridge/MessageBox.sol @@ -13,15 +13,13 @@ abstract contract MessageBox { error MessageBox__NothingToConsume(bytes32 entryKey); error MessageBox__OversizedContent(); - uint256 public constant MESSAGE_SIZE = 32; - - /// @dev Message sender on L1. ChainID if multiple L1s tap into the same Aztec instance. + /// @dev Actor on L1. ChainID if multiple L1s tap into the same Aztec instance. struct L1Actor { address actor; uint256 chainId; } - /// @dev Message receiver on L2. + /// @dev Actor on L2. `version` specifies which Aztec instance the actor is on (useful for upgrades) struct L2Actor { bytes32 actor; uint256 version; @@ -30,16 +28,21 @@ abstract contract MessageBox { /** * @dev Entry struct - Done as struct to easily support extensions if needed * @param count - The occurrence of the entry in the dataset + * @param fee - The fee provided to sequencer for including in the inbox. 0 if Oubox (as not applicable). */ struct Entry { - uint256 count; + uint64 count; + uint64 fee; + uint32 deadline; } address rollup; - mapping(bytes32 entryKey => Entry entry) internal entries; + // Prime field order + uint256 internal constant P = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; - event RollupUpdated(address indexed newRollup, address indexed oldRollup); + mapping(bytes32 entryKey => Entry entry) internal entries; modifier onlyRollup() { if (msg.sender != rollup) revert MessageBox__Unauthorized(); @@ -50,17 +53,24 @@ abstract contract MessageBox { rollup = msg.sender; } - function updateRollup(address _newRollup) external onlyRollup { - emit RollupUpdated(_newRollup, rollup); - rollup = _newRollup; - } - /** * @notice Inserts an entry into the multi-set * @param _entryKey - The key to insert + * @param _fee - The fee provided to sequencer for including in the inbox. 0 if Oubox (as not applicable). + * @param _deadline - The deadline to consume a message. Only after it, can a message be cancalled. */ - function _insert(bytes32 _entryKey) internal { + function _insert(bytes32 _entryKey, uint64 _fee, uint32 _deadline) internal { entries[_entryKey].count++; + entries[_entryKey].fee = _fee; + entries[_entryKey].deadline = _deadline; + } + + /** + * @notice Inserts an entry into the multi-set with default values for fee and deadline (0 each) + * @param _entryKey - The key to insert + */ + function _insertWithDefaultValues(bytes32 _entryKey) internal { + _insert(_entryKey, 0, 0); } /** diff --git a/l1-contracts/src/core/messagebridge/Outbox.sol b/l1-contracts/src/core/messagebridge/Outbox.sol index 89ed61e2f58..648e9ad902c 100644 --- a/l1-contracts/src/core/messagebridge/Outbox.sol +++ b/l1-contracts/src/core/messagebridge/Outbox.sol @@ -27,14 +27,7 @@ contract Outbox is MessageBox { } // to make it easier for portal to know when to consume the message. - event MessageAdded( - bytes32 indexed entryKey, - bytes32 indexed sender, - address indexed recipient, - uint256 senderVersion, - uint256 recipientChainId, - bytes32 content - ); + event MessageAdded(bytes32 indexed entryKey); event MessageConsumed(bytes32 indexed entryKey, address indexed recipient); @@ -44,32 +37,22 @@ contract Outbox is MessageBox { * @return The key of the entry in the set */ function computeEntryKey(L2ToL1Msg memory _message) public pure returns (bytes32) { - return keccak256(abi.encode(_message.sender, _message.recipient, _message.content)); + // FIXME: Replace mod P later on when we have a better idea of how to handle Fields. + return bytes32( + uint256(keccak256(abi.encode(_message.sender, _message.recipient, _message.content))) % P + ); } /** - * @notice Inserts an entry into the Outbox + * @notice Inserts an array of entries into the Outbox * @dev Only callable by the rollup contract - * @param _sender - The L2 sender of the message - * @param _recipient - The L1 recipient of the message - * @param _content - The content of the entry (application specific) - * @return The key of the entry in the set + * @param _entryKey - Array of entry keys (hash of the message) - computed by the L2 counterpart and sent to L1 via rollup block */ - function sendL1Message(L2Actor memory _sender, L1Actor memory _recipient, bytes32 _content) - external - onlyRollup - returns (bytes32) - { - if (block.chainid != _recipient.chainId) revert Outbox__WrongChainId(); - // TODO: Since the rollup contract does it, there isn't anything currently to prevent - // the sequencer from adding random messages from random L2 address. Add signature verification - L2ToL1Msg memory message = L2ToL1Msg(_sender, _recipient, _content); - bytes32 entryKey = computeEntryKey(message); - _insert(entryKey); - emit MessageAdded( - entryKey, _sender.actor, _recipient.actor, _sender.version, _recipient.chainId, _content - ); - return entryKey; + function sendL1Messages(bytes32[] memory _entryKey) external onlyRollup { + for (uint256 i = 0; i < _entryKey.length; i++) { + _insertWithDefaultValues(_entryKey[i]); + emit MessageAdded(_entryKey[i]); + } } /** From a092c859807bdfe8f4b044dc2ffc3be401143516 Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Mon, 15 May 2023 10:26:38 +0000 Subject: [PATCH 4/8] add registry --- .../core/messagebridge/IRegistryReader.sol | 14 ++++++++++ l1-contracts/src/core/messagebridge/Inbox.sol | 2 ++ .../src/core/messagebridge/MessageBox.sol | 10 ++++--- .../src/core/messagebridge/Outbox.sol | 2 ++ .../src/core/messagebridge/Registry.sol | 27 +++++++++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 l1-contracts/src/core/messagebridge/IRegistryReader.sol create mode 100644 l1-contracts/src/core/messagebridge/Registry.sol diff --git a/l1-contracts/src/core/messagebridge/IRegistryReader.sol b/l1-contracts/src/core/messagebridge/IRegistryReader.sol new file mode 100644 index 00000000000..09a8b2f3890 --- /dev/null +++ b/l1-contracts/src/core/messagebridge/IRegistryReader.sol @@ -0,0 +1,14 @@ +// interface that reads getter functions for Registry.sol: + +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.18; + +interface IRegistryReader { + struct L1L2Addresses { + address rollup; + address inbox; + address outbox; + } + + function getL1L2Addresses() external view returns (L1L2Addresses memory); +} diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index d551c3e9346..8101035647d 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -47,6 +47,8 @@ contract Inbox is MessageBox { event L1ToL2MessageCancelled(bytes32 indexed entryKey); + constructor(address _registry) MessageBox(_registry) {} + /// @notice Given a message, computes an entry key for the Inbox function computeMessageKey(L1ToL2Msg memory message) public pure returns (bytes32) { return bytes32( diff --git a/l1-contracts/src/core/messagebridge/MessageBox.sol b/l1-contracts/src/core/messagebridge/MessageBox.sol index ea59fbadf08..7f4641fc9d2 100644 --- a/l1-contracts/src/core/messagebridge/MessageBox.sol +++ b/l1-contracts/src/core/messagebridge/MessageBox.sol @@ -2,6 +2,8 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; +import {IRegistryReader} from "./IRegistryReader.sol"; + /** * @title MessageBox * @author Aztec Labs @@ -36,7 +38,7 @@ abstract contract MessageBox { uint32 deadline; } - address rollup; + IRegistryReader registry; // Prime field order uint256 internal constant P = @@ -45,12 +47,12 @@ abstract contract MessageBox { mapping(bytes32 entryKey => Entry entry) internal entries; modifier onlyRollup() { - if (msg.sender != rollup) revert MessageBox__Unauthorized(); + if (msg.sender != registry.getL1L2Addresses().rollup) revert MessageBox__Unauthorized(); _; } - constructor() { - rollup = msg.sender; + constructor(address _registry) { + registry = IRegistryReader(_registry); } /** diff --git a/l1-contracts/src/core/messagebridge/Outbox.sol b/l1-contracts/src/core/messagebridge/Outbox.sol index 648e9ad902c..3f7c8a4cf89 100644 --- a/l1-contracts/src/core/messagebridge/Outbox.sol +++ b/l1-contracts/src/core/messagebridge/Outbox.sol @@ -31,6 +31,8 @@ contract Outbox is MessageBox { event MessageConsumed(bytes32 indexed entryKey, address indexed recipient); + constructor(address _registry) MessageBox(_registry) {} + /** * @notice Computes an entry key for the Outbox * @param _message - The L2 to L1 message diff --git a/l1-contracts/src/core/messagebridge/Registry.sol b/l1-contracts/src/core/messagebridge/Registry.sol new file mode 100644 index 00000000000..3ba1e0f73ba --- /dev/null +++ b/l1-contracts/src/core/messagebridge/Registry.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +import {IRegistryReader} from "./IRegistryReader.sol"; + +/** + * @title Registry + * @author Aztec Labs + * @notice Keeps track of important information for L1<>L2 communication. + */ +contract Registry is IRegistryReader { + // TODO(rahul) - https://github.com/AztecProtocol/aztec-packages/issues/523 + // Need to create a snashot of addresses per version! + + L1L2Addresses addresses; + + // set the addresses in a setter function: + function setAddresses(address _rollup, address _inbox, address _outbox) public { + addresses = L1L2Addresses(_rollup, _inbox, _outbox); + } + + // get the addresses in a getter function: + function getL1L2Addresses() external view override returns (L1L2Addresses memory) { + return addresses; + } +} From c365699daff3626cb4db2193edef6aa2fe8d67ea Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Mon, 15 May 2023 14:03:11 +0000 Subject: [PATCH 5/8] s/keccak/sha and optimise insert() --- l1-contracts/src/core/messagebridge/Inbox.sol | 2 +- l1-contracts/src/core/messagebridge/MessageBox.sol | 10 +++++++--- l1-contracts/src/core/messagebridge/Outbox.sol | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index 8101035647d..0c43b9a63a0 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -53,7 +53,7 @@ contract Inbox is MessageBox { function computeMessageKey(L1ToL2Msg memory message) public pure returns (bytes32) { return bytes32( uint256( - keccak256( + sha256( abi.encode( message.sender, message.recipient, diff --git a/l1-contracts/src/core/messagebridge/MessageBox.sol b/l1-contracts/src/core/messagebridge/MessageBox.sol index 7f4641fc9d2..4a5f24855a8 100644 --- a/l1-contracts/src/core/messagebridge/MessageBox.sol +++ b/l1-contracts/src/core/messagebridge/MessageBox.sol @@ -62,9 +62,13 @@ abstract contract MessageBox { * @param _deadline - The deadline to consume a message. Only after it, can a message be cancalled. */ function _insert(bytes32 _entryKey, uint64 _fee, uint32 _deadline) internal { - entries[_entryKey].count++; - entries[_entryKey].fee = _fee; - entries[_entryKey].deadline = _deadline; + // since entryKey is a hash of the message, _fee and `deadline` should always be the same + // as such, there is no need to update these vars. Yet adding an if statement breaks + // the slot packing and increases gas. So we leave it as it is. + Entry storage entry = entries[_entryKey]; + entry.count += 1; + entry.fee = _fee; + entry.deadline = _deadline; } /** diff --git a/l1-contracts/src/core/messagebridge/Outbox.sol b/l1-contracts/src/core/messagebridge/Outbox.sol index 3f7c8a4cf89..9f1404fa22e 100644 --- a/l1-contracts/src/core/messagebridge/Outbox.sol +++ b/l1-contracts/src/core/messagebridge/Outbox.sol @@ -41,7 +41,7 @@ contract Outbox is MessageBox { function computeEntryKey(L2ToL1Msg memory _message) public pure returns (bytes32) { // FIXME: Replace mod P later on when we have a better idea of how to handle Fields. return bytes32( - uint256(keccak256(abi.encode(_message.sender, _message.recipient, _message.content))) % P + uint256(sha256(abi.encode(_message.sender, _message.recipient, _message.content))) % P ); } From 79fc682a582a21f5e0aa26194bf47de9b465a0ef Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Tue, 16 May 2023 14:01:05 +0000 Subject: [PATCH 6/8] create interfaces, rename aztec3->aztec, address pr comments --- l1-contracts/foundry.toml | 9 +- l1-contracts/src/core/Rollup.sol | 2 +- l1-contracts/src/core/interfaces/IRollup.sol | 7 ++ .../core/interfaces/messagebridge/IInbox.sol | 90 +++++++++++++++++++ .../interfaces/messagebridge/IMessageBox.sol | 48 ++++++++++ .../core/interfaces/messagebridge/IOutbox.sol | 53 +++++++++++ .../messagebridge/IRegistryReader.sol | 22 +++++ .../core/messagebridge/IRegistryReader.sol | 14 --- l1-contracts/src/core/messagebridge/Inbox.sol | 53 ++++------- .../src/core/messagebridge/MessageBox.sol | 63 ++++++------- .../src/core/messagebridge/Outbox.sol | 34 ++----- .../src/core/messagebridge/Registry.sol | 23 +++-- l1-contracts/test/Decoder.t.sol | 4 +- l1-contracts/test/DecoderHelper.sol | 4 +- 14 files changed, 298 insertions(+), 128 deletions(-) create mode 100644 l1-contracts/src/core/interfaces/IRollup.sol create mode 100644 l1-contracts/src/core/interfaces/messagebridge/IInbox.sol create mode 100644 l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol create mode 100644 l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol create mode 100644 l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol delete mode 100644 l1-contracts/src/core/messagebridge/IRegistryReader.sol diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 1adf884adc6..2c3176e39a6 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -6,10 +6,11 @@ solc = "0.8.18" remappings = [ "@oz/=lib/openzeppelin-contracts/contracts/", - "@aztec3/core/=src/core/", - "@aztec3/periphery/=src/periphery/", - "@aztec3/mock/=src/mock/", - "@aztec3/verifier/=lib/aztec-verifier-contracts/src/" + "@aztec/core/=src/core/", + "@aztec/interfaces/=src/core/interfaces/", + "@aztec/periphery/=src/periphery/", + "@aztec/mock/=src/mock/", + "@aztec/verifier/=lib/aztec-verifier-contracts/src/" ] # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index b1420be4a8e..63851a8ad05 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -2,7 +2,7 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {MockVerifier} from "@aztec3/mock/MockVerifier.sol"; +import {MockVerifier} from "@aztec/mock/MockVerifier.sol"; import {Decoder} from "./Decoder.sol"; /** diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol new file mode 100644 index 00000000000..42fbcd7caf9 --- /dev/null +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +interface IRollup { + function process(bytes memory _proof, bytes calldata _l2Block) external; +} diff --git a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol new file mode 100644 index 00000000000..8c23e27da46 --- /dev/null +++ b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +import {IMessageBox} from "./IMessageBox.sol"; + +/** + * @title Inbox + * @author Aztec Labs + * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. + */ +interface IInbox is IMessageBox { + /** + * @dev struct for sending messages from L1 to L2 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) + * @param deadline - The deadline to consume a message. Only after it, can a message be cancalled. + * @param fee - The fee provided to sequencer for including the entry + */ + struct L1ToL2Msg { + L1Actor sender; + L2Actor recipient; + bytes32 content; + bytes32 secretHash; + uint32 deadline; + uint64 fee; + } + + event MessageAdded( + bytes32 indexed entryKey, + address indexed sender, + bytes32 indexed recipient, + uint256 senderChainId, + uint256 recipientVersion, + uint32 deadline, + uint64 fee, + bytes32 content + ); + + event L1ToL2MessageCancelled(bytes32 indexed entryKey); + + /// @notice Given a message, computes an entry key for the Inbox + function computeMessageKey(L1ToL2Msg memory message) external pure returns (bytes32); + + /** + * @notice Inserts an entry into the Inbox + * @dev Will emit `MessageAdded` with data for easy access by the sequencer + * @dev msg.value - The fee provided to sequencer for including the entry + * @param _recipient - The recipient of the entry + * @param _deadline - The deadline to consume a message. Only after it, can a message be cancalled. + * @param _content - The content of the entry (application specific) + * @param _secretHash - The secret hash of the entry (make it possible to hide when a specific entry is consumed on L2) + * @return The key of the entry in the set + */ + function sendL2Message( + L2Actor memory _recipient, + uint32 _deadline, + bytes32 _content, + bytes32 _secretHash + ) external payable returns (bytes32); + + /** + * @notice Cancel a pending L2 message + * @dev Will revert if the deadline have not been crossed - message only cancellable past the deadline + * so it cannot be yanked away while the sequencer is building a block including it + * @dev Must be called by portal that inserted the entry + * @param _message - The content of the entry (application specific) + * @param _feeCollector - The address to receive the "fee" + * @return entryKey - The key of the entry removed + */ + function cancelL2Message(L1ToL2Msg memory _message, address _feeCollector) + external + returns (bytes32 entryKey); + + /** + * @notice Batch consumes entries from the Inbox + * @dev Only callable by the rollup contract + * @dev Will revert if the message is already past deadline + * @param entryKeys - Array of entry keys (hash of the messages) + * @param _feeCollector - The address to receive the "fee" + */ + function batchConsume(bytes32[] memory entryKeys, address _feeCollector) external; + + /** + * @notice Withdraws fees accrued by the sequencer + */ + function withdrawFees() external; +} diff --git a/l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol b/l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol new file mode 100644 index 00000000000..9edf10fe2c1 --- /dev/null +++ b/l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +/** + * @title IMessageBox + * @author Aztec Labs + * @notice Data structure used in both Inbox and Outbox for keeping track of entries + * Implements a multi-set storing the multiplicity (count for easy reading) at the entry. + */ +interface IMessageBox { + /// @dev Actor on L1. ChainID if multiple L1s tap into the same Aztec instance. + struct L1Actor { + address actor; + uint256 chainId; + } + + /// @dev Actor on L2. `version` specifies which Aztec instance the actor is on (useful for upgrades) + struct L2Actor { + bytes32 actor; + uint256 version; + } + + /** + * @dev Entry struct - Done as struct to easily support extensions if needed + * @param count - The occurrence of the entry in the dataset + * @param fee - The fee provided to sequencer for including in the inbox. 0 if Oubox (as not applicable). + */ + struct Entry { + uint64 count; + uint64 fee; + uint32 deadline; + } + + /** + * @notice Fetch an entry + * @param _entryKey - The key to lookup + * @return The entry matching the provided key + */ + function get(bytes32 _entryKey) external view returns (Entry memory); + + /** + * @notice Check if entry exists + * @param _entryKey - The key to lookup + * @return True if entry exists, false otherwise + */ + function contains(bytes32 _entryKey) external view returns (bool); +} diff --git a/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol b/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol new file mode 100644 index 00000000000..a149a1886df --- /dev/null +++ b/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +import {IMessageBox} from "./IMessageBox.sol"; + +/** + * @title IOutbox + * @author Aztec Labs + * @notice Lives on L1 and is used to consume L2 -> L1 messages. Messages are inserted by the rollup contract + * and will be consumed by the portal contracts. + */ +interface IOutbox is IMessageBox { + /** + * @dev struct for sending messages from L2 to L1 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + */ + struct L2ToL1Msg { + L2Actor sender; + L1Actor recipient; + bytes32 content; + } + + // to make it easier for portal to know when to consume the message. + event MessageAdded(bytes32 indexed entryKey); + + event MessageConsumed(bytes32 indexed entryKey, address indexed recipient); + + /** + * @notice Computes an entry key for the Outbox + * @param _message - The L2 to L1 message + * @return The key of the entry in the set + */ + function computeEntryKey(L2ToL1Msg memory _message) external returns (bytes32); + + /** + * @notice Inserts an array of entries into the Outbox + * @dev Only callable by the rollup contract + * @param _entryKey - Array of entry keys (hash of the message) - computed by the L2 counterpart and sent to L1 via rollup block + */ + function sendL1Messages(bytes32[] memory _entryKey) external; + + /** + * @notice Consumes an entry from the Outbox + * @dev Only meaningfully callable by portals, otherwise should never hit an entry + * @dev Emits the `MessageConsumed` event when consuming messages + * @param _message - The L2 to L1 message + * @return entryKey - The key of the entry removed + */ + function consume(L2ToL1Msg memory _message) external returns (bytes32 entryKey); +} diff --git a/l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol b/l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol new file mode 100644 index 00000000000..1f5bcffe2fe --- /dev/null +++ b/l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.18; + +import {IRollup} from "@aztec/interfaces/IRollup.sol"; +import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; +import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; + +interface IRegistryReader { + struct L1L2Addresses { + address rollup; + address inbox; + address outbox; + } + + function getL1L2Addresses() external view returns (L1L2Addresses memory); + + function getRollupAddress() external view returns (IRollup); + + function getInboxAddress() external view returns (IInbox); + + function getOutboxAddress() external view returns (IOutbox); +} diff --git a/l1-contracts/src/core/messagebridge/IRegistryReader.sol b/l1-contracts/src/core/messagebridge/IRegistryReader.sol deleted file mode 100644 index 09a8b2f3890..00000000000 --- a/l1-contracts/src/core/messagebridge/IRegistryReader.sol +++ /dev/null @@ -1,14 +0,0 @@ -// interface that reads getter functions for Registry.sol: - -// SPDX-License-Identifier: Apache-2.0 -pragma solidity >=0.8.18; - -interface IRegistryReader { - struct L1L2Addresses { - address rollup; - address inbox; - address outbox; - } - - function getL1L2Addresses() external view returns (L1L2Addresses memory); -} diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index 0c43b9a63a0..0e5dc53aea9 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -2,6 +2,7 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; +import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; import {MessageBox} from "./MessageBox.sol"; /** @@ -9,47 +10,22 @@ import {MessageBox} from "./MessageBox.sol"; * @author Aztec Labs * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. */ -contract Inbox is MessageBox { +contract Inbox is MessageBox, IInbox { + error Inbox__DeadlineBeforeNow(); error Inbox__NotPastDeadline(); error Inbox__PastDeadline(); error Inbox__Unauthorized(); - - /** - * @dev struct for sending messages from L1 to L2 - * @param sender - The sender of the message - * @param recipient - The recipient of the message - * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. - * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) - * @param deadline - The deadline to consume a message. Only after it, can a message be cancalled. - * @param fee - The fee provided to sequencer for including the entry - */ - struct L1ToL2Msg { - L1Actor sender; - L2Actor recipient; - bytes32 content; - bytes32 secretHash; - uint32 deadline; - uint64 fee; - } + error Inbox__FailedToWithdrawFees(); mapping(address account => uint256 balance) public feesAccrued; - event MessageAdded( - bytes32 indexed entryKey, - address indexed sender, - bytes32 indexed recipient, - uint256 senderChainId, - uint256 recipientVersion, - uint32 deadline, - uint64 fee, - bytes32 content - ); - - event L1ToL2MessageCancelled(bytes32 indexed entryKey); - constructor(address _registry) MessageBox(_registry) {} - /// @notice Given a message, computes an entry key for the Inbox + /** + * @notice Given a message, computes an entry key for the Inbox + * @param message - The L1 to L2 message + * @return The hash of the message (used as the key of the entry in the set) + */ function computeMessageKey(L1ToL2Msg memory message) public pure returns (bytes32) { return bytes32( uint256( @@ -63,7 +39,7 @@ contract Inbox is MessageBox { message.fee ) ) - ) % P // FIXME: Replace mod P later on when we have a better idea of how to handle Fields. + ) % P // TODO: Replace mod P later on when we have a better idea of how to handle Fields. ); } @@ -83,6 +59,7 @@ contract Inbox is MessageBox { bytes32 _content, bytes32 _secretHash ) external payable returns (bytes32) { + if (_deadline <= block.timestamp) revert Inbox__DeadlineBeforeNow(); uint64 fee = uint64(msg.value); L1ToL2Msg memory message = L1ToL2Msg({ sender: L1Actor(msg.sender, block.chainid), @@ -152,9 +129,11 @@ contract Inbox is MessageBox { /** * @notice Withdraws fees accrued by the sequencer - * @param _amount - The amount to withdraw */ - function withdrawFees(uint256 _amount) external { - // TODO: reentrancy attack, safe eth fees withdrawal. + function withdrawFees() external { + uint256 balance = feesAccrued[msg.sender]; + feesAccrued[msg.sender] = 0; + (bool success,) = msg.sender.call{value: balance}(""); + if (!success) revert Inbox__FailedToWithdrawFees(); } } diff --git a/l1-contracts/src/core/messagebridge/MessageBox.sol b/l1-contracts/src/core/messagebridge/MessageBox.sol index 4a5f24855a8..3d5072405fa 100644 --- a/l1-contracts/src/core/messagebridge/MessageBox.sol +++ b/l1-contracts/src/core/messagebridge/MessageBox.sol @@ -2,7 +2,8 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {IRegistryReader} from "./IRegistryReader.sol"; +import {IRegistryReader} from "@aztec/interfaces/messagebridge/IRegistryReader.sol"; +import {IMessageBox} from "@aztec/interfaces/messagebridge/IMessageBox.sol"; /** * @title MessageBox @@ -10,49 +11,33 @@ import {IRegistryReader} from "./IRegistryReader.sol"; * @notice Data structure used in both Inbox and Outbox for keeping track of entries * Implements a multi-set storing the multiplicity (count for easy reading) at the entry. */ -abstract contract MessageBox { +abstract contract MessageBox is IMessageBox { error MessageBox__Unauthorized(); + error MessageBox__IncompatibleEntryArguments( + bytes32 entryKey, + uint64 storedFee, + uint64 feePassed, + uint32 storedDeadline, + uint32 deadlinePassed + ); error MessageBox__NothingToConsume(bytes32 entryKey); error MessageBox__OversizedContent(); - /// @dev Actor on L1. ChainID if multiple L1s tap into the same Aztec instance. - struct L1Actor { - address actor; - uint256 chainId; - } - - /// @dev Actor on L2. `version` specifies which Aztec instance the actor is on (useful for upgrades) - struct L2Actor { - bytes32 actor; - uint256 version; - } - - /** - * @dev Entry struct - Done as struct to easily support extensions if needed - * @param count - The occurrence of the entry in the dataset - * @param fee - The fee provided to sequencer for including in the inbox. 0 if Oubox (as not applicable). - */ - struct Entry { - uint64 count; - uint64 fee; - uint32 deadline; - } - - IRegistryReader registry; - // Prime field order uint256 internal constant P = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + IRegistryReader immutable REGISTRY; + mapping(bytes32 entryKey => Entry entry) internal entries; modifier onlyRollup() { - if (msg.sender != registry.getL1L2Addresses().rollup) revert MessageBox__Unauthorized(); + if (msg.sender != REGISTRY.getL1L2Addresses().rollup) revert MessageBox__Unauthorized(); _; } constructor(address _registry) { - registry = IRegistryReader(_registry); + REGISTRY = IRegistryReader(_registry); } /** @@ -65,18 +50,20 @@ abstract contract MessageBox { // since entryKey is a hash of the message, _fee and `deadline` should always be the same // as such, there is no need to update these vars. Yet adding an if statement breaks // the slot packing and increases gas. So we leave it as it is. - Entry storage entry = entries[_entryKey]; + Entry memory entry = entries[_entryKey]; + if ( + (entry.fee != 0 && entry.fee != _fee) || (entry.deadline != 0 && entry.deadline != _deadline) + ) { + // this should never happen as it is trying to overwrite `fee` and `deadline` with different values + // even though the entryKey (a hash) is the same! Pass all arguments to the error message for debugging. + revert MessageBox__IncompatibleEntryArguments( + _entryKey, entry.fee, _fee, entry.deadline, _deadline + ); + } entry.count += 1; entry.fee = _fee; entry.deadline = _deadline; - } - - /** - * @notice Inserts an entry into the multi-set with default values for fee and deadline (0 each) - * @param _entryKey - The key to insert - */ - function _insertWithDefaultValues(bytes32 _entryKey) internal { - _insert(_entryKey, 0, 0); + entries[_entryKey] = entry; } /** diff --git a/l1-contracts/src/core/messagebridge/Outbox.sol b/l1-contracts/src/core/messagebridge/Outbox.sol index 9f1404fa22e..cd807cf5b98 100644 --- a/l1-contracts/src/core/messagebridge/Outbox.sol +++ b/l1-contracts/src/core/messagebridge/Outbox.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.18; import {MessageBox} from "./MessageBox.sol"; +import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; /** * @title Outbox @@ -10,27 +11,10 @@ import {MessageBox} from "./MessageBox.sol"; * @notice Lives on L1 and is used to consume L2 -> L1 messages. Messages are inserted by the rollup contract * and will be consumed by the portal contracts. */ -contract Outbox is MessageBox { +contract Outbox is MessageBox, IOutbox { error Outbox__Unauthorized(); error Outbox__WrongChainId(); - /** - * @dev struct for sending messages from L2 to L1 - * @param sender - The sender of the message - * @param recipient - The recipient of the message - * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. - */ - struct L2ToL1Msg { - L2Actor sender; - L1Actor recipient; - bytes32 content; - } - - // to make it easier for portal to know when to consume the message. - event MessageAdded(bytes32 indexed entryKey); - - event MessageConsumed(bytes32 indexed entryKey, address indexed recipient); - constructor(address _registry) MessageBox(_registry) {} /** @@ -39,7 +23,7 @@ contract Outbox is MessageBox { * @return The key of the entry in the set */ function computeEntryKey(L2ToL1Msg memory _message) public pure returns (bytes32) { - // FIXME: Replace mod P later on when we have a better idea of how to handle Fields. + // TODO: Replace mod P later on when we have a better idea of how to handle Fields. return bytes32( uint256(sha256(abi.encode(_message.sender, _message.recipient, _message.content))) % P ); @@ -48,12 +32,12 @@ contract Outbox is MessageBox { /** * @notice Inserts an array of entries into the Outbox * @dev Only callable by the rollup contract - * @param _entryKey - Array of entry keys (hash of the message) - computed by the L2 counterpart and sent to L1 via rollup block + * @param _entryKeys - Array of entry keys (hash of the message) - computed by the L2 counterpart and sent to L1 via rollup block */ - function sendL1Messages(bytes32[] memory _entryKey) external onlyRollup { - for (uint256 i = 0; i < _entryKey.length; i++) { - _insertWithDefaultValues(_entryKey[i]); - emit MessageAdded(_entryKey[i]); + function sendL1Messages(bytes32[] memory _entryKeys) external onlyRollup { + for (uint256 i = 0; i < _entryKeys.length; i++) { + _insert(_entryKeys[i], 0, 0); + emit MessageAdded(_entryKeys[i]); } } @@ -70,6 +54,6 @@ contract Outbox is MessageBox { entryKey = computeEntryKey(_message); _consume(entryKey); - emit MessageConsumed(entryKey, _message.recipient.actor); + emit MessageConsumed(entryKey, msg.sender); } } diff --git a/l1-contracts/src/core/messagebridge/Registry.sol b/l1-contracts/src/core/messagebridge/Registry.sol index 3ba1e0f73ba..90bb87c97f1 100644 --- a/l1-contracts/src/core/messagebridge/Registry.sol +++ b/l1-contracts/src/core/messagebridge/Registry.sol @@ -2,7 +2,10 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {IRegistryReader} from "./IRegistryReader.sol"; +import {IRegistryReader} from "@aztec/interfaces/messagebridge/IRegistryReader.sol"; +import {IRollup} from "@aztec/interfaces/IRollup.sol"; +import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; +import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; /** * @title Registry @@ -10,18 +13,28 @@ import {IRegistryReader} from "./IRegistryReader.sol"; * @notice Keeps track of important information for L1<>L2 communication. */ contract Registry is IRegistryReader { - // TODO(rahul) - https://github.com/AztecProtocol/aztec-packages/issues/523 + // TODO(rahul) - https://github.com/AztecProtocol/aztec-packages/issues/526 // Need to create a snashot of addresses per version! - L1L2Addresses addresses; + L1L2Addresses public addresses; - // set the addresses in a setter function: function setAddresses(address _rollup, address _inbox, address _outbox) public { addresses = L1L2Addresses(_rollup, _inbox, _outbox); } - // get the addresses in a getter function: function getL1L2Addresses() external view override returns (L1L2Addresses memory) { return addresses; } + + function getRollupAddress() external view override returns (IRollup) { + return IRollup(addresses.rollup); + } + + function getInboxAddress() external view override returns (IInbox) { + return IInbox(addresses.inbox); + } + + function getOutboxAddress() external view override returns (IOutbox) { + return IOutbox(addresses.outbox); + } } diff --git a/l1-contracts/test/Decoder.t.sol b/l1-contracts/test/Decoder.t.sol index 31375042e01..c3250651f7e 100644 --- a/l1-contracts/test/Decoder.t.sol +++ b/l1-contracts/test/Decoder.t.sol @@ -4,8 +4,8 @@ pragma solidity >=0.8.18; import {Test} from "forge-std/Test.sol"; -import {Decoder} from "@aztec3/core/Decoder.sol"; -import {Rollup} from "@aztec3/core/Rollup.sol"; +import {Decoder} from "@aztec/core/Decoder.sol"; +import {Rollup} from "@aztec/core/Rollup.sol"; import {DecoderHelper} from "./DecoderHelper.sol"; /** diff --git a/l1-contracts/test/DecoderHelper.sol b/l1-contracts/test/DecoderHelper.sol index 5831b486a82..e35f7cec887 100644 --- a/l1-contracts/test/DecoderHelper.sol +++ b/l1-contracts/test/DecoderHelper.sol @@ -2,8 +2,8 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {Decoder} from "@aztec3/core/Decoder.sol"; -import {Rollup} from "@aztec3/core/Rollup.sol"; +import {Decoder} from "@aztec/core/Decoder.sol"; +import {Rollup} from "@aztec/core/Rollup.sol"; contract DecoderHelper is Decoder { function decode(bytes calldata _l2Block) From d64b7d23e7bc937a1eb7808df2be095433169455 Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Tue, 16 May 2023 14:01:18 +0000 Subject: [PATCH 7/8] add tests --- .../src/core/messagebridge/MessageBox.sol | 2 +- l1-contracts/test/Inbox.t.sol | 235 ++++++++++++++++++ l1-contracts/test/Outbox.t.sol | 111 +++++++++ 3 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 l1-contracts/test/Inbox.t.sol create mode 100644 l1-contracts/test/Outbox.t.sol diff --git a/l1-contracts/src/core/messagebridge/MessageBox.sol b/l1-contracts/src/core/messagebridge/MessageBox.sol index 3d5072405fa..2f8df2da92d 100644 --- a/l1-contracts/src/core/messagebridge/MessageBox.sol +++ b/l1-contracts/src/core/messagebridge/MessageBox.sol @@ -32,7 +32,7 @@ abstract contract MessageBox is IMessageBox { mapping(bytes32 entryKey => Entry entry) internal entries; modifier onlyRollup() { - if (msg.sender != REGISTRY.getL1L2Addresses().rollup) revert MessageBox__Unauthorized(); + if (msg.sender != address(REGISTRY.getRollupAddress())) revert MessageBox__Unauthorized(); _; } diff --git a/l1-contracts/test/Inbox.t.sol b/l1-contracts/test/Inbox.t.sol new file mode 100644 index 00000000000..001ca6bb94c --- /dev/null +++ b/l1-contracts/test/Inbox.t.sol @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +import {Test} from "forge-std/Test.sol"; +import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; +import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; +import {IMessageBox} from "@aztec/interfaces/messagebridge/IMessageBox.sol"; +import {MessageBox} from "@aztec/core/messagebridge/MessageBox.sol"; +import {Registry} from "@aztec/core/messagebridge/Registry.sol"; + +contract InboxTest is Test { + Inbox inbox; + + event MessageAdded( + bytes32 indexed entryKey, + address indexed sender, + bytes32 indexed recipient, + uint256 senderChainId, + uint256 recipientVersion, + uint32 deadline, + uint64 fee, + bytes32 content + ); + event L1ToL2MessageCancelled(bytes32 indexed entryKey); + + function setUp() public { + address rollup = address(this); + Registry registry = new Registry(); + inbox = new Inbox(address(registry)); + registry.setAddresses(rollup, address(inbox), address(0x0)); + } + + function _helper_computeEntryKey(Inbox.L1ToL2Msg memory message) internal pure returns (bytes32) { + return bytes32( + uint256( + sha256( + abi.encode( + message.sender, + message.recipient, + message.content, + message.secretHash, + message.deadline, + message.fee + ) + ) + ) % 21888242871839275222246405745257275088548364400416034343698204186575808495617 + ); + } + + function _fakeMessage() internal view returns (Inbox.L1ToL2Msg memory) { + return IInbox.L1ToL2Msg({ + sender: IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}), + recipient: IMessageBox.L2Actor({ + actor: 0x2000000000000000000000000000000000000000000000000000000000000000, + version: 1 + }), + content: 0x3000000000000000000000000000000000000000000000000000000000000000, + secretHash: 0x4000000000000000000000000000000000000000000000000000000000000000, + fee: 5, + deadline: uint32(block.timestamp + 100) + }); + } + + function testFuzzSendL2Msg(Inbox.L1ToL2Msg memory message) public { + // fix message.sender and deadline: + message.sender = IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}); + if (message.deadline <= block.timestamp) { + message.deadline = uint32(block.timestamp + 100); + } + bytes32 expectedEntryKey = _helper_computeEntryKey(message); + vm.expectEmit(true, true, true, true); + // event we expect + emit MessageAdded( + expectedEntryKey, + message.sender.actor, + message.recipient.actor, + message.sender.chainId, + message.recipient.version, + message.deadline, + message.fee, + message.content + ); + // event we will get + bytes32 entryKey = inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + assertEq(entryKey, expectedEntryKey); + assertEq(inbox.get(entryKey).count, 1); + assertEq(inbox.get(entryKey).fee, message.fee); + assertEq(inbox.get(entryKey).deadline, message.deadline); + } + + function testSendMultipleSameL2Messages() public { + Inbox.L1ToL2Msg memory message = _fakeMessage(); + bytes32 entryKey1 = inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + bytes32 entryKey2 = inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + bytes32 entryKey3 = inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + + assertEq(entryKey1, entryKey2); + assertEq(entryKey2, entryKey3); + assertEq(inbox.get(entryKey1).count, 3); + assertEq(inbox.get(entryKey1).fee, 5); + assertEq(inbox.get(entryKey1).deadline, message.deadline); + } + + function testRevertIfCancellingMessageFromDifferentAddress() public { + Inbox.L1ToL2Msg memory message = _fakeMessage(); + inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + vm.prank(address(0x1)); + vm.expectRevert(Inbox.Inbox__Unauthorized.selector); + inbox.cancelL2Message(message, address(0x1)); + } + + function testRevertIfCancellingMessageWhenDeadlineHasntPassed() public { + Inbox.L1ToL2Msg memory message = _fakeMessage(); + inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + skip(1000); // block.timestamp now +1000 ms. + vm.expectRevert(Inbox.Inbox__NotPastDeadline.selector); + inbox.cancelL2Message(message, address(0x1)); + } + + function testRevertIfCancellingNonExistentMessage() public { + Inbox.L1ToL2Msg memory message = _fakeMessage(); + bytes32 entryKey = _helper_computeEntryKey(message); + vm.expectRevert( + abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, entryKey) + ); + inbox.cancelL2Message(message, address(0x1)); + } + + function testCancelMessage() public { + Inbox.L1ToL2Msg memory message = _fakeMessage(); + address feeCollector = address(0x1); + bytes32 expectedEntryKey = inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + + vm.expectEmit(true, false, false, false); + // event we expect + emit L1ToL2MessageCancelled(expectedEntryKey); + // event we will get + inbox.cancelL2Message(message, feeCollector); + + // no such message to consume: + vm.expectRevert( + abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, expectedEntryKey) + ); + inbox.get(expectedEntryKey); + + // fees accrued as expected: + assertEq(inbox.feesAccrued(feeCollector), message.fee); + } + + function testRevertIfNotConsumingFromRollup() public { + vm.prank(address(0x1)); + bytes32[] memory entryKeys = new bytes32[](1); + entryKeys[0] = bytes32("random"); + vm.expectRevert(MessageBox.MessageBox__Unauthorized.selector); + inbox.batchConsume(entryKeys, address(0x1)); + } + + function testRevertIfOneKeyIsPastDeadlineWhenBatchConsuming() public { + Inbox.L1ToL2Msg memory message = _fakeMessage(); + bytes32 entryKey1 = inbox.sendL2Message{value: message.fee}( + message.recipient, uint32(block.timestamp + 100), message.content, message.secretHash + ); + bytes32 entryKey2 = inbox.sendL2Message{value: message.fee}( + message.recipient, uint32(block.timestamp + 200), message.content, message.secretHash + ); + bytes32 entryKey3 = inbox.sendL2Message{value: message.fee}( + message.recipient, uint32(block.timestamp + 300), message.content, message.secretHash + ); + bytes32[] memory entryKeys = new bytes32[](3); + entryKeys[0] = entryKey1; + entryKeys[1] = entryKey2; + entryKeys[2] = entryKey3; + + skip(150); // block.timestamp now +150 ms. entryKey2 and entryKey3 is past deadline + vm.expectRevert(Inbox.Inbox__PastDeadline.selector); + inbox.batchConsume(entryKeys, address(0x1)); + } + + function testRevertIfConsumingAMessageThatDoesntExist(bytes32[] memory entryKeys) public { + if (entryKeys.length == 0) { + entryKeys = new bytes32[](1); + entryKeys[0] = bytes32("random"); + } + vm.expectRevert( + abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, entryKeys[0]) + ); + inbox.batchConsume(entryKeys, address(0x1)); + } + + function testBatchConsume(Inbox.L1ToL2Msg[] memory messages) public { + bytes32[] memory entryKeys = new bytes32[](messages.length); + uint256 expectedTotalFee = 0; + address feeCollector = address(0x1); + uint256 maxDeadline = 0; // for skipping time (to avoid past deadline revert) + + // insert messages: + for (uint256 i = 0; i < messages.length; i++) { + Inbox.L1ToL2Msg memory message = messages[i]; + // fix message.sender and deadline: + message.sender = IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}); + if (message.deadline <= block.timestamp) { + message.deadline = uint32(block.timestamp + 100); + } + if (message.deadline > maxDeadline) { + maxDeadline = message.deadline; + } + expectedTotalFee += message.fee; + entryKeys[i] = inbox.sendL2Message{value: message.fee}( + message.recipient, message.deadline, message.content, message.secretHash + ); + } + + skip(maxDeadline + 100); + // batch consume: + inbox.batchConsume(entryKeys, feeCollector); + + // fees accrued as expected: + assertEq(inbox.feesAccrued(feeCollector), expectedTotalFee); + } +} diff --git a/l1-contracts/test/Outbox.t.sol b/l1-contracts/test/Outbox.t.sol new file mode 100644 index 00000000000..ee880fbb86f --- /dev/null +++ b/l1-contracts/test/Outbox.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +import {Test} from "forge-std/Test.sol"; +import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; +import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; +import {IMessageBox} from "@aztec/interfaces/messagebridge/IMessageBox.sol"; +import {MessageBox} from "@aztec/core/messagebridge/MessageBox.sol"; +import {Registry} from "@aztec/core/messagebridge/Registry.sol"; + +contract OutboxTest is Test { + Outbox outbox; + + event MessageAdded(bytes32 indexed entryKey); + event MessageConsumed(bytes32 indexed entryKey, address indexed recipient); + + function setUp() public { + address rollup = address(this); + Registry registry = new Registry(); + outbox = new Outbox(address(registry)); + registry.setAddresses(rollup, address(0x0), address(outbox)); + } + + function _helper_computeEntryKey(Outbox.L2ToL1Msg memory message) internal pure returns (bytes32) { + return bytes32( + uint256(sha256(abi.encode(message.sender, message.recipient, message.content))) + % 21888242871839275222246405745257275088548364400416034343698204186575808495617 + ); + } + + function _fakeMessage() internal view returns (Outbox.L2ToL1Msg memory) { + return IOutbox.L2ToL1Msg({ + sender: IMessageBox.L2Actor({ + actor: 0x2000000000000000000000000000000000000000000000000000000000000000, + version: 1 + }), + recipient: IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}), + content: 0x3000000000000000000000000000000000000000000000000000000000000000 + }); + } + + function testRevertIfInsertingFromNonRollup() public { + vm.prank(address(0x1)); + bytes32[] memory entryKeys = new bytes32[](1); + entryKeys[0] = bytes32("random"); + vm.expectRevert(MessageBox.MessageBox__Unauthorized.selector); + outbox.sendL1Messages(entryKeys); + } + + // fuzz batch insert -> check inserted. event emitted + function testFuzzBatchInsert(bytes32[] memory entryKeys) public { + // expected events + for (uint256 i = 0; i < entryKeys.length; i++) { + vm.expectEmit(true, false, false, false); + emit MessageAdded(entryKeys[i]); + } + + outbox.sendL1Messages(entryKeys); + for (uint256 i = 0; i < entryKeys.length; i++) { + bytes32 key = entryKeys[i]; + Outbox.Entry memory entry = outbox.get(key); + assertGt(entry.count, 0); + assertEq(entry.fee, 0); + assertEq(entry.deadline, 0); + } + } + + function testRevertIfConsumingFromWrongRecipient() public { + Outbox.L2ToL1Msg memory message = _fakeMessage(); + message.recipient.actor = address(0x1); + vm.expectRevert(Outbox.Outbox__Unauthorized.selector); + outbox.consume(message); + } + + function testRevertIfConsumingForWrongChain() public { + Outbox.L2ToL1Msg memory message = _fakeMessage(); + message.recipient.chainId = 2; + vm.expectRevert(Outbox.Outbox__WrongChainId.selector); + outbox.consume(message); + } + + function testRevertIfConsumingMessageThatDoesntExist() public { + Outbox.L2ToL1Msg memory message = _fakeMessage(); + bytes32 entryKey = _helper_computeEntryKey(message); + vm.expectRevert( + abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, entryKey) + ); + outbox.consume(message); + } + + function testConsume() public { + // with fuzzing it takes way too long :() + Outbox.L2ToL1Msg memory message = _fakeMessage(); + bytes32 expectedEntryKey = _helper_computeEntryKey(message); + bytes32[] memory entryKeys = new bytes32[](1); + entryKeys[0] = expectedEntryKey; + outbox.sendL1Messages(entryKeys); + + vm.prank(message.recipient.actor); + vm.expectEmit(true, true, false, false); + emit MessageConsumed(expectedEntryKey, message.recipient.actor); + outbox.consume(message); + + // ensure no such message to consume: + vm.expectRevert( + abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, expectedEntryKey) + ); + outbox.get(expectedEntryKey); + } +} From a7022988aa0b601c73f24ff79e11b9d8e354f8d9 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Tue, 16 May 2023 15:56:52 +0100 Subject: [PATCH 8/8] Contracts: Data structure library (#592) * fix: data structure library * fix: forge fmt --- l1-contracts/foundry.toml | 1 - .../core/interfaces/messagebridge/IInbox.sol | 31 ++------ .../interfaces/messagebridge/IMessageBox.sol | 27 +------ .../core/interfaces/messagebridge/IOutbox.sol | 20 +---- .../interfaces/messagebridge/IRegistry.sol | 17 ++++ .../messagebridge/IRegistryReader.sol | 22 ------ .../src/core/libraries/DataStructures.sol | 78 +++++++++++++++++++ l1-contracts/src/core/messagebridge/Inbox.sol | 15 ++-- .../src/core/messagebridge/MessageBox.sol | 23 +++--- .../src/core/messagebridge/Outbox.sol | 7 +- .../src/core/messagebridge/Registry.sol | 24 +++--- l1-contracts/test/Inbox.t.sol | 42 +++++----- l1-contracts/test/Outbox.t.sol | 30 ++++--- 13 files changed, 189 insertions(+), 148 deletions(-) create mode 100644 l1-contracts/src/core/interfaces/messagebridge/IRegistry.sol delete mode 100644 l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol create mode 100644 l1-contracts/src/core/libraries/DataStructures.sol diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 2c3176e39a6..dba23d01fd8 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -7,7 +7,6 @@ solc = "0.8.18" remappings = [ "@oz/=lib/openzeppelin-contracts/contracts/", "@aztec/core/=src/core/", - "@aztec/interfaces/=src/core/interfaces/", "@aztec/periphery/=src/periphery/", "@aztec/mock/=src/mock/", "@aztec/verifier/=lib/aztec-verifier-contracts/src/" diff --git a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol index 8c23e27da46..d93c3b7276b 100644 --- a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol +++ b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol @@ -2,32 +2,14 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {IMessageBox} from "./IMessageBox.sol"; +import {DataStructures} from "../../libraries/DataStructures.sol"; /** * @title Inbox * @author Aztec Labs * @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages. */ -interface IInbox is IMessageBox { - /** - * @dev struct for sending messages from L1 to L2 - * @param sender - The sender of the message - * @param recipient - The recipient of the message - * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. - * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) - * @param deadline - The deadline to consume a message. Only after it, can a message be cancalled. - * @param fee - The fee provided to sequencer for including the entry - */ - struct L1ToL2Msg { - L1Actor sender; - L2Actor recipient; - bytes32 content; - bytes32 secretHash; - uint32 deadline; - uint64 fee; - } - +interface IInbox { event MessageAdded( bytes32 indexed entryKey, address indexed sender, @@ -42,7 +24,10 @@ interface IInbox is IMessageBox { event L1ToL2MessageCancelled(bytes32 indexed entryKey); /// @notice Given a message, computes an entry key for the Inbox - function computeMessageKey(L1ToL2Msg memory message) external pure returns (bytes32); + function computeMessageKey(DataStructures.L1ToL2Msg memory message) + external + pure + returns (bytes32); /** * @notice Inserts an entry into the Inbox @@ -55,7 +40,7 @@ interface IInbox is IMessageBox { * @return The key of the entry in the set */ function sendL2Message( - L2Actor memory _recipient, + DataStructures.L2Actor memory _recipient, uint32 _deadline, bytes32 _content, bytes32 _secretHash @@ -70,7 +55,7 @@ interface IInbox is IMessageBox { * @param _feeCollector - The address to receive the "fee" * @return entryKey - The key of the entry removed */ - function cancelL2Message(L1ToL2Msg memory _message, address _feeCollector) + function cancelL2Message(DataStructures.L1ToL2Msg memory _message, address _feeCollector) external returns (bytes32 entryKey); diff --git a/l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol b/l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol index 9edf10fe2c1..bd6e4efc3a5 100644 --- a/l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol +++ b/l1-contracts/src/core/interfaces/messagebridge/IMessageBox.sol @@ -2,6 +2,8 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; +import {DataStructures} from "../../libraries/DataStructures.sol"; + /** * @title IMessageBox * @author Aztec Labs @@ -9,35 +11,12 @@ pragma solidity >=0.8.18; * Implements a multi-set storing the multiplicity (count for easy reading) at the entry. */ interface IMessageBox { - /// @dev Actor on L1. ChainID if multiple L1s tap into the same Aztec instance. - struct L1Actor { - address actor; - uint256 chainId; - } - - /// @dev Actor on L2. `version` specifies which Aztec instance the actor is on (useful for upgrades) - struct L2Actor { - bytes32 actor; - uint256 version; - } - - /** - * @dev Entry struct - Done as struct to easily support extensions if needed - * @param count - The occurrence of the entry in the dataset - * @param fee - The fee provided to sequencer for including in the inbox. 0 if Oubox (as not applicable). - */ - struct Entry { - uint64 count; - uint64 fee; - uint32 deadline; - } - /** * @notice Fetch an entry * @param _entryKey - The key to lookup * @return The entry matching the provided key */ - function get(bytes32 _entryKey) external view returns (Entry memory); + function get(bytes32 _entryKey) external view returns (DataStructures.Entry memory); /** * @notice Check if entry exists diff --git a/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol b/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol index a149a1886df..83cb6096dba 100644 --- a/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol +++ b/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol @@ -2,7 +2,7 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {IMessageBox} from "./IMessageBox.sol"; +import {DataStructures} from "../../libraries/DataStructures.sol"; /** * @title IOutbox @@ -10,19 +10,7 @@ import {IMessageBox} from "./IMessageBox.sol"; * @notice Lives on L1 and is used to consume L2 -> L1 messages. Messages are inserted by the rollup contract * and will be consumed by the portal contracts. */ -interface IOutbox is IMessageBox { - /** - * @dev struct for sending messages from L2 to L1 - * @param sender - The sender of the message - * @param recipient - The recipient of the message - * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. - */ - struct L2ToL1Msg { - L2Actor sender; - L1Actor recipient; - bytes32 content; - } - +interface IOutbox { // to make it easier for portal to know when to consume the message. event MessageAdded(bytes32 indexed entryKey); @@ -33,7 +21,7 @@ interface IOutbox is IMessageBox { * @param _message - The L2 to L1 message * @return The key of the entry in the set */ - function computeEntryKey(L2ToL1Msg memory _message) external returns (bytes32); + function computeEntryKey(DataStructures.L2ToL1Msg memory _message) external returns (bytes32); /** * @notice Inserts an array of entries into the Outbox @@ -49,5 +37,5 @@ interface IOutbox is IMessageBox { * @param _message - The L2 to L1 message * @return entryKey - The key of the entry removed */ - function consume(L2ToL1Msg memory _message) external returns (bytes32 entryKey); + function consume(DataStructures.L2ToL1Msg memory _message) external returns (bytes32 entryKey); } diff --git a/l1-contracts/src/core/interfaces/messagebridge/IRegistry.sol b/l1-contracts/src/core/interfaces/messagebridge/IRegistry.sol new file mode 100644 index 00000000000..d6b40e7d3dd --- /dev/null +++ b/l1-contracts/src/core/interfaces/messagebridge/IRegistry.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.18; + +import {DataStructures} from "../../libraries/DataStructures.sol"; +import {IRollup} from "../IRollup.sol"; +import {IInbox} from "./IInbox.sol"; +import {IOutbox} from "./IOutbox.sol"; + +interface IRegistry { + function getL1L2Addresses() external view returns (DataStructures.L1L2Addresses memory); + + function getRollup() external view returns (IRollup); + + function getInbox() external view returns (IInbox); + + function getOutbox() external view returns (IOutbox); +} diff --git a/l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol b/l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol deleted file mode 100644 index 1f5bcffe2fe..00000000000 --- a/l1-contracts/src/core/interfaces/messagebridge/IRegistryReader.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity >=0.8.18; - -import {IRollup} from "@aztec/interfaces/IRollup.sol"; -import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; -import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; - -interface IRegistryReader { - struct L1L2Addresses { - address rollup; - address inbox; - address outbox; - } - - function getL1L2Addresses() external view returns (L1L2Addresses memory); - - function getRollupAddress() external view returns (IRollup); - - function getInboxAddress() external view returns (IInbox); - - function getOutboxAddress() external view returns (IOutbox); -} diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol new file mode 100644 index 00000000000..8de6b40d8f0 --- /dev/null +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.18; + +library DataStructures { + /** + * @notice Entry struct - Done as struct to easily support extensions if needed + * @param count - The occurrence of the entry in the dataset + * @param fee - The fee provided to sequencer for including in the inbox. 0 if Outbox (as not applicable). + */ + struct Entry { + uint64 count; + uint64 fee; + uint32 deadline; + } + + /** + * @notice Actor on L1. + * @param actor - The address of the actor + * @param chainId - The chainId of the actor + */ + struct L1Actor { + address actor; + uint256 chainId; + } + + /** + * @notice Actor on L2. + * @param actor - The aztec address of the actor + * @param version - Ahe Aztec instance the actor is on + */ + struct L2Actor { + bytes32 actor; + uint256 version; + } + + /** + * @notice Struct containing a message from L1 to L2 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + * @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2) + * @param deadline - The deadline to consume a message. Only after it, can a message be cancelled. + * @param fee - The fee provided to sequencer for including the entry + */ + struct L1ToL2Msg { + L1Actor sender; + L2Actor recipient; + bytes32 content; + bytes32 secretHash; + uint32 deadline; + uint64 fee; + } + + /** + * @notice Struct containing a message from L2 to L1 + * @param sender - The sender of the message + * @param recipient - The recipient of the message + * @param content - The content of the message (application specific) padded to bytes32 or hashed if larger. + */ + struct L2ToL1Msg { + DataStructures.L2Actor sender; + DataStructures.L1Actor recipient; + bytes32 content; + } + + /** + * @notice Struct for storing address of cross communication components + * @param rollup - The address of the rollup contract + * @param inbox - The address of the inbox contract + * @param outbox - The address of the outbox contract + */ + struct L1L2Addresses { + address rollup; + address inbox; + address outbox; + } +} diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index 0e5dc53aea9..3552a124aa6 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -2,7 +2,8 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {MessageBox} from "./MessageBox.sol"; /** @@ -26,7 +27,7 @@ contract Inbox is MessageBox, IInbox { * @param message - The L1 to L2 message * @return The hash of the message (used as the key of the entry in the set) */ - function computeMessageKey(L1ToL2Msg memory message) public pure returns (bytes32) { + function computeMessageKey(DataStructures.L1ToL2Msg memory message) public pure returns (bytes32) { return bytes32( uint256( sha256( @@ -54,15 +55,15 @@ contract Inbox is MessageBox, IInbox { * @return The key of the entry in the set */ function sendL2Message( - L2Actor memory _recipient, + DataStructures.L2Actor memory _recipient, uint32 _deadline, bytes32 _content, bytes32 _secretHash ) external payable returns (bytes32) { if (_deadline <= block.timestamp) revert Inbox__DeadlineBeforeNow(); uint64 fee = uint64(msg.value); - L1ToL2Msg memory message = L1ToL2Msg({ - sender: L1Actor(msg.sender, block.chainid), + DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({ + sender: DataStructures.L1Actor(msg.sender, block.chainid), recipient: _recipient, content: _content, secretHash: _secretHash, @@ -96,7 +97,7 @@ contract Inbox is MessageBox, IInbox { * @param _feeCollector - The address to receive the "fee" * @return entryKey - The key of the entry removed */ - function cancelL2Message(L1ToL2Msg memory _message, address _feeCollector) + function cancelL2Message(DataStructures.L1ToL2Msg memory _message, address _feeCollector) external returns (bytes32 entryKey) { @@ -119,7 +120,7 @@ contract Inbox is MessageBox, IInbox { uint256 totalFee = 0; for (uint256 i = 0; i < entryKeys.length; i++) { // TODO: Combine these to optimise for gas. - Entry memory entry = get(entryKeys[i]); + DataStructures.Entry memory entry = get(entryKeys[i]); if (entry.deadline > block.timestamp) revert Inbox__PastDeadline(); _consume(entryKeys[i]); totalFee += entry.fee; diff --git a/l1-contracts/src/core/messagebridge/MessageBox.sol b/l1-contracts/src/core/messagebridge/MessageBox.sol index 2f8df2da92d..6fd29c1ec65 100644 --- a/l1-contracts/src/core/messagebridge/MessageBox.sol +++ b/l1-contracts/src/core/messagebridge/MessageBox.sol @@ -2,8 +2,9 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {IRegistryReader} from "@aztec/interfaces/messagebridge/IRegistryReader.sol"; -import {IMessageBox} from "@aztec/interfaces/messagebridge/IMessageBox.sol"; +import {IRegistry} from "@aztec/core/interfaces/messagebridge/IRegistry.sol"; +import {IMessageBox} from "@aztec/core/interfaces/messagebridge/IMessageBox.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; /** * @title MessageBox @@ -27,30 +28,30 @@ abstract contract MessageBox is IMessageBox { uint256 internal constant P = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - IRegistryReader immutable REGISTRY; + IRegistry immutable REGISTRY; - mapping(bytes32 entryKey => Entry entry) internal entries; + mapping(bytes32 entryKey => DataStructures.Entry entry) internal entries; modifier onlyRollup() { - if (msg.sender != address(REGISTRY.getRollupAddress())) revert MessageBox__Unauthorized(); + if (msg.sender != address(REGISTRY.getRollup())) revert MessageBox__Unauthorized(); _; } constructor(address _registry) { - REGISTRY = IRegistryReader(_registry); + REGISTRY = IRegistry(_registry); } /** * @notice Inserts an entry into the multi-set * @param _entryKey - The key to insert * @param _fee - The fee provided to sequencer for including in the inbox. 0 if Oubox (as not applicable). - * @param _deadline - The deadline to consume a message. Only after it, can a message be cancalled. + * @param _deadline - The deadline to consume a message. Only after it, can a message be cancelled. */ function _insert(bytes32 _entryKey, uint64 _fee, uint32 _deadline) internal { // since entryKey is a hash of the message, _fee and `deadline` should always be the same // as such, there is no need to update these vars. Yet adding an if statement breaks // the slot packing and increases gas. So we leave it as it is. - Entry memory entry = entries[_entryKey]; + DataStructures.Entry memory entry = entries[_entryKey]; if ( (entry.fee != 0 && entry.fee != _fee) || (entry.deadline != 0 && entry.deadline != _deadline) ) { @@ -72,7 +73,7 @@ abstract contract MessageBox is IMessageBox { * @param _entryKey - The key to consume */ function _consume(bytes32 _entryKey) internal { - Entry storage entry = entries[_entryKey]; + DataStructures.Entry storage entry = entries[_entryKey]; if (entry.count == 0) revert MessageBox__NothingToConsume(_entryKey); entry.count--; } @@ -82,8 +83,8 @@ abstract contract MessageBox is IMessageBox { * @param _entryKey - The key to lookup * @return The entry matching the provided key */ - function get(bytes32 _entryKey) public view returns (Entry memory) { - Entry memory entry = entries[_entryKey]; + function get(bytes32 _entryKey) public view returns (DataStructures.Entry memory) { + DataStructures.Entry memory entry = entries[_entryKey]; if (entry.count == 0) revert MessageBox__NothingToConsume(_entryKey); return entry; } diff --git a/l1-contracts/src/core/messagebridge/Outbox.sol b/l1-contracts/src/core/messagebridge/Outbox.sol index cd807cf5b98..af68a98d588 100644 --- a/l1-contracts/src/core/messagebridge/Outbox.sol +++ b/l1-contracts/src/core/messagebridge/Outbox.sol @@ -2,8 +2,9 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; +import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {MessageBox} from "./MessageBox.sol"; -import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; /** * @title Outbox @@ -22,7 +23,7 @@ contract Outbox is MessageBox, IOutbox { * @param _message - The L2 to L1 message * @return The key of the entry in the set */ - function computeEntryKey(L2ToL1Msg memory _message) public pure returns (bytes32) { + function computeEntryKey(DataStructures.L2ToL1Msg memory _message) public pure returns (bytes32) { // TODO: Replace mod P later on when we have a better idea of how to handle Fields. return bytes32( uint256(sha256(abi.encode(_message.sender, _message.recipient, _message.content))) % P @@ -48,7 +49,7 @@ contract Outbox is MessageBox, IOutbox { * @param _message - The L2 to L1 message * @return entryKey - The key of the entry removed */ - function consume(L2ToL1Msg memory _message) external returns (bytes32 entryKey) { + function consume(DataStructures.L2ToL1Msg memory _message) external returns (bytes32 entryKey) { if (msg.sender != _message.recipient.actor) revert Outbox__Unauthorized(); if (block.chainid != _message.recipient.chainId) revert Outbox__WrongChainId(); diff --git a/l1-contracts/src/core/messagebridge/Registry.sol b/l1-contracts/src/core/messagebridge/Registry.sol index 90bb87c97f1..ed979c50398 100644 --- a/l1-contracts/src/core/messagebridge/Registry.sol +++ b/l1-contracts/src/core/messagebridge/Registry.sol @@ -2,39 +2,41 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; -import {IRegistryReader} from "@aztec/interfaces/messagebridge/IRegistryReader.sol"; -import {IRollup} from "@aztec/interfaces/IRollup.sol"; -import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; -import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; +import {IRegistry} from "@aztec/core/interfaces/messagebridge/IRegistry.sol"; +import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; + +import {DataStructures} from "../libraries/DataStructures.sol"; /** * @title Registry * @author Aztec Labs * @notice Keeps track of important information for L1<>L2 communication. */ -contract Registry is IRegistryReader { +contract Registry is IRegistry { // TODO(rahul) - https://github.com/AztecProtocol/aztec-packages/issues/526 // Need to create a snashot of addresses per version! - L1L2Addresses public addresses; + DataStructures.L1L2Addresses public addresses; function setAddresses(address _rollup, address _inbox, address _outbox) public { - addresses = L1L2Addresses(_rollup, _inbox, _outbox); + addresses = DataStructures.L1L2Addresses(_rollup, _inbox, _outbox); } - function getL1L2Addresses() external view override returns (L1L2Addresses memory) { + function getL1L2Addresses() external view override returns (DataStructures.L1L2Addresses memory) { return addresses; } - function getRollupAddress() external view override returns (IRollup) { + function getRollup() external view override returns (IRollup) { return IRollup(addresses.rollup); } - function getInboxAddress() external view override returns (IInbox) { + function getInbox() external view override returns (IInbox) { return IInbox(addresses.inbox); } - function getOutboxAddress() external view override returns (IOutbox) { + function getOutbox() external view override returns (IOutbox) { return IOutbox(addresses.outbox); } } diff --git a/l1-contracts/test/Inbox.t.sol b/l1-contracts/test/Inbox.t.sol index 001ca6bb94c..07f9011f77d 100644 --- a/l1-contracts/test/Inbox.t.sol +++ b/l1-contracts/test/Inbox.t.sol @@ -3,12 +3,14 @@ pragma solidity >=0.8.18; import {Test} from "forge-std/Test.sol"; -import {IInbox} from "@aztec/interfaces/messagebridge/IInbox.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; -import {IMessageBox} from "@aztec/interfaces/messagebridge/IMessageBox.sol"; +import {IMessageBox} from "@aztec/core/interfaces/messagebridge/IMessageBox.sol"; import {MessageBox} from "@aztec/core/messagebridge/MessageBox.sol"; import {Registry} from "@aztec/core/messagebridge/Registry.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; + contract InboxTest is Test { Inbox inbox; @@ -31,7 +33,11 @@ contract InboxTest is Test { registry.setAddresses(rollup, address(inbox), address(0x0)); } - function _helper_computeEntryKey(Inbox.L1ToL2Msg memory message) internal pure returns (bytes32) { + function _helper_computeEntryKey(DataStructures.L1ToL2Msg memory message) + internal + pure + returns (bytes32) + { return bytes32( uint256( sha256( @@ -48,10 +54,10 @@ contract InboxTest is Test { ); } - function _fakeMessage() internal view returns (Inbox.L1ToL2Msg memory) { - return IInbox.L1ToL2Msg({ - sender: IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}), - recipient: IMessageBox.L2Actor({ + function _fakeMessage() internal view returns (DataStructures.L1ToL2Msg memory) { + return DataStructures.L1ToL2Msg({ + sender: DataStructures.L1Actor({actor: address(this), chainId: block.chainid}), + recipient: DataStructures.L2Actor({ actor: 0x2000000000000000000000000000000000000000000000000000000000000000, version: 1 }), @@ -62,9 +68,9 @@ contract InboxTest is Test { }); } - function testFuzzSendL2Msg(Inbox.L1ToL2Msg memory message) public { + function testFuzzSendL2Msg(DataStructures.L1ToL2Msg memory message) public { // fix message.sender and deadline: - message.sender = IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}); + message.sender = DataStructures.L1Actor({actor: address(this), chainId: block.chainid}); if (message.deadline <= block.timestamp) { message.deadline = uint32(block.timestamp + 100); } @@ -92,7 +98,7 @@ contract InboxTest is Test { } function testSendMultipleSameL2Messages() public { - Inbox.L1ToL2Msg memory message = _fakeMessage(); + DataStructures.L1ToL2Msg memory message = _fakeMessage(); bytes32 entryKey1 = inbox.sendL2Message{value: message.fee}( message.recipient, message.deadline, message.content, message.secretHash ); @@ -111,7 +117,7 @@ contract InboxTest is Test { } function testRevertIfCancellingMessageFromDifferentAddress() public { - Inbox.L1ToL2Msg memory message = _fakeMessage(); + DataStructures.L1ToL2Msg memory message = _fakeMessage(); inbox.sendL2Message{value: message.fee}( message.recipient, message.deadline, message.content, message.secretHash ); @@ -121,7 +127,7 @@ contract InboxTest is Test { } function testRevertIfCancellingMessageWhenDeadlineHasntPassed() public { - Inbox.L1ToL2Msg memory message = _fakeMessage(); + DataStructures.L1ToL2Msg memory message = _fakeMessage(); inbox.sendL2Message{value: message.fee}( message.recipient, message.deadline, message.content, message.secretHash ); @@ -131,7 +137,7 @@ contract InboxTest is Test { } function testRevertIfCancellingNonExistentMessage() public { - Inbox.L1ToL2Msg memory message = _fakeMessage(); + DataStructures.L1ToL2Msg memory message = _fakeMessage(); bytes32 entryKey = _helper_computeEntryKey(message); vm.expectRevert( abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, entryKey) @@ -140,7 +146,7 @@ contract InboxTest is Test { } function testCancelMessage() public { - Inbox.L1ToL2Msg memory message = _fakeMessage(); + DataStructures.L1ToL2Msg memory message = _fakeMessage(); address feeCollector = address(0x1); bytes32 expectedEntryKey = inbox.sendL2Message{value: message.fee}( message.recipient, message.deadline, message.content, message.secretHash @@ -171,7 +177,7 @@ contract InboxTest is Test { } function testRevertIfOneKeyIsPastDeadlineWhenBatchConsuming() public { - Inbox.L1ToL2Msg memory message = _fakeMessage(); + DataStructures.L1ToL2Msg memory message = _fakeMessage(); bytes32 entryKey1 = inbox.sendL2Message{value: message.fee}( message.recipient, uint32(block.timestamp + 100), message.content, message.secretHash ); @@ -202,7 +208,7 @@ contract InboxTest is Test { inbox.batchConsume(entryKeys, address(0x1)); } - function testBatchConsume(Inbox.L1ToL2Msg[] memory messages) public { + function testBatchConsume(DataStructures.L1ToL2Msg[] memory messages) public { bytes32[] memory entryKeys = new bytes32[](messages.length); uint256 expectedTotalFee = 0; address feeCollector = address(0x1); @@ -210,9 +216,9 @@ contract InboxTest is Test { // insert messages: for (uint256 i = 0; i < messages.length; i++) { - Inbox.L1ToL2Msg memory message = messages[i]; + DataStructures.L1ToL2Msg memory message = messages[i]; // fix message.sender and deadline: - message.sender = IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}); + message.sender = DataStructures.L1Actor({actor: address(this), chainId: block.chainid}); if (message.deadline <= block.timestamp) { message.deadline = uint32(block.timestamp + 100); } diff --git a/l1-contracts/test/Outbox.t.sol b/l1-contracts/test/Outbox.t.sol index ee880fbb86f..a8c0455bbc2 100644 --- a/l1-contracts/test/Outbox.t.sol +++ b/l1-contracts/test/Outbox.t.sol @@ -3,12 +3,14 @@ pragma solidity >=0.8.18; import {Test} from "forge-std/Test.sol"; -import {IOutbox} from "@aztec/interfaces/messagebridge/IOutbox.sol"; +import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; -import {IMessageBox} from "@aztec/interfaces/messagebridge/IMessageBox.sol"; +import {IMessageBox} from "@aztec/core/interfaces/messagebridge/IMessageBox.sol"; import {MessageBox} from "@aztec/core/messagebridge/MessageBox.sol"; import {Registry} from "@aztec/core/messagebridge/Registry.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; + contract OutboxTest is Test { Outbox outbox; @@ -22,20 +24,24 @@ contract OutboxTest is Test { registry.setAddresses(rollup, address(0x0), address(outbox)); } - function _helper_computeEntryKey(Outbox.L2ToL1Msg memory message) internal pure returns (bytes32) { + function _helper_computeEntryKey(DataStructures.L2ToL1Msg memory message) + internal + pure + returns (bytes32) + { return bytes32( uint256(sha256(abi.encode(message.sender, message.recipient, message.content))) % 21888242871839275222246405745257275088548364400416034343698204186575808495617 ); } - function _fakeMessage() internal view returns (Outbox.L2ToL1Msg memory) { - return IOutbox.L2ToL1Msg({ - sender: IMessageBox.L2Actor({ + function _fakeMessage() internal view returns (DataStructures.L2ToL1Msg memory) { + return DataStructures.L2ToL1Msg({ + sender: DataStructures.L2Actor({ actor: 0x2000000000000000000000000000000000000000000000000000000000000000, version: 1 }), - recipient: IMessageBox.L1Actor({actor: address(this), chainId: block.chainid}), + recipient: DataStructures.L1Actor({actor: address(this), chainId: block.chainid}), content: 0x3000000000000000000000000000000000000000000000000000000000000000 }); } @@ -59,7 +65,7 @@ contract OutboxTest is Test { outbox.sendL1Messages(entryKeys); for (uint256 i = 0; i < entryKeys.length; i++) { bytes32 key = entryKeys[i]; - Outbox.Entry memory entry = outbox.get(key); + DataStructures.Entry memory entry = outbox.get(key); assertGt(entry.count, 0); assertEq(entry.fee, 0); assertEq(entry.deadline, 0); @@ -67,21 +73,21 @@ contract OutboxTest is Test { } function testRevertIfConsumingFromWrongRecipient() public { - Outbox.L2ToL1Msg memory message = _fakeMessage(); + DataStructures.L2ToL1Msg memory message = _fakeMessage(); message.recipient.actor = address(0x1); vm.expectRevert(Outbox.Outbox__Unauthorized.selector); outbox.consume(message); } function testRevertIfConsumingForWrongChain() public { - Outbox.L2ToL1Msg memory message = _fakeMessage(); + DataStructures.L2ToL1Msg memory message = _fakeMessage(); message.recipient.chainId = 2; vm.expectRevert(Outbox.Outbox__WrongChainId.selector); outbox.consume(message); } function testRevertIfConsumingMessageThatDoesntExist() public { - Outbox.L2ToL1Msg memory message = _fakeMessage(); + DataStructures.L2ToL1Msg memory message = _fakeMessage(); bytes32 entryKey = _helper_computeEntryKey(message); vm.expectRevert( abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, entryKey) @@ -91,7 +97,7 @@ contract OutboxTest is Test { function testConsume() public { // with fuzzing it takes way too long :() - Outbox.L2ToL1Msg memory message = _fakeMessage(); + DataStructures.L2ToL1Msg memory message = _fakeMessage(); bytes32 expectedEntryKey = _helper_computeEntryKey(message); bytes32[] memory entryKeys = new bytes32[](1); entryKeys[0] = expectedEntryKey;