-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRollbackManager.sol
More file actions
495 lines (401 loc) · 18.5 KB
/
RollbackManager.sol
File metadata and controls
495 lines (401 loc) · 18.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
// External Libraries
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
// Internal Libraries
import {IRollbackManager, Rollback} from "interfaces/IRollbackManager.sol";
import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol";
/// @title Rollback Manager Core
/// @author [ScopeLift](https://scopelift.co)
/// @notice Manages the lifecycle of rollback proposals, allowing the admin to propose, and the guardian to queue,
/// cancel, or execute rollback transactions.
/// @dev This contract coordinates a multi-phase rollback process:
/// - Admin proposes rollback transactions.
/// - Guardian queues them for execution within a specified window
/// - Guardian later executes or cancels the queued rollback.
/// - On queuing / cancelling / executing, the transactions are sent to TARGET_TIMELOCK.
abstract contract RollbackManager is IRollbackManager, ReentrancyGuard {
/*///////////////////////////////////////////////////////////////
Errors
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when an unauthorized caller attempts perform an action.
error RollbackManager__Unauthorized();
/// @notice Thrown when an invalid address is provided.
error RollbackManager__InvalidAddress();
/// @notice Thrown when a rollback does not exist.
error RollbackManager__NonExistentRollback(uint256 rollbackId);
/// @notice Thrown when an invalid rollback queueable duration is provided.
error RollbackManager__InvalidRollbackQueueableDuration();
/// @notice Thrown when the lengths of the parameters do not match.
error RollbackManager__MismatchedParameters();
/// @notice Thrown when a rollback is not proposed or already queued.
error RollbackManager__NotQueueable(uint256 rollbackId);
/// @notice Thrown when a rollback is not queued for execution.
error RollbackManager__NotQueued(uint256 rollbackId);
/// @notice Thrown when a rollback queue has expired.
error RollbackManager__Expired(uint256 rollbackId);
/// @notice Thrown when a rollback's execution time has not yet arrived.
error RollbackManager__ExecutionTooEarly(uint256 rollbackId);
/// @notice Thrown when a rollback already exists.
error RollbackManager__AlreadyExists(uint256 rollbackId);
/*///////////////////////////////////////////////////////////////
Events
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when a rollback is proposed.
/// @param rollbackId The ID of the rollback.
/// @param expiresAt Timestamp before which the rollback must be queued for execution.
/// @param targets The targets of the transactions.
/// @param values The values of the transactions.
/// @param calldatas The calldatas of the transactions.
/// @param description The description of the rollback.
event RollbackProposed(
uint256 indexed rollbackId,
uint256 expiresAt,
address[] targets,
uint256[] values,
bytes[] calldatas,
string description
);
/// @notice Emitted when a rollback is queued.
/// @param rollbackId The ID of the rollback.
/// @param eta Timestamp after which the rollback can be executed.
event RollbackQueued(uint256 indexed rollbackId, uint256 eta);
/// @notice Emitted when a rollback is canceled.
/// @param rollbackId The ID of the rollback.
event RollbackCanceled(uint256 indexed rollbackId);
/// @notice Emitted when a rollback is executed.
/// @param rollbackId The ID of the rollback.
event RollbackExecuted(uint256 indexed rollbackId);
/// @notice Emitted when a new guardian is set.
/// @param oldGuardian The old guardian.
/// @param newGuardian The new guardian.
event GuardianSet(address indexed oldGuardian, address indexed newGuardian);
/// @notice Emitted when the rollback queueable duration is set.
/// @param oldRollbackQueueableDuration The old rollback queueable duration.
/// @param newRollbackQueueableDuration The new rollback queueable duration.
event RollbackQueueableDurationSet(uint256 oldRollbackQueueableDuration, uint256 newRollbackQueueableDuration);
/// @notice Emitted when the admin is set.
/// @param oldAdmin The old admin.
/// @param newAdmin The new admin.
event AdminSet(address indexed oldAdmin, address indexed newAdmin);
/*///////////////////////////////////////////////////////////////
State Variables
//////////////////////////////////////////////////////////////*/
/// @notice Target for timelocked execution of rollback transactions.
address public immutable TARGET_TIMELOCK;
/// @notice The lower bound enforced for the rollbackQueueableDuration setting.
uint256 public immutable MIN_ROLLBACK_QUEUEABLE_DURATION;
/// @notice Address that manages this contract.
address public admin;
/// @notice Address that can execute rollback transactions.
address public guardian;
/// @notice The duration after a rollback is proposed during which it remains eligible to be queued for execution (in
/// seconds).
uint256 public rollbackQueueableDuration;
/// @notice Rollback id to rollback data.
mapping(uint256 rollbackId => Rollback rollbackData) internal rollbacks;
/*///////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/
/// @notice Initializes the RollbackManager.
/// @param _targetTimelock The target for timelocked execution of rollback transactions.
/// @param _admin The address that manages this contract.
/// @param _guardian The address that can execute rollback transactions.
/// @param _rollbackQueueableDuration The duration within which a proposed rollback remains eligible to be queued for
/// execution (in seconds).
/// @param _minRollbackQueueableDuration The lower bound enforced for the rollbackQueueableDuration setting (in
/// seconds).
constructor(
address _targetTimelock,
address _admin,
address _guardian,
uint256 _rollbackQueueableDuration,
uint256 _minRollbackQueueableDuration
) {
if (_minRollbackQueueableDuration == 0) {
revert RollbackManager__InvalidRollbackQueueableDuration();
}
if (_targetTimelock == address(0)) {
revert RollbackManager__InvalidAddress();
}
TARGET_TIMELOCK = _targetTimelock;
MIN_ROLLBACK_QUEUEABLE_DURATION = _minRollbackQueueableDuration;
_setAdmin(_admin);
_setRollbackQueueableDuration(_rollbackQueueableDuration);
_setGuardian(_guardian);
}
/*///////////////////////////////////////////////////////////////
External Functions
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IRollbackManager
function getRollback(uint256 _rollbackId) external view returns (Rollback memory) {
return rollbacks[_rollbackId];
}
/// @inheritdoc IRollbackManager
function propose(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) external returns (uint256 _rollbackId) {
_revertIfNotAdmin();
_revertIfMismatchedParameters(_targets, _values, _calldatas);
_rollbackId = getRollbackId(_targets, _values, _calldatas, _description);
Rollback storage rollback = rollbacks[_rollbackId];
// Revert if the rollback already exists.
if (rollback.queueExpiresAt != 0) {
revert RollbackManager__AlreadyExists(_rollbackId);
}
// Set the time before which the rollback can be queued for execution.
uint256 _expiresAt = block.timestamp + rollbackQueueableDuration;
rollback.queueExpiresAt = SafeCast.toUint48(_expiresAt);
emit RollbackProposed(_rollbackId, _expiresAt, _targets, _values, _calldatas, _description);
}
/// @inheritdoc IRollbackManager
function queue(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) external nonReentrant returns (uint256 _rollbackId) {
_revertIfNotGuardian();
_revertIfMismatchedParameters(_targets, _values, _calldatas);
_rollbackId = getRollbackId(_targets, _values, _calldatas, _description);
Rollback storage rollback = rollbacks[_rollbackId];
IGovernor.ProposalState _state = _getState(_rollbackId);
// Revert if the rollback is not pending.
if (_state != IGovernor.ProposalState.Pending) {
// Custom revert if the rollback queue has expired.
if (_state == IGovernor.ProposalState.Expired) {
revert RollbackManager__Expired(_rollbackId);
}
revert RollbackManager__NotQueueable(_rollbackId);
}
// Set the time after which the queued rollback can be executed.
uint256 _eta = block.timestamp + _delay();
rollback.executableAt = SafeCast.toUint48(_eta);
// Queue the rollback to the timelock target.
_queue(_targets, _values, _calldatas, _description);
emit RollbackQueued(_rollbackId, _eta);
}
/// @inheritdoc IRollbackManager
function cancel(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) external nonReentrant returns (uint256 _rollbackId) {
_revertIfNotGuardian();
_revertIfMismatchedParameters(_targets, _values, _calldatas);
_rollbackId = getRollbackId(_targets, _values, _calldatas, _description);
Rollback storage rollback = rollbacks[_rollbackId];
IGovernor.ProposalState _state = _getState(_rollbackId);
// Revert if the rollback has not been queued.
if (_state != IGovernor.ProposalState.Queued) {
revert RollbackManager__NotQueued(_rollbackId);
}
rollback.canceled = true;
// Cancel the rollback transactions on the timelock target.
_cancel(_targets, _values, _calldatas, _description);
emit RollbackCanceled(_rollbackId);
}
/// @inheritdoc IRollbackManager
function execute(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) external payable nonReentrant returns (uint256 _rollbackId) {
_revertIfNotGuardian();
_revertIfMismatchedParameters(_targets, _values, _calldatas);
_rollbackId = getRollbackId(_targets, _values, _calldatas, _description);
Rollback storage rollback = rollbacks[_rollbackId];
IGovernor.ProposalState _state = _getState(_rollbackId);
// Revert if the rollback is not queued.
if (_state != IGovernor.ProposalState.Queued) {
revert RollbackManager__NotQueued(_rollbackId);
}
// Revert if the rollback's execution time has not arrived.
if (block.timestamp < rollback.executableAt) {
revert RollbackManager__ExecutionTooEarly(_rollbackId);
}
rollback.executed = true;
// Execute the rollback on the timelock target.
_execute(_targets, _values, _calldatas, _description);
emit RollbackExecuted(_rollbackId);
}
/// @inheritdoc IRollbackManager
function setGuardian(address _newGuardian) external {
_revertIfNotAdmin();
_setGuardian(_newGuardian);
}
/// @inheritdoc IRollbackManager
function setRollbackQueueableDuration(uint256 _newRollbackQueueableDuration) external {
_revertIfNotAdmin();
_setRollbackQueueableDuration(_newRollbackQueueableDuration);
}
/// @inheritdoc IRollbackManager
function setAdmin(address _newAdmin) external {
_revertIfNotAdmin();
_setAdmin(_newAdmin);
}
/// @inheritdoc IRollbackManager
/// @dev Refer to {_getState} for details on the possible rollback states.
function state(uint256 _rollbackId) external view returns (IGovernor.ProposalState) {
return _getState(_rollbackId);
}
/// @inheritdoc IRollbackManager
/// @dev Refer to {_getState} for details on the possible rollback states.
function isRollbackExecutable(uint256 _rollbackId) external view returns (bool) {
if (_getState(_rollbackId) != IGovernor.ProposalState.Queued) {
return false;
}
return block.timestamp >= rollbacks[_rollbackId].executableAt;
}
/*///////////////////////////////////////////////////////////////
Public Functions
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IRollbackManager
function getRollbackId(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) public view virtual returns (uint256);
/*///////////////////////////////////////////////////////////////
Internal Functions
//////////////////////////////////////////////////////////////*/
/// @notice Reverts if the caller is not the admin.
function _revertIfNotAdmin() internal view {
if (msg.sender != admin) {
revert RollbackManager__Unauthorized();
}
}
/// @notice Reverts if the caller is not the guardian.
function _revertIfNotGuardian() internal view {
if (msg.sender != guardian) {
revert RollbackManager__Unauthorized();
}
}
/// @notice Reverts if the lengths of the parameters do not match.
/// @param _targets The targets of the transactions.
/// @param _values The values of the transactions.
/// @param _calldatas The calldatas of the transactions.
function _revertIfMismatchedParameters(address[] memory _targets, uint256[] memory _values, bytes[] memory _calldatas)
internal
pure
{
if (_targets.length != _values.length || _targets.length != _calldatas.length) {
revert RollbackManager__MismatchedParameters();
}
}
/// @notice Utility function to set the guardian.
/// @param _newGuardian The new guardian.
function _setGuardian(address _newGuardian) internal {
if (_newGuardian == address(0)) {
revert RollbackManager__InvalidAddress();
}
emit GuardianSet(guardian, _newGuardian);
guardian = _newGuardian;
}
/// @notice Utility function to set the rollback queueable duration.
/// @param _newRollbackQueueableDuration The new rollback queueable duration (in seconds).
function _setRollbackQueueableDuration(uint256 _newRollbackQueueableDuration) internal {
if (_newRollbackQueueableDuration < MIN_ROLLBACK_QUEUEABLE_DURATION) {
revert RollbackManager__InvalidRollbackQueueableDuration();
}
emit RollbackQueueableDurationSet(rollbackQueueableDuration, _newRollbackQueueableDuration);
rollbackQueueableDuration = _newRollbackQueueableDuration;
}
/// @notice Utility function to set the admin.
/// @param _newAdmin The new admin.
function _setAdmin(address _newAdmin) internal {
if (_newAdmin == address(0)) {
revert RollbackManager__InvalidAddress();
}
emit AdminSet(admin, _newAdmin);
admin = _newAdmin;
}
/// @notice Returns the current state of a proposed rollback.
/// @param _rollbackId The rollback ID.
/// @return The current IGovernor.ProposalState of the rollback, which can be:
/// - `Pending`: proposed but not yet queued and not expired.
/// - `Expired`: proposed but not queued before expiration.
/// - `Queued`: queued for execution, but delay not elapsed.
/// - `Executed`: rollback has already been executed (terminal state).
/// - `Canceled`: rollback was canceled before execution (terminal state).
/// @dev This function determines the rollback's lifecycle state based on timestamps and flags.
function _getState(uint256 _rollbackId) internal view returns (IGovernor.ProposalState) {
Rollback memory _rollback = rollbacks[_rollbackId];
// Revert if the rollback was not proposed (i.e., does not exist).
if (_rollback.queueExpiresAt == 0) {
revert RollbackManager__NonExistentRollback(_rollbackId);
}
// Check if the rollback has been executed.
if (_rollback.executed) {
return IGovernor.ProposalState.Executed;
}
// Check if the rollback has been canceled.
if (_rollback.canceled) {
return IGovernor.ProposalState.Canceled;
}
// Check if the rollback has been queued for execution.
if (_rollback.executableAt != 0) {
// Check if the rollback is within the grace period
if (_isExpiredDueToGracePeriod(_rollbackId)) {
return IGovernor.ProposalState.Expired;
}
return IGovernor.ProposalState.Queued;
}
if (block.timestamp >= _rollback.queueExpiresAt) {
// Rollback is proposed but it's queue window has expired.
return IGovernor.ProposalState.Expired;
}
// Rollback is proposed but not queued for execution.
return IGovernor.ProposalState.Pending;
}
/*///////////////////////////////////////////////////////////////
Abstract Target Methods
//////////////////////////////////////////////////////////////*/
/// @notice Returns the minimum delay required by the timelock target before a queued rollback can be executed.
/// @return The delay in seconds that must elapse between queueing and executing a rollback.
function _delay() internal view virtual returns (uint256);
/// @notice Queues a rollback to the timelock target.
/// @param _targets The targets of the transactions.
/// @param _values The values of the transactions.
/// @param _calldatas The calldatas of the transactions.
/// @param _description The description of the rollback.
function _queue(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) internal virtual;
/// @notice Cancels a rollback on the timelock target.
/// @param _targets The targets of the transactions.
/// @param _values The values of the transactions.
/// @param _calldatas The calldatas of the transactions.
/// @param _description The description of the rollback.
function _cancel(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) internal virtual;
/// @notice Executes a rollback on the timelock target.
/// @param _targets The targets of the transactions.
/// @param _values The values of the transactions.
/// @param _calldatas The calldatas of the transactions.
/// @param _description The description of the rollback.
function _execute(
address[] memory _targets,
uint256[] memory _values,
bytes[] memory _calldatas,
string memory _description
) internal virtual;
/// @notice Returns whether a rollback has expired due to grace period constraints.
/// @param _rollbackId The rollback id to check.
/// @return True if the rollback has expired due to grace period, false otherwise.
function _isExpiredDueToGracePeriod(uint256 _rollbackId) internal view virtual returns (bool);
}