-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathOldEmergencyBrake.sol
154 lines (132 loc) · 6.11 KB
/
OldEmergencyBrake.sol
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@yield-protocol/utils-v2/src/access/AccessControl.sol";
interface IEmergencyBrake {
struct Permission {
address contact;
bytes4[] signatures;
}
function plan(address target, Permission[] calldata permissions) external returns (bytes32 txHash);
function cancel(bytes32 txHash) external;
function execute(bytes32 txHash) external;
function restore(bytes32 txHash) external;
function terminate(bytes32 txHash) external;
}
/// @dev EmergencyBrake allows to plan for and execute transactions that remove access permissions for a target
/// contract. In an permissioned environment this can be used for pausing components.
/// All contracts in scope of emergency plans must grant ROOT permissions to EmergencyBrake. To mitigate the risk
/// of governance capture, EmergencyBrake has very limited functionality, being able only to revoke existing roles
/// and to restore previously revoked roles. Thus EmergencyBrake cannot grant permissions that weren't there in the
/// first place. As an additional safeguard, EmergencyBrake cannot revoke or grant ROOT roles.
/// In addition, there is a separation of concerns between the planner and the executor accounts, so that both of them
/// must be compromised simultaneously to execute non-approved emergency plans, and then only creating a denial of service.
contract OldEmergencyBrake is AccessControl, IEmergencyBrake {
enum State {UNPLANNED, PLANNED, EXECUTED}
struct Plan {
State state;
address target;
bytes permissions;
}
event Planned(bytes32 indexed txHash, address indexed target);
event Cancelled(bytes32 indexed txHash);
event Executed(bytes32 indexed txHash, address indexed target);
event Restored(bytes32 indexed txHash, address indexed target);
event Terminated(bytes32 indexed txHash);
mapping (bytes32 => Plan) public plans;
constructor(address planner, address executor) AccessControl() {
_grantRole(IEmergencyBrake.plan.selector, planner);
_grantRole(IEmergencyBrake.cancel.selector, planner);
_grantRole(IEmergencyBrake.execute.selector, executor);
_grantRole(IEmergencyBrake.restore.selector, planner);
_grantRole(IEmergencyBrake.terminate.selector, planner);
// Granting roles (plan, cancel, execute, restore, terminate) is reserved to ROOT
}
/// @dev Compute the hash of a plan
function hash(address target, Permission[] calldata permissions)
external pure
returns (bytes32 txHash)
{
txHash = keccak256(abi.encode(target, permissions));
}
/// @dev Register an access removal transaction
function plan(address target, Permission[] calldata permissions)
external override auth
returns (bytes32 txHash)
{
txHash = keccak256(abi.encode(target, permissions));
require(plans[txHash].state == State.UNPLANNED, "Emergency already planned for.");
// Removing or granting ROOT permissions is out of bounds for EmergencyBrake
for (uint256 i = 0; i < permissions.length; i++){
for (uint256 j = 0; j < permissions[i].signatures.length; j++){
require(
permissions[i].signatures[j] != ROOT,
"Can't remove ROOT"
);
}
}
plans[txHash] = Plan({
state: State.PLANNED,
target: target,
permissions: abi.encode(permissions)
});
emit Planned(txHash, target);
}
/// @dev Erase a planned access removal transaction
function cancel(bytes32 txHash)
external override auth
{
require(plans[txHash].state == State.PLANNED, "Emergency not planned for.");
delete plans[txHash];
emit Cancelled(txHash);
}
/// @dev Execute an access removal transaction
function execute(bytes32 txHash)
external override auth
{
Plan memory plan_ = plans[txHash];
require(plan_.state == State.PLANNED, "Emergency not planned for.");
plans[txHash].state = State.EXECUTED;
Permission[] memory permissions_ = abi.decode(plan_.permissions, (Permission[]));
for (uint256 i = 0; i < permissions_.length; i++){
// AccessControl.sol doesn't revert if revoking permissions that haven't been granted
// If we don't check, planner and executor can collude to gain access to contacts
Permission memory permission_ = permissions_[i];
for (uint256 j = 0; j < permission_.signatures.length; j++){
AccessControl contact = AccessControl(permission_.contact);
bytes4 signature_ = permission_.signatures[j];
require(
contact.hasRole(signature_, plan_.target),
"Permission not found"
);
contact.revokeRole(signature_, plan_.target);
}
}
emit Executed(txHash, plan_.target);
}
/// @dev Restore the orchestration from an isolated target
function restore(bytes32 txHash)
external override auth
{
Plan memory plan_ = plans[txHash];
require(plan_.state == State.EXECUTED, "Emergency plan not executed.");
plans[txHash].state = State.PLANNED;
Permission[] memory permissions_ = abi.decode(plan_.permissions, (Permission[]));
for (uint256 i = 0; i < permissions_.length; i++){
Permission memory permission_ = permissions_[i];
for (uint256 j = 0; j < permission_.signatures.length; j++){
AccessControl contact = AccessControl(permission_.contact);
bytes4 signature_ = permission_.signatures[j];
contact.grantRole(signature_, plan_.target);
}
}
emit Restored(txHash, plan_.target);
}
/// @dev Remove the restoring option from an isolated target
function terminate(bytes32 txHash)
external override auth
{
require(plans[txHash].state == State.EXECUTED, "Emergency plan not executed.");
delete plans[txHash];
emit Terminated(txHash);
}
}