14
14
15
15
pragma solidity ^ 0.8.3 ;
16
16
17
+ import '@quant-finance/solidity-datetime/contracts/DateTime.sol ' ;
17
18
import '@mimic-fi/v3-authorizer/contracts/Authorized.sol ' ;
19
+
18
20
import '../interfaces/base/ITimeLockedTask.sol ' ;
19
21
20
22
/**
21
23
* @dev Time lock config for tasks. It allows limiting the frequency of a task.
22
24
*/
23
25
abstract contract TimeLockedTask is ITimeLockedTask , Authorized {
24
- // Period in seconds that must pass after a task has been executed
25
- uint256 public override timeLockDelay;
26
+ using DateTime for uint256 ;
27
+
28
+ uint256 private constant DAYS_28 = 60 * 60 * 24 * 28 ;
29
+
30
+ /**
31
+ * @dev Time-locks supports different frequency modes
32
+ * @param Seconds To indicate the execution must occur every certain number of seconds
33
+ * @param OnDay To indicate the execution must occur on day number from 1 to 28 every certain months
34
+ * @param OnLastMonthDay To indicate the execution must occur on the last day of the month every certain months
35
+ */
36
+ enum Mode {
37
+ Seconds,
38
+ OnDay,
39
+ OnLastMonthDay
40
+ }
41
+
42
+ // Time lock mode
43
+ Mode internal _mode;
44
+
45
+ // Time lock frequency
46
+ uint256 internal _frequency;
26
47
27
- // Future timestamp in which the task can be executed
28
- uint256 public override timeLockExpiration ;
48
+ // Future timestamp since when the task can be executed
49
+ uint256 internal _allowedAt ;
29
50
30
- // Period in seconds during when a time-locked task can be executed right after it becomes executable
31
- uint256 public override timeLockExecutionPeriod;
51
+ // Next future timestamp since when the task can be executed to be set, only used internally
52
+ uint256 internal _nextAllowedAt;
53
+
54
+ // Period in seconds during when a time-locked task can be executed since the allowed timestamp
55
+ uint256 internal _window;
32
56
33
57
/**
34
58
* @dev Time lock config params. Only used in the initializer.
35
- * @param delay Period in seconds that must pass after a task has been executed
36
- * @param nextExecutionTimestamp Next time when the task can be executed
37
- * @param executionPeriod Period in seconds during when a time-locked task can be executed
59
+ * @param mode Time lock mode
60
+ * @param frequency Time lock frequency value
61
+ * @param allowedAt Time lock allowed date
62
+ * @param window Time lock execution window
38
63
*/
39
64
struct TimeLockConfig {
40
- uint256 delay;
41
- uint256 nextExecutionTimestamp;
42
- uint256 executionPeriod;
65
+ uint8 mode;
66
+ uint256 frequency;
67
+ uint256 allowedAt;
68
+ uint256 window;
43
69
}
44
70
45
71
/**
@@ -55,100 +81,171 @@ abstract contract TimeLockedTask is ITimeLockedTask, Authorized {
55
81
* @param config Time locked task config
56
82
*/
57
83
function __TimeLockedTask_init_unchained (TimeLockConfig memory config ) internal onlyInitializing {
58
- _setTimeLockDelay (config.delay);
59
- _setTimeLockExpiration (config.nextExecutionTimestamp);
60
- _setTimeLockExecutionPeriod (config.executionPeriod);
84
+ _setTimeLock (config.mode, config.frequency, config.allowedAt, config.window);
61
85
}
62
86
63
87
/**
64
- * @dev Sets the time-lock delay
65
- * @param delay New delay to be set
88
+ * @dev Tells the time-lock related information
66
89
*/
67
- function setTimeLockDelay ( uint256 delay ) external override authP ( authParams (delay) ) {
68
- _setTimeLockDelay (delay );
90
+ function getTimeLock ( ) external view returns ( uint8 mode , uint256 frequency , uint256 allowedAt , uint256 window ) {
91
+ return ( uint8 (_mode), _frequency, _allowedAt, _window );
69
92
}
70
93
71
94
/**
72
- * @dev Sets the time-lock expiration timestamp
73
- * @param expiration New expiration timestamp to be set
95
+ * @dev Sets a new time lock
74
96
*/
75
- function setTimeLockExpiration (uint256 expiration ) external override authP (authParams (expiration)) {
76
- _setTimeLockExpiration (expiration);
97
+ function setTimeLock (uint8 mode , uint256 frequency , uint256 allowedAt , uint256 window )
98
+ external
99
+ override
100
+ authP (authParams (mode, frequency, allowedAt, window))
101
+ {
102
+ _setTimeLock (mode, frequency, allowedAt, window);
77
103
}
78
104
79
105
/**
80
- * @dev Sets the time-lock execution period
81
- * @param period New execution period to be set
106
+ * @dev Before time locked task hook
82
107
*/
83
- function setTimeLockExecutionPeriod (uint256 period ) external override authP (authParams (period)) {
84
- _setTimeLockExecutionPeriod (period);
108
+ function _beforeTimeLockedTask (address , uint256 ) internal virtual {
109
+ // Load storage variables
110
+ Mode mode = _mode;
111
+ uint256 frequency = _frequency;
112
+ uint256 allowedAt = _allowedAt;
113
+ uint256 window = _window;
114
+
115
+ // First we check the current timestamp is not in the past
116
+ if (block .timestamp < allowedAt) revert TaskTimeLockActive (block .timestamp , allowedAt);
117
+
118
+ if (mode == Mode.Seconds) {
119
+ if (frequency == 0 ) return ;
120
+
121
+ // If no window is set, the next allowed date is simply moved the number of seconds set as frequency.
122
+ // Otherwise, the offset must be validated and the next allowed date is set to the next period.
123
+ if (window == 0 ) _nextAllowedAt = block .timestamp + frequency;
124
+ else {
125
+ uint256 diff = block .timestamp - allowedAt;
126
+ uint256 periods = diff / frequency;
127
+ uint256 offset = diff - (periods * frequency);
128
+ if (offset > window) revert TaskTimeLockActive (block .timestamp , allowedAt);
129
+ _nextAllowedAt = allowedAt + ((periods + 1 ) * frequency);
130
+ }
131
+ } else {
132
+ if (block .timestamp >= allowedAt && block .timestamp <= allowedAt + window) {
133
+ // Check the current timestamp has not passed the allowed date set
134
+ _nextAllowedAt = _getNextAllowedDate (allowedAt, frequency);
135
+ } else {
136
+ // Check the current timestamp is not before the current allowed date
137
+ uint256 currentAllowedDay = mode == Mode.OnDay ? allowedAt.getDay () : block .timestamp .getDaysInMonth ();
138
+ uint256 currentAllowedAt = _getCurrentAllowedDate (allowedAt, currentAllowedDay);
139
+ if (block .timestamp < currentAllowedAt) revert TaskTimeLockActive (block .timestamp , currentAllowedAt);
140
+
141
+ // Check the current timestamp has not passed the allowed execution window
142
+ uint256 extendedCurrentAllowedAt = currentAllowedAt + window;
143
+ bool exceedsExecutionWindow = block .timestamp > extendedCurrentAllowedAt;
144
+ if (exceedsExecutionWindow) revert TaskTimeLockActive (block .timestamp , extendedCurrentAllowedAt);
145
+
146
+ // Finally set the next allowed date to the corresponding number of months from the current date
147
+ _nextAllowedAt = _getNextAllowedDate (currentAllowedAt, frequency);
148
+ }
149
+ }
85
150
}
86
151
87
152
/**
88
- * @dev Tells the number of delay periods passed between the last expiration timestamp and the current timestamp
153
+ * @dev After time locked task hook
89
154
*/
90
- function _getDelayPeriods () internal view returns (uint256 ) {
91
- uint256 diff = block .timestamp - timeLockExpiration;
92
- return diff / timeLockDelay;
155
+ function _afterTimeLockedTask (address , uint256 ) internal virtual {
156
+ if (_nextAllowedAt == 0 ) return ;
157
+ _setTimeLockAllowedAt (_nextAllowedAt);
158
+ _nextAllowedAt = 0 ;
93
159
}
94
160
95
161
/**
96
- * @dev Before time locked task hook
162
+ * @dev Sets a new time lock
97
163
*/
98
- function _beforeTimeLockedTask (address , uint256 ) internal virtual {
99
- if (block .timestamp < timeLockExpiration) revert TaskTimeLockNotExpired (timeLockExpiration, block .timestamp );
100
-
101
- if (timeLockExecutionPeriod > 0 ) {
102
- uint256 diff = block .timestamp - timeLockExpiration;
103
- uint256 periods = diff / timeLockDelay;
104
- uint256 offset = diff - (periods * timeLockDelay);
105
- if (offset > timeLockExecutionPeriod) revert TaskTimeLockWaitNextPeriod (offset, timeLockExecutionPeriod);
164
+ function _setTimeLock (uint8 mode , uint256 frequency , uint256 allowedAt , uint256 window ) internal {
165
+ if (mode == uint8 (Mode.Seconds)) {
166
+ // The execution window and timestamp are optional, but both must be given or none
167
+ // If given the execution window cannot be larger than the number of seconds
168
+ // Also, if these are given the frequency must be checked as well, otherwise it could be unsetting the lock
169
+ if (window > 0 || allowedAt > 0 ) {
170
+ if (frequency == 0 ) revert TaskInvalidFrequency (mode, frequency);
171
+ if (window == 0 || window > frequency) revert TaskInvalidAllowedWindow (mode, window);
172
+ if (allowedAt == 0 ) revert TaskInvalidAllowedDate (mode, allowedAt);
173
+ }
174
+ } else {
175
+ // The other modes can be "on-day" or "on-last-day" where the frequency represents a number of months
176
+ // There is no limit for the frequency, it simply cannot be zero
177
+ if (frequency == 0 ) revert TaskInvalidFrequency (mode, frequency);
178
+
179
+ // The execution window cannot be larger than the number of months considering months of 28 days
180
+ if (window == 0 || window > frequency * DAYS_28) revert TaskInvalidAllowedWindow (mode, window);
181
+
182
+ // The allowed date cannot be zero
183
+ if (allowedAt == 0 ) revert TaskInvalidAllowedDate (mode, allowedAt);
184
+
185
+ // If the mode is "on-day", the allowed date must be valid for every month, then the allowed day cannot be
186
+ // larger than 28. But if the mode is "on-last-day", the allowed date day must be the last day of the month
187
+ if (mode == uint8 (Mode.OnDay)) {
188
+ if (allowedAt.getDay () > 28 ) revert TaskInvalidAllowedDate (mode, allowedAt);
189
+ } else if (mode == uint8 (Mode.OnLastMonthDay)) {
190
+ if (allowedAt.getDay () != allowedAt.getDaysInMonth ()) revert TaskInvalidAllowedDate (mode, allowedAt);
191
+ } else {
192
+ revert TaskInvalidFrequencyMode (mode);
193
+ }
106
194
}
195
+
196
+ _mode = Mode (mode);
197
+ _frequency = frequency;
198
+ _allowedAt = allowedAt;
199
+ _window = window;
200
+
201
+ emit TimeLockSet (mode, frequency, allowedAt, window);
107
202
}
108
203
109
204
/**
110
- * @dev After time locked task hook
205
+ * @dev Sets the time-lock execution allowed timestamp
206
+ * @param allowedAt New execution allowed timestamp to be set
111
207
*/
112
- function _afterTimeLockedTask (address , uint256 ) internal virtual {
113
- if (timeLockDelay > 0 ) {
114
- uint256 nextExpirationTimestamp;
115
- if (timeLockExpiration == 0 ) {
116
- nextExpirationTimestamp = block .timestamp + timeLockDelay;
117
- } else {
118
- uint256 diff = block .timestamp - timeLockExpiration;
119
- uint256 nextPeriod = (diff / timeLockDelay) + 1 ;
120
- nextExpirationTimestamp = timeLockExpiration + (nextPeriod * timeLockDelay);
121
- }
122
- _setTimeLockExpiration (nextExpirationTimestamp);
123
- }
208
+ function _setTimeLockAllowedAt (uint256 allowedAt ) internal {
209
+ _allowedAt = allowedAt;
210
+ emit TimeLockAllowedAtSet (allowedAt);
124
211
}
125
212
126
213
/**
127
- * @dev Sets the time-lock delay
128
- * @param delay New delay to be set
214
+ * @dev Tells the corresponding allowed date based on a current timestamp
129
215
*/
130
- function _setTimeLockDelay (uint256 delay ) internal {
131
- if (delay < timeLockExecutionPeriod) revert TaskExecutionPeriodGtDelay (timeLockExecutionPeriod, delay);
132
- timeLockDelay = delay;
133
- emit TimeLockDelaySet (delay);
216
+ function _getCurrentAllowedDate (uint256 allowedAt , uint256 day ) private view returns (uint256 ) {
217
+ (uint256 year , uint256 month , ) = block .timestamp .timestampToDate ();
218
+ return _getAllowedDateFor (allowedAt, year, month, day);
134
219
}
135
220
136
221
/**
137
- * @dev Sets the time-lock expiration timestamp
138
- * @param expiration New expiration timestamp to be set
222
+ * @dev Tells the next allowed date based on a current allowed date considering a number of months to increase
139
223
*/
140
- function _setTimeLockExpiration (uint256 expiration ) internal {
141
- timeLockExpiration = expiration;
142
- emit TimeLockExpirationSet (expiration);
224
+ function _getNextAllowedDate (uint256 allowedAt , uint256 monthsToIncrease ) private view returns (uint256 ) {
225
+ (uint256 year , uint256 month , uint256 day ) = allowedAt.timestampToDate ();
226
+ uint256 increasedMonth = month + monthsToIncrease;
227
+ uint256 nextMonth = increasedMonth % 12 ;
228
+ uint256 nextYear = year + (increasedMonth / 12 );
229
+ uint256 nextDay = _mode == Mode.OnLastMonthDay ? DateTime._getDaysInMonth (nextYear, nextMonth) : day;
230
+ return _getAllowedDateFor (allowedAt, nextYear, nextMonth, nextDay);
143
231
}
144
232
145
233
/**
146
- * @dev Sets the time-lock execution period
147
- * @param period New execution period to be set
234
+ * @dev Builds an allowed date using a specific year, month, and day
148
235
*/
149
- function _setTimeLockExecutionPeriod (uint256 period ) internal {
150
- if (period > timeLockDelay) revert TaskExecutionPeriodGtDelay (period, timeLockDelay);
151
- timeLockExecutionPeriod = period;
152
- emit TimeLockExecutionPeriodSet (period);
236
+ function _getAllowedDateFor (uint256 allowedAt , uint256 year , uint256 month , uint256 day )
237
+ private
238
+ pure
239
+ returns (uint256 )
240
+ {
241
+ return
242
+ DateTime.timestampFromDateTime (
243
+ year,
244
+ month,
245
+ day,
246
+ allowedAt.getHour (),
247
+ allowedAt.getMinute (),
248
+ allowedAt.getSecond ()
249
+ );
153
250
}
154
251
}
0 commit comments