|
@@ -35,7 +35,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
|
|
|
enum OperationState {
|
|
|
Unset,
|
|
|
- Pending,
|
|
|
+ Waiting,
|
|
|
Ready,
|
|
|
Done
|
|
|
}
|
|
@@ -52,8 +52,12 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
|
|
|
/**
|
|
|
* @dev The current state of an operation is not as required.
|
|
|
+ * The `expectedStates` is a bitmap with the bits enabled for each OperationState enum position
|
|
|
+ * counting from right to left.
|
|
|
+ *
|
|
|
+ * See {_encodeStateBitmap}.
|
|
|
*/
|
|
|
- error TimelockUnexpectedOperationState(bytes32 operationId, OperationState expected);
|
|
|
+ error TimelockUnexpectedOperationState(bytes32 operationId, bytes32 expectedStates);
|
|
|
|
|
|
/**
|
|
|
* @dev The predecessor to an operation not yet done.
|
|
@@ -166,30 +170,30 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
* @dev Returns whether an id correspond to a registered operation. This
|
|
|
* includes both Pending, Ready and Done operations.
|
|
|
*/
|
|
|
- function isOperation(bytes32 id) public view virtual returns (bool) {
|
|
|
- return getTimestamp(id) > 0;
|
|
|
+ function isOperation(bytes32 id) public view returns (bool) {
|
|
|
+ return getOperationState(id) != OperationState.Unset;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready".
|
|
|
*/
|
|
|
- function isOperationPending(bytes32 id) public view virtual returns (bool) {
|
|
|
- return getTimestamp(id) > _DONE_TIMESTAMP;
|
|
|
+ function isOperationPending(bytes32 id) public view returns (bool) {
|
|
|
+ OperationState state = getOperationState(id);
|
|
|
+ return state == OperationState.Waiting || state == OperationState.Ready;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending".
|
|
|
*/
|
|
|
- function isOperationReady(bytes32 id) public view virtual returns (bool) {
|
|
|
- uint256 timestamp = getTimestamp(id);
|
|
|
- return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
|
|
|
+ function isOperationReady(bytes32 id) public view returns (bool) {
|
|
|
+ return getOperationState(id) == OperationState.Ready;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @dev Returns whether an operation is done or not.
|
|
|
*/
|
|
|
- function isOperationDone(bytes32 id) public view virtual returns (bool) {
|
|
|
- return getTimestamp(id) == _DONE_TIMESTAMP;
|
|
|
+ function isOperationDone(bytes32 id) public view returns (bool) {
|
|
|
+ return getOperationState(id) == OperationState.Done;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -200,6 +204,22 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
return _timestamps[id];
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @dev Returns operation state.
|
|
|
+ */
|
|
|
+ function getOperationState(bytes32 id) public view virtual returns (OperationState) {
|
|
|
+ uint256 timestamp = getTimestamp(id);
|
|
|
+ if (timestamp == 0) {
|
|
|
+ return OperationState.Unset;
|
|
|
+ } else if (timestamp == _DONE_TIMESTAMP) {
|
|
|
+ return OperationState.Done;
|
|
|
+ } else if (timestamp > block.timestamp) {
|
|
|
+ return OperationState.Waiting;
|
|
|
+ } else {
|
|
|
+ return OperationState.Ready;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* @dev Returns the minimum delay for an operation to become valid.
|
|
|
*
|
|
@@ -298,7 +318,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
*/
|
|
|
function _schedule(bytes32 id, uint256 delay) private {
|
|
|
if (isOperation(id)) {
|
|
|
- revert TimelockUnexpectedOperationState(id, OperationState.Unset);
|
|
|
+ revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Unset));
|
|
|
}
|
|
|
uint256 minDelay = getMinDelay();
|
|
|
if (delay < minDelay) {
|
|
@@ -316,7 +336,10 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
*/
|
|
|
function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
|
|
|
if (!isOperationPending(id)) {
|
|
|
- revert TimelockUnexpectedOperationState(id, OperationState.Pending);
|
|
|
+ revert TimelockUnexpectedOperationState(
|
|
|
+ id,
|
|
|
+ _encodeStateBitmap(OperationState.Waiting) | _encodeStateBitmap(OperationState.Ready)
|
|
|
+ );
|
|
|
}
|
|
|
delete _timestamps[id];
|
|
|
|
|
@@ -399,7 +422,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
*/
|
|
|
function _beforeCall(bytes32 id, bytes32 predecessor) private view {
|
|
|
if (!isOperationReady(id)) {
|
|
|
- revert TimelockUnexpectedOperationState(id, OperationState.Ready);
|
|
|
+ revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready));
|
|
|
}
|
|
|
if (predecessor != bytes32(0) && !isOperationDone(predecessor)) {
|
|
|
revert TimelockUnexecutedPredecessor(predecessor);
|
|
@@ -411,7 +434,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
*/
|
|
|
function _afterCall(bytes32 id) private {
|
|
|
if (!isOperationReady(id)) {
|
|
|
- revert TimelockUnexpectedOperationState(id, OperationState.Ready);
|
|
|
+ revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready));
|
|
|
}
|
|
|
_timestamps[id] = _DONE_TIMESTAMP;
|
|
|
}
|
|
@@ -434,4 +457,19 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder {
|
|
|
emit MinDelayChange(_minDelay, newDelay);
|
|
|
_minDelay = newDelay;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @dev Encodes a `OperationState` into a `bytes32` representation where each bit enabled corresponds to
|
|
|
+ * the underlying position in the `OperationState` enum. For example:
|
|
|
+ *
|
|
|
+ * 0x000...1000
|
|
|
+ * ^^^^^^----- ...
|
|
|
+ * ^---- Done
|
|
|
+ * ^--- Ready
|
|
|
+ * ^-- Waiting
|
|
|
+ * ^- Unset
|
|
|
+ */
|
|
|
+ function _encodeStateBitmap(OperationState operationState) internal pure returns (bytes32) {
|
|
|
+ return bytes32(1 << uint8(operationState));
|
|
|
+ }
|
|
|
}
|