This repository has been archived by the owner on Dec 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 16
/
Phased.sol
174 lines (161 loc) · 6.97 KB
/
Phased.sol
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
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.10;
/// @title Phased
/// @notice `Phased` is an abstract contract that defines up to `9` phases that
/// an implementing contract moves through.
///
/// `Phase.ZERO` is always the first phase and does not, and cannot, be set
/// expicitly. Effectively it is implied that `Phase.ZERO` has been active
/// since block zero.
///
/// Each subsequent phase `Phase.ONE` through `Phase.EIGHT` must be
/// scheduled sequentially and explicitly at a block number.
///
/// Only the immediate next phase can be scheduled with `scheduleNextPhase`,
/// it is not possible to schedule multiple phases ahead.
///
/// Multiple phases can be scheduled in a single block if each scheduled phase
/// is scheduled for the current block.
///
/// Several utility functions and modifiers are provided.
///
/// One event `PhaseShiftScheduled` is emitted each time a phase shift is
/// scheduled (not when the scheduled phase is reached).
///
/// @dev `Phased` contracts have a defined timeline with available
/// functionality grouped into phases.
/// Every `Phased` contract starts at `Phase.ZERO` and moves sequentially
/// through phases `ONE` to `EIGHT`.
/// Every `Phase` other than `Phase.ZERO` is optional, there is no requirement
/// that all 9 phases are implemented.
/// Phases can never be revisited, the inheriting contract always moves through
/// each achieved phase linearly.
/// This is enforced by only allowing `scheduleNextPhase` to be called once per
/// phase.
/// It is possible to call `scheduleNextPhase` several times in a single block
/// but the `block.number` for each phase must be reached each time to schedule
/// the next phase.
/// Importantly there are events and several modifiers and checks available to
/// ensure that functionality is limited to the current phase.
/// The full history of each phase shift block is recorded as a fixed size
/// array of `uint32`.
contract Phased {
/// Every phase block starts uninitialized.
/// Only uninitialized blocks can be set by the phase scheduler.
uint32 private constant UNINITIALIZED = type(uint32).max;
uint256 private constant MAX_PHASE = 8;
/// `PhaseScheduled` is emitted when the next phase is scheduled.
event PhaseScheduled(
address sender,
uint256 newPhase,
uint256 scheduledBlock
);
/// 8 phases each as 32 bits to fit a single 32 byte word.
uint32[8] public phaseBlocks;
/// Initialize the blocks at "never".
/// All phase blocks are initialized to `UNINITIALIZED`.
/// i.e. not fallback solidity value of `0`.
function initializePhased() internal {
// Reinitialization is a bug.
// Only need to check the first block as all blocks are about to be set
// to `UNINITIALIZED`.
assert(phaseBlocks[0] < 1);
uint32[8] memory phaseBlocks_ = [
UNINITIALIZED,
UNINITIALIZED,
UNINITIALIZED,
UNINITIALIZED,
UNINITIALIZED,
UNINITIALIZED,
UNINITIALIZED,
UNINITIALIZED
];
phaseBlocks = phaseBlocks_;
// 0 is always the block for implied phase 0.
emit PhaseScheduled(msg.sender, 0, 0);
}
/// Pure function to reduce an array of phase blocks and block number to a
/// specific `Phase`.
/// The phase will be the highest attained even if several phases have the
/// same block number.
/// If every phase block is after the block number then `Phase.ZERO` is
/// returned.
/// If every phase block is before the block number then `Phase.EIGHT` is
/// returned.
/// @param phaseBlocks_ Fixed array of phase blocks to compare against.
/// @param blockNumber_ Determine the relevant phase relative to this block
/// number.
/// @return The "current" phase relative to the block number and phase
/// blocks list.
function phaseAtBlockNumber(
uint32[8] memory phaseBlocks_,
uint256 blockNumber_
) public pure returns (uint256) {
for (uint256 i_ = 0; i_ < MAX_PHASE; i_++) {
if (blockNumber_ < phaseBlocks_[i_]) {
return i_;
}
}
return MAX_PHASE;
}
/// Pure function to reduce an array of phase blocks and phase to a
/// specific block number.
/// `Phase.ZERO` will always return block `0`.
/// Every other phase will map to a block number in `phaseBlocks_`.
/// @param phaseBlocks_ Fixed array of phase blocks to compare against.
/// @param phase_ Determine the relevant block number for this phase.
/// @return The block number for the phase according to `phaseBlocks_`.
function blockNumberForPhase(uint32[8] memory phaseBlocks_, uint256 phase_)
public
pure
returns (uint256)
{
return phase_ > 0 ? phaseBlocks_[phase_ - 1] : 0;
}
/// Impure read-only function to return the "current" phase from internal
/// contract state.
/// Simply wraps `phaseAtBlockNumber` for current values of `phaseBlocks`
/// and `block.number`.
function currentPhase() public view returns (uint256) {
return phaseAtBlockNumber(phaseBlocks, block.number);
}
/// Modifies functions to only be callable in a specific phase.
/// @param phase_ Modified functions can only be called during this phase.
modifier onlyPhase(uint256 phase_) {
require(currentPhase() == phase_, "BAD_PHASE");
_;
}
/// Modifies functions to only be callable in a specific phase OR if the
/// specified phase has passed.
/// @param phase_ Modified function only callable during or after this
/// phase.
modifier onlyAtLeastPhase(uint256 phase_) {
require(currentPhase() >= phase_, "MIN_PHASE");
_;
}
/// Writes the block for the next phase.
/// Only uninitialized blocks can be written to.
/// Only the immediate next phase relative to `currentPhase` can be written
/// to. It is still required to specify the `phase_` so that it is explicit
/// and clear in the calling code which phase is being moved to.
/// Emits `PhaseShiftScheduled` with the phase block.
/// @param phase_ The phase being scheduled.
/// @param block_ The block for the phase.
function schedulePhase(uint256 phase_, uint256 block_) internal {
require(block.number <= block_, "NEXT_BLOCK_PAST");
require(block_ < UNINITIALIZED, "NEXT_BLOCK_UNINITIALIZED");
// Don't need to check for underflow as the index will be used as a
// fixed array index below. Implies that scheduling phase `0` is NOT
// supported.
uint256 index_;
unchecked {
index_ = phase_ - 1;
}
// Bit of a hack to check the current phase against the index to
// save calculating the subtraction twice.
require(currentPhase() == index_, "NEXT_PHASE");
require(UNINITIALIZED == phaseBlocks[index_], "NEXT_BLOCK_SET");
phaseBlocks[index_] = uint32(block_);
emit PhaseScheduled(msg.sender, phase_, block_);
}
}