Skip to content

Commit 2c92a19

Browse files
Tasks: Implement new time lock modes (#104)
Co-authored-by: lgalende <63872655+lgalende@users.noreply.github.com>
1 parent 09ce034 commit 2c92a19

File tree

12 files changed

+625
-438
lines changed

12 files changed

+625
-438
lines changed

packages/authorizer/contracts/AuthorizedHelpers.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ contract AuthorizedHelpers {
8484
r[2] = p3;
8585
}
8686

87+
function authParams(uint256 p1, uint256 p2, uint256 p3, uint256 p4) internal pure returns (uint256[] memory r) {
88+
r = new uint256[](4);
89+
r[0] = p1;
90+
r[1] = p2;
91+
r[2] = p3;
92+
r[3] = p4;
93+
}
94+
8795
function authParams(address p1, address p2, uint256 p3, uint256 p4) internal pure returns (uint256[] memory r) {
8896
r = new uint256[](4);
8997
r[0] = uint256(uint160(p1));

packages/authorizer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mimic-fi/v3-authorizer",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"license": "GPL-3.0",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

packages/tasks/contracts/base/TimeLockedTask.sol

Lines changed: 167 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,58 @@
1414

1515
pragma solidity ^0.8.3;
1616

17+
import '@quant-finance/solidity-datetime/contracts/DateTime.sol';
1718
import '@mimic-fi/v3-authorizer/contracts/Authorized.sol';
19+
1820
import '../interfaces/base/ITimeLockedTask.sol';
1921

2022
/**
2123
* @dev Time lock config for tasks. It allows limiting the frequency of a task.
2224
*/
2325
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;
2647

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;
2950

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;
3256

3357
/**
3458
* @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
3863
*/
3964
struct TimeLockConfig {
40-
uint256 delay;
41-
uint256 nextExecutionTimestamp;
42-
uint256 executionPeriod;
65+
uint8 mode;
66+
uint256 frequency;
67+
uint256 allowedAt;
68+
uint256 window;
4369
}
4470

4571
/**
@@ -55,100 +81,171 @@ abstract contract TimeLockedTask is ITimeLockedTask, Authorized {
5581
* @param config Time locked task config
5682
*/
5783
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);
6185
}
6286

6387
/**
64-
* @dev Sets the time-lock delay
65-
* @param delay New delay to be set
88+
* @dev Tells the time-lock related information
6689
*/
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);
6992
}
7093

7194
/**
72-
* @dev Sets the time-lock expiration timestamp
73-
* @param expiration New expiration timestamp to be set
95+
* @dev Sets a new time lock
7496
*/
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);
77103
}
78104

79105
/**
80-
* @dev Sets the time-lock execution period
81-
* @param period New execution period to be set
106+
* @dev Before time locked task hook
82107
*/
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+
}
85150
}
86151

87152
/**
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
89154
*/
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;
93159
}
94160

95161
/**
96-
* @dev Before time locked task hook
162+
* @dev Sets a new time lock
97163
*/
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+
}
106194
}
195+
196+
_mode = Mode(mode);
197+
_frequency = frequency;
198+
_allowedAt = allowedAt;
199+
_window = window;
200+
201+
emit TimeLockSet(mode, frequency, allowedAt, window);
107202
}
108203

109204
/**
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
111207
*/
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);
124211
}
125212

126213
/**
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
129215
*/
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);
134219
}
135220

136221
/**
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
139223
*/
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);
143231
}
144232

145233
/**
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
148235
*/
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+
);
153250
}
154251
}

0 commit comments

Comments
 (0)