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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 |
55×
55×
55×
55×
55×
55×
55×
55×
55×
55×
55×
55×
55×
357×
356×
356×
4468×
11780×
26×
26×
11×
11×
11×
5×
5×
5×
56×
56×
18×
18×
60×
58×
58×
60×
58×
58×
42×
38×
38×
| // SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.17;
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../interfaces/IMain.sol";
uint256 constant LONG_FREEZE_CHARGES = 6; // 6 uses
uint48 constant MAX_UNFREEZE_AT = type(uint48).max;
uint48 constant MAX_SHORT_FREEZE = 2592000; // 1 month
uint48 constant MAX_LONG_FREEZE = 31536000; // 1 year
/**
* @title Auth
* @notice Provides fine-grained access controls and exports frozen/paused states to Components.
*/
abstract contract Auth is AccessControlUpgradeable, IAuth {
/**
* System-wide states (does not impact ERC20 functions)
* - Frozen: only allow OWNER actions and staking
* - Paused: only allow OWNER actions, redemption, staking, and rewards payout
*
* Typically freezing thaws on its own in a predetemined number of blocks.
* However, OWNER can also freeze forever.
*/
/// The rest of the contract uses the shorthand; these declarations are here for getters
bytes32 public constant OWNER_ROLE = OWNER;
bytes32 public constant SHORT_FREEZER_ROLE = SHORT_FREEZER;
bytes32 public constant LONG_FREEZER_ROLE = LONG_FREEZER;
bytes32 public constant PAUSER_ROLE = PAUSER;
// === Freezing ===
mapping(address => uint256) public longFreezes;
uint48 public unfreezeAt; // {s} uint48.max to pause indefinitely
uint48 public shortFreeze; // {s} length of an initial freeze
uint48 public longFreeze; // {s} length of a freeze extension
// === Pausing ===
bool public paused;
/* ==== Invariants ====
0 <= longFreeze[a] <= LONG_FREEZE_CHARGES for all addrs a
set{a has LONG_FREEZER} == set{a : longFreeze[a] == 0}
*/
// checks:
// - __Auth_init has not previously been called
// - 0 < shortFreeze_ <= MAX_SHORT_FREEZE
// - 0 < longFreeze_ <= MAX_LONG_FREEZE
// effects:
// - caller has all roles
// - OWNER is the admin role for all roles
// - longFreezes[caller] == LONG_FREEZE_CHARGES
// - shortFreeze' == shortFreeze_
// - longFreeze' == longFreeze_
// questions: (what do I know about the values of paused and unfreezeAt?)
// untestable:
// `else` branch of `onlyInitializing` (ie. revert) is currently untestable.
// This function is only called inside other `init` functions, each of which is wrapped
// in an `initializer` modifier, which would fail first.
// solhint-disable-next-line func-name-mixedcase
function __Auth_init(uint48 shortFreeze_, uint48 longFreeze_) internal EonlyInitializing {
__AccessControl_init();
// Role setup
_setRoleAdmin(OWNER, OWNER);
_setRoleAdmin(SHORT_FREEZER, OWNER);
_setRoleAdmin(LONG_FREEZER, OWNER);
_setRoleAdmin(PAUSER, OWNER);
address msgSender = _msgSender();
_grantRole(OWNER, msgSender);
_grantRole(SHORT_FREEZER, msgSender);
_grantRole(LONG_FREEZER, msgSender);
_grantRole(PAUSER, msgSender);
longFreezes[msgSender] = LONG_FREEZE_CHARGES;
setShortFreeze(shortFreeze_);
setLongFreeze(longFreeze_);
}
// checks: caller is an admin for role, account is not 0
// effects:
// - account has the `role` role
// - if role is LONG_FREEZER, then longFreezes'[account] == LONG_FREEZE_CHARGES
function grantRole(bytes32 role, address account)
public
override(AccessControlUpgradeable, IAccessControlUpgradeable)
onlyRole(getRoleAdmin(role))
{
require(account != address(0), "cannot grant role to address 0");
if (role == LONG_FREEZER) longFreezes[account] = LONG_FREEZE_CHARGES;
_grantRole(role, account);
}
// ==== System-wide views ====
// returns: bool(main is frozen) == now < unfreezeAt
function frozen() external view returns (bool) {
return block.timestamp < unfreezeAt;
}
/// @dev This -or- condition is a performance optimization for the consuming Component
// returns: bool(main is frozen or paused) == paused || (now < unfreezeAt)
function pausedOrFrozen() public view returns (bool) {
return paused || block.timestamp < unfreezeAt;
}
// === Freezing ===
/// Enter a freeze for the `shortFreeze` duration
// checks:
// - caller has the SHORT_FREEZER role
// - now + shortFreeze >= unfreezeAt (that is, this call should increase unfreezeAt)
// effects:
// - unfreezeAt' = now + shortFreeze
// - after, caller does not have the SHORT_FREEZER role
function freezeShort() external onlyRole(SHORT_FREEZER) {
// Revoke short freezer role after one use
_revokeRole(SHORT_FREEZER, _msgSender());
freezeUntil(uint48(block.timestamp) + shortFreeze);
}
/// Enter a freeze by the `longFreeze` duration
// checks:
// - caller has the LONG_FREEZER role
// - longFreezes[caller] > 0
// - now + longFreeze >= unfreezeAt (that is, this call should increase unfreezeAt)
// effects:
// - unfreezeAt' = now + longFreeze
// - longFreezes'[caller] = longFreezes[caller] - 1
// - if longFreezes'[caller] == 0 then caller loses the LONG_FREEZER role
function freezeLong() external onlyRole(LONG_FREEZER) {
longFreezes[_msgSender()] -= 1; // reverts on underflow
// Revoke on 0 charges as a cleanup step
if (longFreezes[_msgSender()] == 0) _revokeRole(LONG_FREEZER, _msgSender());
freezeUntil(uint48(block.timestamp) + longFreeze);
}
/// Enter a permanent freeze
// checks:
// - caller has the OWNER role
// - unfreezeAt != type(uint48).max
// effects: unfreezeAt' = type(uint48).max
function freezeForever() external onlyRole(OWNER) {
freezeUntil(MAX_UNFREEZE_AT);
}
/// End all freezes
// checks:
// - unfreezeAt > now (i.e, frozen() == true before the call)
// - caller has the OWNER role
// effects: unfreezeAt' = now (i.e, frozen() == false after the call)
function unfreeze() external onlyRole(OWNER) {
emit UnfreezeAtSet(unfreezeAt, uint48(block.timestamp));
unfreezeAt = uint48(block.timestamp);
}
// === Pausing ===
// checks: caller has PAUSER
// effects: paused' = true
function pause() external onlyRole(PAUSER) {
emit PausedSet(paused, true);
paused = true;
}
// checks: caller has PAUSER
// effects: paused' = false
function unpause() external onlyRole(PAUSER) {
emit PausedSet(paused, false);
paused = false;
}
// === Gov params ===
/// @custom:governance
function setShortFreeze(uint48 shortFreeze_) public onlyRole(OWNER) {
require(shortFreeze_ > 0 && shortFreeze_ <= MAX_SHORT_FREEZE, "short freeze out of range");
emit ShortFreezeDurationSet(shortFreeze, shortFreeze_);
shortFreeze = shortFreeze_;
}
/// @custom:governance
function setLongFreeze(uint48 longFreeze_) public onlyRole(OWNER) {
require(longFreeze_ > 0 && longFreeze_ <= MAX_LONG_FREEZE, "long freeze out of range");
emit LongFreezeDurationSet(longFreeze, longFreeze_);
longFreeze = longFreeze_;
}
// === Private Helper ===
// checks: newUnfreezeAt > unfreezeAt
// effects: unfreezeAt' = newUnfreezeAt
function freezeUntil(uint48 newUnfreezeAt) private {
require(newUnfreezeAt > unfreezeAt, "frozen");
emit UnfreezeAtSet(unfreezeAt, newUnfreezeAt);
unfreezeAt = newUnfreezeAt;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
}
|