diff --git a/src/bridge/AbsBridge.sol b/src/bridge/AbsBridge.sol index 40f2b45c..434a9ca4 100644 --- a/src/bridge/AbsBridge.sol +++ b/src/bridge/AbsBridge.sol @@ -20,6 +20,7 @@ import { import "./IBridge.sol"; import "./Messages.sol"; import "../libraries/DelegateCallAware.sol"; +import "./BridgePausable.sol"; import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol"; @@ -65,7 +66,7 @@ abstract contract AbsBridge is DelegateCallAware, IBridge, AbsBridgeStorage, - AccessControlUpgradeable + BridgePausable { using AddressUpgradeable for address; @@ -81,6 +82,10 @@ abstract contract AbsBridge is _; } + function postUpgradeInit() public onlyDelegated onlyProxyOwner { + _grantAllPauseRolesTo(rollup.owner()); + } + /// @notice Allows the rollup owner to set another rollup address function updateRollupAddress(IOwnable _rollup) external onlyRollupOrOwner { rollup = _rollup; @@ -119,12 +124,8 @@ abstract contract AbsBridge is ) external onlySequencerInbox - returns ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 acc - ) + whenSequencerInboxMsgsNotPaused + returns (uint256 seqMessageIndex, bytes32 beforeAcc, bytes32 delayedAcc, bytes32 acc) { if ( sequencerReportedSubMessageCount != prevMessageCount && @@ -146,11 +147,11 @@ abstract contract AbsBridge is } /// @inheritdoc IBridge - function submitBatchSpendingReport(address sender, bytes32 messageDataHash) - external - onlySequencerInbox - returns (uint256) - { + + function submitBatchSpendingReport( + address sender, + bytes32 messageDataHash + ) external onlySequencerInbox whenDelayedMessageEnqueueNotPaused returns (uint256) { return addMessageToDelayedAccumulator( L1MessageType_batchPostingReport, @@ -225,7 +226,7 @@ abstract contract AbsBridge is address to, uint256 value, bytes calldata data - ) external returns (bool success, bytes memory returnData) { + ) external whenOutboxExecutionNotPaused returns (bool success, bytes memory returnData) { if (!allowedOutboxes(msg.sender)) revert NotOutbox(msg.sender); if (data.length > 0 && !to.isContract()) revert NotContract(to); address prevOutbox = _activeOutbox; diff --git a/src/bridge/AbsInbox.sol b/src/bridge/AbsInbox.sol index 35256220..efb858fd 100644 --- a/src/bridge/AbsInbox.sol +++ b/src/bridge/AbsInbox.sol @@ -13,7 +13,8 @@ import { NotAllowedOrigin, NotOrigin, NotRollupOrOwner, - RetryableData + RetryableData, + Deprecated } from "../libraries/Error.sol"; import "./IInboxBase.sol"; import "./ISequencerInbox.sol"; @@ -110,13 +111,13 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase } /// @inheritdoc IInboxBase - function pause() external onlyRollupOrOwner { - _pause(); + function pause() external { + revert Deprecated(); } /// @inheritdoc IInboxBase - function unpause() external onlyRollupOrOwner { - _unpause(); + function unpause() external { + revert Deprecated(); } /* solhint-disable func-name-mixedcase */ @@ -133,7 +134,6 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase /// @inheritdoc IInboxBase function sendL2MessageFromOrigin(bytes calldata messageData) external - whenNotPaused onlyAllowed returns (uint256) { @@ -149,7 +149,6 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase /// @inheritdoc IInboxBase function sendL2Message(bytes calldata messageData) external - whenNotPaused onlyAllowed returns (uint256) { @@ -165,7 +164,7 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase address to, uint256 value, bytes calldata data - ) external whenNotPaused onlyAllowed returns (uint256) { + ) external onlyAllowed returns (uint256) { // arbos will discard unsigned tx with gas limit too large if (gasLimit > type(uint64).max) { revert GasLimitTooLarge(); @@ -194,7 +193,7 @@ abstract contract AbsInbox is DelegateCallAware, PausableUpgradeable, IInboxBase address to, uint256 value, bytes calldata data - ) external whenNotPaused onlyAllowed returns (uint256) { + ) external onlyAllowed returns (uint256) { // arbos will discard unsigned tx with gas limit too large if (gasLimit > type(uint64).max) { revert GasLimitTooLarge(); diff --git a/src/bridge/Bridge.sol b/src/bridge/Bridge.sol index d46fa4ed..2a240518 100644 --- a/src/bridge/Bridge.sol +++ b/src/bridge/Bridge.sol @@ -24,6 +24,7 @@ contract Bridge is AbsBridge, IEthBridge { function initialize(IOwnable rollup_) external initializer onlyDelegated { _activeOutbox = EMPTY_ACTIVEOUTBOX; rollup = rollup_; + _grantAllPauseRolesTo(rollup_.owner()); } /// @inheritdoc IEthBridge @@ -31,7 +32,7 @@ contract Bridge is AbsBridge, IEthBridge { uint8 kind, address sender, bytes32 messageDataHash - ) external payable returns (uint256) { + ) external payable whenDelayedMessageEnqueueNotPaused returns (uint256) { return _enqueueDelayedMessage(kind, sender, messageDataHash, msg.value); } diff --git a/src/bridge/BridgePausable.sol b/src/bridge/BridgePausable.sol new file mode 100644 index 00000000..a6e73b25 --- /dev/null +++ b/src/bridge/BridgePausable.sol @@ -0,0 +1,133 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE +// SPDX-License-Identifier: BUSL-1.1 +import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import { + OutboxExecutionPaused, + OutboxExecutionNotPaused, + SequencerInboxMsgsPaused, + SequencerInboxMsgsNotPaused, + DelayedMessagesEnqueuePaused, + DelayedMessagesEnqueueNotPaused +} from "../libraries/Error.sol"; +pragma solidity ^0.8.4; + +contract BridgePausable is AccessControlUpgradeable { + bool internal _outboxExecutionPaused; + bool internal _sequencerInboxMsgsPaused; + bool internal _delayedMessageEnqueuePaused; + + bytes32 public constant PAUSE_OUTBOX_EXECUTION_ROLE = keccak256("PAUSE_OUTBOX_EXECUTION_ROLE"); + bytes32 public constant UNPAUSE_OUTBOX_EXECUTION_ROLE = + keccak256("UNPAUSE_OUTBOX_EXECUTION_ROLE"); + + bytes32 public constant PAUSE_SEQUENCER_INBOX_MSGS_ROLE = + keccak256("PAUSE_SEQUENCER_INBOX_MSGS_ROLE"); + bytes32 public constant UNPAUSE_SEQUENCER_INBOX_MSGS_ROLE = + keccak256("UNPAUSE_SEQUENCER_INBOX_MSGS_ROLE"); + + bytes32 public constant PAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE = + keccak256("PAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE"); + bytes32 public constant UNPAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE = + keccak256("UNPAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE"); + + modifier whenOutboxExecutionNotPaused() { + if (_outboxExecutionPaused) { + revert OutboxExecutionPaused(); + } + _; + } + + modifier whenOutboxExecutionPaused() { + if (!_outboxExecutionPaused) { + revert OutboxExecutionNotPaused(); + } + _; + } + + modifier whenSequencerInboxMsgsNotPaused() { + if (_sequencerInboxMsgsPaused) { + revert SequencerInboxMsgsPaused(); + } + _; + } + + modifier whenSequencerInboxMsgsPaused() { + if (!_sequencerInboxMsgsPaused) { + revert SequencerInboxMsgsNotPaused(); + } + _; + } + + modifier whenDelayedMessageEnqueueNotPaused() { + if (_delayedMessageEnqueuePaused) { + revert DelayedMessagesEnqueuePaused(); + } + _; + } + + modifier whenDelayedMessageEnqueuePaused() { + if (!_delayedMessageEnqueuePaused) { + revert DelayedMessagesEnqueueNotPaused(); + } + _; + } + + function _grantAllPauseRolesTo(address owner) internal { + _grantRole(DEFAULT_ADMIN_ROLE, owner); + _grantRole(PAUSE_OUTBOX_EXECUTION_ROLE, owner); + _grantRole(UNPAUSE_OUTBOX_EXECUTION_ROLE, owner); + _grantRole(PAUSE_SEQUENCER_INBOX_MSGS_ROLE, owner); + _grantRole(UNPAUSE_SEQUENCER_INBOX_MSGS_ROLE, owner); + _grantRole(PAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE, owner); + _grantRole(UNPAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE, owner); + } + + function pauseOutboxExecution() + external + onlyRole(PAUSE_OUTBOX_EXECUTION_ROLE) + whenOutboxExecutionNotPaused + { + _outboxExecutionPaused = true; + } + + function unpauseOutboxExecution() + external + onlyRole(UNPAUSE_OUTBOX_EXECUTION_ROLE) + whenOutboxExecutionPaused + { + _outboxExecutionPaused = false; + } + + function pauseSequencerInboxMsgs() + external + onlyRole(PAUSE_SEQUENCER_INBOX_MSGS_ROLE) + whenSequencerInboxMsgsNotPaused + { + _sequencerInboxMsgsPaused = true; + } + + function unpauseSequencerInboxMsgs() + external + onlyRole(UNPAUSE_SEQUENCER_INBOX_MSGS_ROLE) + whenSequencerInboxMsgsPaused + { + _sequencerInboxMsgsPaused = false; + } + + function pauseDelayedMsgsEnque() + external + onlyRole(PAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE) + whenDelayedMessageEnqueueNotPaused + { + _delayedMessageEnqueuePaused = true; + } + + function unpauseDelayedMsgsEnque() + external + onlyRole(UNPAUSE_DELAYED_MESSAGE_ENQUEUE_ROLE) + whenDelayedMessageEnqueuePaused + { + _delayedMessageEnqueuePaused = false; + } +} diff --git a/src/bridge/ERC20Bridge.sol b/src/bridge/ERC20Bridge.sol index cb62d5bb..98f8e3c3 100644 --- a/src/bridge/ERC20Bridge.sol +++ b/src/bridge/ERC20Bridge.sol @@ -30,6 +30,7 @@ contract ERC20Bridge is AbsBridge, IERC20Bridge { nativeToken = nativeToken_; _activeOutbox = EMPTY_ACTIVEOUTBOX; rollup = rollup_; + _grantAllPauseRolesTo(rollup_.owner()); } /// @inheritdoc IERC20Bridge @@ -38,7 +39,7 @@ contract ERC20Bridge is AbsBridge, IERC20Bridge { address sender, bytes32 messageDataHash, uint256 tokenFeeAmount - ) external returns (uint256) { + ) external whenDelayedMessageEnqueueNotPaused returns (uint256) { return _enqueueDelayedMessage(kind, sender, messageDataHash, tokenFeeAmount); } diff --git a/src/bridge/ERC20Inbox.sol b/src/bridge/ERC20Inbox.sol index ddcfff15..92ad071f 100644 --- a/src/bridge/ERC20Inbox.sol +++ b/src/bridge/ERC20Inbox.sol @@ -37,7 +37,7 @@ contract ERC20Inbox is AbsInbox, IERC20Inbox { } /// @inheritdoc IERC20Inbox - function depositERC20(uint256 amount) public whenNotPaused onlyAllowed returns (uint256) { + function depositERC20(uint256 amount) public onlyAllowed returns (uint256) { address dest = msg.sender; // solhint-disable-next-line avoid-tx-origin @@ -66,7 +66,7 @@ contract ERC20Inbox is AbsInbox, IERC20Inbox { uint256 maxFeePerGas, uint256 tokenTotalFeeAmount, bytes calldata data - ) external whenNotPaused onlyAllowed returns (uint256) { + ) external onlyAllowed returns (uint256) { return _createRetryableTicket( to, @@ -92,7 +92,7 @@ contract ERC20Inbox is AbsInbox, IERC20Inbox { uint256 maxFeePerGas, uint256 tokenTotalFeeAmount, bytes calldata data - ) public whenNotPaused onlyAllowed returns (uint256) { + ) public onlyAllowed returns (uint256) { return _unsafeCreateRetryableTicket( to, diff --git a/src/bridge/IInboxBase.sol b/src/bridge/IInboxBase.sol index c9775a36..c4e9722f 100644 --- a/src/bridge/IInboxBase.sol +++ b/src/bridge/IInboxBase.sol @@ -61,10 +61,10 @@ interface IInboxBase is IDelayedMessageProvider { // ---------- onlyRollupOrOwner functions ---------- - /// @notice pauses all inbox functionality + /// @notice deprecated in favor of pausing on Bridge function pause() external; - /// @notice unpauses all inbox functionality + /// @notice deprecated in favor of pausing on Bridge function unpause() external; /// @notice add or remove users from allowList diff --git a/src/bridge/Inbox.sol b/src/bridge/Inbox.sol index f080e76e..ba38aec1 100644 --- a/src/bridge/Inbox.sol +++ b/src/bridge/Inbox.sol @@ -58,7 +58,7 @@ contract Inbox is AbsInbox, IInbox { uint256 nonce, address to, bytes calldata data - ) external payable whenNotPaused onlyAllowed returns (uint256) { + ) external payable onlyAllowed returns (uint256) { // arbos will discard unsigned tx with gas limit too large if (gasLimit > type(uint64).max) { revert GasLimitTooLarge(); @@ -86,7 +86,7 @@ contract Inbox is AbsInbox, IInbox { uint256 maxFeePerGas, address to, bytes calldata data - ) external payable whenNotPaused onlyAllowed returns (uint256) { + ) external payable onlyAllowed returns (uint256) { // arbos will discard unsigned tx with gas limit too large if (gasLimit > type(uint64).max) { revert GasLimitTooLarge(); @@ -114,7 +114,7 @@ contract Inbox is AbsInbox, IInbox { uint256 nonce, address to, bytes calldata data - ) external payable whenNotPaused onlyAllowed returns (uint256) { + ) external payable onlyAllowed returns (uint256) { if (!_chainIdChanged()) revert NotForked(); // solhint-disable-next-line avoid-tx-origin if (msg.sender != tx.origin) revert NotOrigin(); @@ -148,7 +148,7 @@ contract Inbox is AbsInbox, IInbox { address to, uint256 value, bytes calldata data - ) external whenNotPaused onlyAllowed returns (uint256) { + ) external onlyAllowed returns (uint256) { if (!_chainIdChanged()) revert NotForked(); // solhint-disable-next-line avoid-tx-origin if (msg.sender != tx.origin) revert NotOrigin(); @@ -181,7 +181,7 @@ contract Inbox is AbsInbox, IInbox { uint256 nonce, uint256 value, address withdrawTo - ) external whenNotPaused onlyAllowed returns (uint256) { + ) external onlyAllowed returns (uint256) { if (!_chainIdChanged()) revert NotForked(); // solhint-disable-next-line avoid-tx-origin if (msg.sender != tx.origin) revert NotOrigin(); @@ -208,7 +208,7 @@ contract Inbox is AbsInbox, IInbox { } /// @inheritdoc IInbox - function depositEth() public payable whenNotPaused onlyAllowed returns (uint256) { + function depositEth() public payable onlyAllowed returns (uint256) { address dest = msg.sender; // solhint-disable-next-line avoid-tx-origin @@ -227,7 +227,7 @@ contract Inbox is AbsInbox, IInbox { } /// @notice deprecated in favour of depositEth with no parameters - function depositEth(uint256) external payable whenNotPaused onlyAllowed returns (uint256) { + function depositEth(uint256) external payable onlyAllowed returns (uint256) { return depositEth(); } @@ -254,7 +254,7 @@ contract Inbox is AbsInbox, IInbox { uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data - ) external payable whenNotPaused onlyAllowed returns (uint256) { + ) external payable onlyAllowed returns (uint256) { // gas limit is validated to be within uint64 in unsafeCreateRetryableTicket return unsafeCreateRetryableTicket( @@ -279,7 +279,7 @@ contract Inbox is AbsInbox, IInbox { uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data - ) external payable whenNotPaused onlyAllowed returns (uint256) { + ) external payable onlyAllowed returns (uint256) { return _createRetryableTicket( to, @@ -304,7 +304,7 @@ contract Inbox is AbsInbox, IInbox { uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data - ) public payable whenNotPaused onlyAllowed returns (uint256) { + ) public payable onlyAllowed returns (uint256) { return _unsafeCreateRetryableTicket( to, diff --git a/src/libraries/Error.sol b/src/libraries/Error.sol index fd6f3255..8e5eede1 100644 --- a/src/libraries/Error.sol +++ b/src/libraries/Error.sol @@ -178,3 +178,14 @@ error NoSuchKeyset(bytes32); /// @dev Thrown when rollup is not updated with updateRollupAddress error RollupNotChanged(); + +error OutboxExecutionPaused(); +error OutboxExecutionNotPaused(); + +error SequencerInboxMsgsPaused(); +error SequencerInboxMsgsNotPaused(); + +error DelayedMessagesEnqueuePaused(); +error DelayedMessagesEnqueueNotPaused(); + +error Deprecated(); \ No newline at end of file