This repository was archived by the owner on Dec 3, 2025. It is now read-only.
forked from ZTX-Foundation/tuxedo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGlobalReentrancyLock.sol
More file actions
197 lines (157 loc) · 8.28 KB
/
GlobalReentrancyLock.sol
File metadata and controls
197 lines (157 loc) · 8.28 KB
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
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;
import {Roles} from "../core/Roles.sol";
import {CoreRef} from "../refs/CoreRef.sol";
import {IGlobalReentrancyLock} from "./IGlobalReentrancyLock.sol";
/// @notice inpsired by the openzeppelin reentrancy guard smart contracts
/// data container size has been changed.
/// @dev allows contracts and addresses with the LOCKER role to call
/// in and lock and unlock this smart contract.
/// once locked, only the original caller that locked can unlock the contract
/// without the governor emergency unlock functionality.
/// Governor can unpause if locked but not unlocked.
/// @notice explanation on data types used in contract
/// @dev block number can be safely downcasted without a check on exceeding
/// uint88 max because the sun will explode before this statement is true:
/// block.number > 2^88 - 1
/// address can be stored in a uint160 because an address is only 20 bytes
/// @dev in the EVM. 160bits / 8 bits per byte = 20 bytes
/// https://docs.soliditylang.org/en/develop/types.html#address
contract GlobalReentrancyLock is IGlobalReentrancyLock, CoreRef {
/// -------------------------------------------------
/// -------------------------------------------------
/// ------------------- Constants -------------------
/// -------------------------------------------------
/// -------------------------------------------------
uint8 private constant _NOT_ENTERED = 0;
// uint8 private constant _ENTERED_LEVEL_ONE = 1;
uint8 private constant _ENTERED_LEVEL_TWO = 2;
/// ------------- System States ---------------
/// system unlocked
/// request level 2 locked
/// call reverts because system must be locked at level 1 before locking to level 2
///
/// system unlocked
/// request level 1 locked
/// level 1 locked, msg.sender stored
/// level 1 unlocked, msg.sender checked to ensure same as locking
///
/// lock level 1, msg.sender is stored
/// request level 2 locked
/// level 2 locked, msg.sender not stored
/// request level 2 unlocked,
/// level 2 unlocked, msg.sender not checked
/// level 1 unlocked, msg.sender checked
///
/// level 1 locked
/// request level 2 locked
/// level 2 locked
/// request level 0 unlocked, invalid state, must unlock to level 1, call reverts
///
/// request level 3 or greater locked from any system state, call reverts
/// -------------------------------------------------
/// -------------------------------------------------
/// --------- Single Storage Slot Per Lock ----------
/// -------------------------------------------------
/// -------------------------------------------------
/// @notice cache the address that locked the system
/// only this address can unlock it
address public lastSender;
/// @notice store the last block entered
/// if last block entered was in the past and status
/// is entered, the system is in an invalid state
/// which means that actions should not be allowed
uint88 public lastBlockEntered;
/// @notice system lock level
uint8 public lockLevel;
/// @param _core reference to core
constructor(address _core) CoreRef(_core) {}
/// ---------- View Only APIs ----------
/// @notice returns true if the contract is not currently entered
/// at level 1 and 2, returns false otherwise
function isUnlocked() external view override returns (bool) {
return lockLevel == _NOT_ENTERED;
}
/// @notice returns whether or not the contract is currently locked
function isLocked() external view override returns (bool) {
return lockLevel != _NOT_ENTERED;
}
/// ---------- Global Locker Role State Changing APIs ----------
/// @notice set the status to entered
/// Callable only by locker role
/// @dev only valid state transitions:
/// - lock to level 1 from level 0
/// - lock to level 2 from level 1
function lock(uint8 toLock) external override onlyRole(Roles.LOCKER_PROTOCOL_ROLE) {
uint8 currentLevel = lockLevel; /// cache to save 1 warm SLOAD
require(toLock == currentLevel + 1, "GlobalReentrancyLock: invalid lock level");
require(toLock <= _ENTERED_LEVEL_TWO, "GlobalReentrancyLock: exceeds lock state");
/// only store the sender and lastBlockEntered if first caller (locking to level 1)
if (currentLevel == _NOT_ENTERED) {
/// - lock to level 1 from level 0
uint88 blockEntered = uint88(block.number);
lastSender = msg.sender;
lastBlockEntered = blockEntered;
} else {
/// - lock to level 2 from level 1
/// ------ increasing lock level flow ------
/// do not update sender, to ensure original sender gets checked on final unlock
/// do not update lastBlockEntered because it should be the same, if it isn't, revert
/// if already entered, ensure entry happened this block
//slither-disable-next-line incorrect-equality
require(block.number == lastBlockEntered, "GlobalReentrancyLock: system not entered this block");
/// prevent footguns, do not allow original locker to lock again
require(msg.sender != lastSender, "GlobalReentrancyLock: reentrant");
}
lockLevel = toLock;
}
/// @notice set the status to not entered
/// only available if entered in same block
/// otherwise, system is in an indeterminate state and no execution should be allowed
/// can only be called by the last address to lock the system
/// to prevent incorrect system behavior
/// Only callable by sender's with the locker role
/// @dev toUnlock can only be _ENTERED_LEVEL_ONE or _NOT_ENTERED
/// currentLevel cannot be _NOT_ENTERED when this function is called
/// @dev only valid state transitions:
/// - unlock to level 0 from level 1 as original locker in same block as lock
/// - lock from level 2 down to level 1 in same block as lock
function unlock(uint8 toUnlock) external override onlyRole(Roles.LOCKER_PROTOCOL_ROLE) {
uint8 currentLevel = lockLevel;
//slither-disable-next-line incorrect-equality
require(uint88(block.number) == lastBlockEntered, "GlobalReentrancyLock: not entered this block");
require(currentLevel != _NOT_ENTERED, "GlobalReentrancyLock: system not entered");
/// if started at level 1, locked up to level 2,
/// and trying to lock down to level 0,
/// fail as that puts us in an invalid state
require(toUnlock == currentLevel - 1, "GlobalReentrancyLock: unlock level must be 1 lower");
if (toUnlock == _NOT_ENTERED) {
/// - unlock to level 0 from level 1, verify sender is original locker
require(msg.sender == lastSender, "GlobalReentrancyLock: caller is not locker");
} else {
/// prevent footguns, do not allow original locker to unlock from level 2 to level 1
require(msg.sender != lastSender, "GlobalReentrancyLock: reentrant");
}
lockLevel = toUnlock;
}
/// ---------- Admin Only State Changing API ----------
/// @notice function to recover the system from an incorrect state
/// in case of emergency by setting status to not entered
/// only callable if system is entered in a previous block
function adminEmergencyRecover() external override onlyRole(Roles.ADMIN) {
/// must be locked either at level one, or at level 2
require(lockLevel != _NOT_ENTERED, "GlobalReentrancyLock: governor recovery, system not entered");
/// status level 1 or level 2 lock == entered at this point
/// stop malicious governor from unlocking in the same block as lock happened
/// if governor is compromised, we're likely in a state FUBAR
require(block.number != lastBlockEntered, "GlobalReentrancyLock: cannot unlock in same block as lock");
lockLevel = _NOT_ENTERED;
emit EmergencyUnlock(msg.sender, block.timestamp);
}
/// @notice governor only function to pause the entire system
/// sets the lock to level two lock
function adminEmergencyPause() external override onlyRole(Roles.ADMIN) {
lockLevel = _ENTERED_LEVEL_TWO;
emit EmergencyLock(msg.sender, block.timestamp);
}
}