@@ -23,6 +23,11 @@ contract PauseController is OwnableUpgradeable {
2323 /// @param component The component that is unpaused.
2424 event Unpause (address indexed component );
2525
26+ /// @notice Emitted when a component's pause time is extended.
27+ /// @param component The component that is paused.
28+ /// @param timestamp The new pause expiry timestamp.
29+ event SetPauseExpiry (address indexed component , uint256 timestamp );
30+
2631 /// @notice Emitted when the pause cooldown period of a component is reset.
2732 /// @param component The component that has its pause cooldown period reset.
2833 event ResetPauseCooldownPeriod (address indexed component );
@@ -51,13 +56,19 @@ contract PauseController is OwnableUpgradeable {
5156 /// @dev Thrown when the execution of `ScrollOwner` contract fails.
5257 error ErrorExecuteUnpauseFailed ();
5358
59+ /// @dev Thrown when the provided pause expiry timestamp is invalid.
60+ error ErrorInvalidPauseExpiry ();
61+
5462 /*************
5563 * Constants *
5664 *************/
5765
5866 /// @notice The role for pause controller in `ScrollOwner` contract.
5967 bytes32 public constant PAUSE_CONTROLLER_ROLE = keccak256 ("PAUSE_CONTROLLER_ROLE " );
6068
69+ /// @notice The default pause expiry duration, after which anyone can unpause the component.
70+ uint256 public constant DEFAULT_PAUSE_EXPIRY = 7 days ;
71+
6172 /***********************
6273 * Immutable Variables *
6374 ***********************/
@@ -75,6 +86,9 @@ contract PauseController is OwnableUpgradeable {
7586 /// @notice The last unpause time of each component.
7687 mapping (address => uint256 ) private lastUnpauseTime;
7788
89+ /// @notice The last unpause time of each component.
90+ mapping (address => uint256 ) private pauseExpiry;
91+
7892 /***************
7993 * Constructor *
8094 ***************/
@@ -128,12 +142,21 @@ contract PauseController is OwnableUpgradeable {
128142 revert ErrorExecutePauseFailed ();
129143 }
130144
145+ uint256 timestamp = block .timestamp + DEFAULT_PAUSE_EXPIRY;
146+ pauseExpiry[address (component)] = timestamp;
147+
131148 emit Pause (address (component));
149+ emit SetPauseExpiry (address (component), timestamp);
132150 }
133151
134152 /// @notice Unpause a component.
135153 /// @param component The component to unpause.
136- function unpause (IPausable component ) external onlyOwner {
154+ function unpause (IPausable component ) external {
155+ // Skip owner check after the pause expiry time
156+ if (pauseExpiry[address (component)] == 0 || pauseExpiry[address (component)] > block .timestamp ) {
157+ _checkOwner ();
158+ }
159+
137160 if (! component.paused ()) {
138161 revert ErrorComponentNotPaused ();
139162 }
@@ -145,12 +168,13 @@ contract PauseController is OwnableUpgradeable {
145168 PAUSE_CONTROLLER_ROLE
146169 );
147170
148- lastUnpauseTime[address (component)] = block .timestamp ;
149-
150171 if (component.paused ()) {
151172 revert ErrorExecuteUnpauseFailed ();
152173 }
153174
175+ lastUnpauseTime[address (component)] = block .timestamp ;
176+ pauseExpiry[address (component)] = 0 ;
177+
154178 emit Unpause (address (component));
155179 }
156180
@@ -168,6 +192,36 @@ contract PauseController is OwnableUpgradeable {
168192 _updatePauseCooldownPeriod (newPauseCooldownPeriod);
169193 }
170194
195+ /// @notice Extend the pause expiry time of a component.
196+ /// @param component The component to pause.
197+ /// @param newTimestamp The new pause expiry timestamp.
198+ function extendPause (IPausable component , uint256 newTimestamp ) external onlyOwner {
199+ if (newTimestamp <= block .timestamp || newTimestamp <= pauseExpiry[address (component)]) {
200+ revert ErrorInvalidPauseExpiry ();
201+ }
202+
203+ // Re-pause if needed, in case there is a race between signing the
204+ // extendPause transaction and the permissionless unpause.
205+ if (! component.paused ()) {
206+ ScrollOwner (payable (SCROLL_OWNER)).execute (
207+ address (component),
208+ 0 ,
209+ abi.encodeWithSelector (IPausable.setPause.selector , true ),
210+ PAUSE_CONTROLLER_ROLE
211+ );
212+
213+ emit Pause (address (component));
214+ }
215+
216+ if (! component.paused ()) {
217+ revert ErrorComponentNotPaused ();
218+ }
219+
220+ pauseExpiry[address (component)] = newTimestamp;
221+
222+ emit SetPauseExpiry (address (component), newTimestamp);
223+ }
224+
171225 /**********************
172226 * Internal Functions *
173227 **********************/
0 commit comments