Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tasks: Implement new time lock modes #104

Merged
merged 9 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/authorizer/contracts/AuthorizedHelpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ contract AuthorizedHelpers {
r[2] = p3;
}

function authParams(uint256 p1, uint256 p2, uint256 p3, uint256 p4) internal pure returns (uint256[] memory r) {
r = new uint256[](4);
r[0] = p1;
r[1] = p2;
r[2] = p3;
r[3] = p4;
}

function authParams(address p1, address p2, uint256 p3, uint256 p4) internal pure returns (uint256[] memory r) {
r = new uint256[](4);
r[0] = uint256(uint160(p1));
Expand Down
2 changes: 1 addition & 1 deletion packages/authorizer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mimic-fi/v3-authorizer",
"version": "0.1.0",
"version": "0.1.1",
"license": "GPL-3.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
236 changes: 166 additions & 70 deletions packages/tasks/contracts/base/TimeLockedTask.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,58 @@

pragma solidity ^0.8.3;

import '@quant-finance/solidity-datetime/contracts/DateTime.sol';
import '@mimic-fi/v3-authorizer/contracts/Authorized.sol';

import '../interfaces/base/ITimeLockedTask.sol';

/**
* @dev Time lock config for tasks. It allows limiting the frequency of a task.
*/
abstract contract TimeLockedTask is ITimeLockedTask, Authorized {
// Period in seconds that must pass after a task has been executed
uint256 public override timeLockDelay;
using DateTime for uint256;

uint256 private constant DAYS_28 = 60 * 60 * 24 * 28;

/**
* @dev Time-locks supports different frequency modes
* @param Seconds To indicate the execution must occur every certain number of seconds
* @param OnDay To indicate the execution must occur on day number from 1 to 28 every certain months
* @param OnLastMonthDay To indicate the execution must occur on the last day of the month every certain months
*/
enum Mode {
Seconds,
OnDay,
OnLastMonthDay
}

// Time lock mode
Mode internal _mode;

// Time lock frequency
uint256 internal _frequency;

// Future timestamp in which the task can be executed
uint256 public override timeLockExpiration;
// Future timestamp since when the task can be executed
uint256 internal _allowedAt;

// Period in seconds during when a time-locked task can be executed right after it becomes executable
uint256 public override timeLockExecutionPeriod;
// Next future timestamp since when the task can be executed to be set, only used internally
uint256 internal _nextAllowedAt;

// Period in seconds during when a time-locked task can be executed since the allowed timestamp
uint256 internal _window;

/**
* @dev Time lock config params. Only used in the initializer.
* @param delay Period in seconds that must pass after a task has been executed
* @param nextExecutionTimestamp Next time when the task can be executed
* @param executionPeriod Period in seconds during when a time-locked task can be executed
* @param mode Time lock mode
* @param frequency Time lock frequency value
* @param allowedAt Time lock allowed date
* @param window Time lock execution window
*/
struct TimeLockConfig {
uint256 delay;
uint256 nextExecutionTimestamp;
uint256 executionPeriod;
uint8 mode;
uint256 frequency;
uint256 allowedAt;
uint256 window;
}

/**
Expand All @@ -55,100 +81,170 @@ abstract contract TimeLockedTask is ITimeLockedTask, Authorized {
* @param config Time locked task config
*/
function __TimeLockedTask_init_unchained(TimeLockConfig memory config) internal onlyInitializing {
_setTimeLockDelay(config.delay);
_setTimeLockExpiration(config.nextExecutionTimestamp);
_setTimeLockExecutionPeriod(config.executionPeriod);
_setTimeLock(config.mode, config.frequency, config.allowedAt, config.window);
}

/**
* @dev Sets the time-lock delay
* @param delay New delay to be set
* @dev Tells the time-lock related information
*/
function setTimeLockDelay(uint256 delay) external override authP(authParams(delay)) {
_setTimeLockDelay(delay);
function getTimeLock() external view returns (uint8 mode, uint256 frequency, uint256 allowedAt, uint256 window) {
return (uint8(_mode), _frequency, _allowedAt, _window);
}

/**
* @dev Sets the time-lock expiration timestamp
* @param expiration New expiration timestamp to be set
* @dev Sets a new time lock
*/
function setTimeLockExpiration(uint256 expiration) external override authP(authParams(expiration)) {
_setTimeLockExpiration(expiration);
function setTimeLock(uint8 mode, uint256 frequency, uint256 allowedAt, uint256 window)
external
override
authP(authParams(mode, frequency, allowedAt, window))
{
_setTimeLock(mode, frequency, allowedAt, window);
}

/**
* @dev Sets the time-lock execution period
* @param period New execution period to be set
* @dev Before time locked task hook
*/
function setTimeLockExecutionPeriod(uint256 period) external override authP(authParams(period)) {
_setTimeLockExecutionPeriod(period);
function _beforeTimeLockedTask(address, uint256) internal virtual {
// Load storage variables
Mode mode = _mode;
uint256 frequency = _frequency;
uint256 allowedAt = _allowedAt;
uint256 window = _window;

// First we check the current timestamp is not in the past
if (block.timestamp < allowedAt) revert TaskTimeLockActive(block.timestamp, allowedAt);

if (mode == Mode.Seconds) {
if (frequency == 0) return;

// If no window is set, the next allowed date is simply moved the number of seconds set as frequency.
// Otherwise, the offset must be validated and the next allowed date is set to the next period.
if (window == 0) _nextAllowedAt = block.timestamp + frequency;
else {
uint256 diff = block.timestamp - allowedAt;
uint256 periods = diff / frequency;
uint256 offset = diff - (periods * frequency);
if (offset > window) revert TaskTimeLockActive(block.timestamp, allowedAt);
_nextAllowedAt = allowedAt + ((periods + 1) * frequency);
}
} else {
if (block.timestamp >= allowedAt && block.timestamp <= allowedAt + window) {
// Check the current timestamp has not passed the allowed at set
_nextAllowedAt = _getNextAllowedDate(allowedAt, frequency);
} else {
// Check the current timestamp is not before the current allowed date
uint256 currentAllowedDay = mode == Mode.OnDay ? allowedAt.getDay() : block.timestamp.getDaysInMonth();
uint256 currentAllowedAt = _getCurrentAllowedDate(allowedAt, currentAllowedDay);
if (block.timestamp < currentAllowedAt) revert TaskTimeLockActive(block.timestamp, currentAllowedAt);

// Check the current timestamp has not passed the allowed execution window
uint256 extendedCurrentAllowedAt = currentAllowedAt + window;
bool exceedsExecutionWindow = block.timestamp > extendedCurrentAllowedAt;
if (exceedsExecutionWindow) revert TaskTimeLockActive(block.timestamp, extendedCurrentAllowedAt);

// Finally set the next allowed date to the corresponding number of months from the current date
_nextAllowedAt = _getNextAllowedDate(currentAllowedAt, frequency);
}
}
}

/**
* @dev Tells the number of delay periods passed between the last expiration timestamp and the current timestamp
* @dev After time locked task hook
*/
function _getDelayPeriods() internal view returns (uint256) {
uint256 diff = block.timestamp - timeLockExpiration;
return diff / timeLockDelay;
function _afterTimeLockedTask(address, uint256) internal virtual {
if (_nextAllowedAt == 0) return;
_setTimeLockAllowedAt(_nextAllowedAt);
_nextAllowedAt = 0;
}

/**
* @dev Before time locked task hook
* @dev Sets a new time lock
*/
function _beforeTimeLockedTask(address, uint256) internal virtual {
if (block.timestamp < timeLockExpiration) revert TaskTimeLockNotExpired(timeLockExpiration, block.timestamp);

if (timeLockExecutionPeriod > 0) {
uint256 diff = block.timestamp - timeLockExpiration;
uint256 periods = diff / timeLockDelay;
uint256 offset = diff - (periods * timeLockDelay);
if (offset > timeLockExecutionPeriod) revert TaskTimeLockWaitNextPeriod(offset, timeLockExecutionPeriod);
function _setTimeLock(uint8 mode, uint256 frequency, uint256 allowedAt, uint256 window) internal {
if (mode == uint8(Mode.Seconds)) {
// The execution window and timestamp are optional, but both must be given or none
// If given the execution window cannot be larger than the number of seconds
// Also, if these are given the frequency must be checked as well, otherwise it could be unsetting the lock
if (window > 0 || allowedAt > 0) {
if (frequency == 0) revert TaskInvalidFrequency(mode, frequency);
if (window == 0 || window > frequency) revert TaskInvalidAllowedWindow(mode, window);
if (allowedAt == 0) revert TaskInvalidAllowedDate(mode, allowedAt);
}
} else {
// The other modes can be "on-day" or "on-last-day" where the frequency represents a number of months
// There is no limit for the frequency, it simply cannot be zero
if (frequency == 0) revert TaskInvalidFrequency(mode, frequency);

// The execution window cannot be larger than the number of months considering months of 28 days
if (window == 0 || window > frequency * DAYS_28) revert TaskInvalidAllowedWindow(mode, window);

// The allowed date cannot be zero
if (allowedAt == 0) revert TaskInvalidAllowedDate(mode, allowedAt);

// If the mode is "on-day", the allowed date must be valid for every month, then the allowed day cannot be
// larger than 28. But if the mode is "on-last-day", the allowed date day must be the last day of the month
if (mode == uint8(Mode.OnDay)) {
if (allowedAt.getDay() > 28) revert TaskInvalidAllowedDate(mode, allowedAt);
} else if (mode == uint8(Mode.OnLastMonthDay)) {
if (allowedAt.getDay() != allowedAt.getDaysInMonth()) revert TaskInvalidAllowedDate(mode, allowedAt);
} else {
revert TaskInvalidFrequencyMode(mode);
}
}

_mode = Mode(mode);
_frequency = frequency;
_allowedAt = allowedAt;
_window = window;

emit TimeLockSet(mode, frequency, allowedAt, window);
}

/**
* @dev After time locked task hook
* @dev Sets the time-lock execution allowed timestamp
* @param allowedAt New execution allowed timestamp to be set
*/
function _afterTimeLockedTask(address, uint256) internal virtual {
if (timeLockDelay > 0) {
uint256 nextExpirationTimestamp;
if (timeLockExpiration == 0) {
nextExpirationTimestamp = block.timestamp + timeLockDelay;
} else {
uint256 diff = block.timestamp - timeLockExpiration;
uint256 nextPeriod = (diff / timeLockDelay) + 1;
nextExpirationTimestamp = timeLockExpiration + (nextPeriod * timeLockDelay);
}
_setTimeLockExpiration(nextExpirationTimestamp);
}
function _setTimeLockAllowedAt(uint256 allowedAt) internal {
_allowedAt = allowedAt;
emit TimeLockAllowedAtSet(allowedAt);
}

/**
* @dev Sets the time-lock delay
* @param delay New delay to be set
* @dev Tells the corresponding allowed date based on a current timestamp
*/
function _setTimeLockDelay(uint256 delay) internal {
if (delay < timeLockExecutionPeriod) revert TaskExecutionPeriodGtDelay(timeLockExecutionPeriod, delay);
timeLockDelay = delay;
emit TimeLockDelaySet(delay);
function _getCurrentAllowedDate(uint256 allowedAt, uint256 day) private view returns (uint256) {
(uint256 year, uint256 month, ) = block.timestamp.timestampToDate();
return _getAllowedDateFor(allowedAt, year, month, day);
}

/**
* @dev Sets the time-lock expiration timestamp
* @param expiration New expiration timestamp to be set
* @dev Tells the next allowed date based on a current allowed date considering a number of months to increase
*/
function _setTimeLockExpiration(uint256 expiration) internal {
timeLockExpiration = expiration;
emit TimeLockExpirationSet(expiration);
function _getNextAllowedDate(uint256 allowedAt, uint256 monthsToIncrease) private view returns (uint256) {
(uint256 year, uint256 month, uint256 day) = allowedAt.timestampToDate();
uint256 nextMonth = (month + monthsToIncrease) % 12;
uint256 nextYear = year + ((month + monthsToIncrease) / 12);
facuspagnuolo marked this conversation as resolved.
Show resolved Hide resolved
uint256 nextDay = _mode == Mode.OnLastMonthDay ? DateTime._getDaysInMonth(nextYear, nextMonth) : day;
return _getAllowedDateFor(allowedAt, nextYear, nextMonth, nextDay);
}

/**
* @dev Sets the time-lock execution period
* @param period New execution period to be set
* @dev Builds an allowed date using a specific year, month, and day
*/
function _setTimeLockExecutionPeriod(uint256 period) internal {
if (period > timeLockDelay) revert TaskExecutionPeriodGtDelay(period, timeLockDelay);
timeLockExecutionPeriod = period;
emit TimeLockExecutionPeriodSet(period);
function _getAllowedDateFor(uint256 allowedAt, uint256 year, uint256 month, uint256 day)
private
pure
returns (uint256)
{
return
DateTime.timestampFromDateTime(
year,
month,
day,
allowedAt.getHour(),
allowedAt.getMinute(),
allowedAt.getSecond()
);
}
}
Loading
Loading