-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathModulesProxyRegistry.sol
306 lines (272 loc) · 12.4 KB
/
ModulesProxyRegistry.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
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// SPDX-License-Identifier: MIT
pragma solidity 0.5.17;
import "../../utils/Utils.sol";
import "../../utils/ProxyOwnable.sol";
import "../modules/interfaces/IFunctionsList.sol";
import "../modules/interfaces/IModulesProxyRegistry.sol";
import "../../openzeppelin/Address.sol";
/**
* ModulesProxyRegistry provides modules registration/removing/replacing functionality to ModulesProxy
* Designed to be inherited
*/
contract ModulesProxyRegistry is IModulesProxyRegistry, ProxyOwnable {
using Address for address;
bytes32 internal constant KEY_IMPLEMENTATION = keccak256("key.implementation");
///@notice Constructor is internal to make contract abstract
constructor() internal {
// abstract
}
/// @notice Add module functions.
/// Overriding functions is not allowed. To replace modules use replaceModule function.
/// @param _impl Module implementation address
function addModule(address _impl) external onlyProxyOwner {
_addModule(_impl);
}
/// @notice Add modules functions.
/// @param _implementations Modules implementation addresses
function addModules(address[] calldata _implementations) external onlyProxyOwner {
_addModules(_implementations);
}
/// @notice Replace module - remove the previous, add the new one
/// @param _oldModuleImpl Module implementation address to remove
/// @param _newModuleImpl Module implementation address to add
function replaceModule(
address _oldModuleImpl,
address _newModuleImpl
) external onlyProxyOwner {
_replaceModule(_oldModuleImpl, _newModuleImpl);
}
/// @notice Add modules functions.
/// @param _implementationsFrom Modules to replace
/// @param _implementationsTo Replacing modules
function replaceModules(
address[] calldata _implementationsFrom,
address[] calldata _implementationsTo
) external onlyProxyOwner {
require(
_implementationsFrom.length == _implementationsTo.length,
"ModulesProxyRegistry::replaceModules: arrays sizes must be equal"
); //MR10
// because the order of addresses is arbitrary, all modules are removed first to avoid collisions
_removeModules(_implementationsFrom);
_addModules(_implementationsTo);
}
/// @notice To disable module - set all its functions implementation to address(0)
/// @param _impl implementation address
function removeModule(address _impl) external onlyProxyOwner {
_removeModule(_impl);
}
/// @notice Add modules functions.
/// @param _implementations Modules implementation addresses
function removeModules(address[] calldata _implementations) external onlyProxyOwner {
_removeModules(_implementations);
}
/// @param _sig Function signature to get impmementation address for
/// @return Function's contract implelementation address
function getFuncImplementation(bytes4 _sig) external view returns (address) {
return _getFuncImplementation(_sig);
}
/// @notice Verifies if no functions from the module already registered
/// @param _impl Module implementation address to verify
/// @return True if module can be added
function canAddModule(address _impl) external view returns (bool) {
return _canAddModule(_impl);
}
/// @notice Multiple modules verification if there are functions from the modules already registered
/// @param _implementations modules implementation addresses to verify
/// @return addresses of registered modules
function canNotAddModules(
address[] memory _implementations
) public view returns (address[] memory) {
for (uint256 i = 0; i < _implementations.length; i++) {
if (_canAddModule(_implementations[i])) {
delete _implementations[i];
}
}
return _implementations;
}
/// @notice Used externally to verify module being added for clashing
/// @param _newModule module implementation which functions to verify
/// @return Clashing functions signatures and corresponding modules (contracts) addresses
function checkClashingFuncSelectors(
address _newModule
)
external
view
returns (
address[] memory clashingModules,
bytes4[] memory clashingModulesFuncSelectors,
bytes4[] memory clashingProxyRegistryFuncSelectors
)
{
require(
_newModule.isContract(),
"ModulesProxyRegistry::checkClashingFuncSelectors: address is not a contract"
); //MR06
bytes4[] memory newModuleFunctions = IFunctionsList(_newModule).getFunctionsList();
bytes4[] memory proxyRegistryFunctions = _getFunctionsList(); //registry functions list
uint256 clashingProxyRegistryFuncsSize;
uint256 clashingArraySize;
uint256 clashingArrayIndex;
uint256 clashingRegistryArrayIndex;
for (uint256 i = 0; i < newModuleFunctions.length; i++) {
address funcImpl = _getFuncImplementation(newModuleFunctions[i]);
if (funcImpl != address(0) && funcImpl != _newModule) {
clashingArraySize++;
} else if (_isFuncClashingWithProxyFunctions(newModuleFunctions[i]))
clashingProxyRegistryFuncsSize++;
}
clashingModules = new address[](clashingArraySize);
clashingModulesFuncSelectors = new bytes4[](clashingArraySize);
clashingProxyRegistryFuncSelectors = new bytes4[](clashingProxyRegistryFuncsSize);
if (clashingArraySize == 0 && clashingProxyRegistryFuncsSize == 0)
//return empty arrays
return (
clashingModules,
clashingModulesFuncSelectors,
clashingProxyRegistryFuncSelectors
);
for (uint256 i = 0; i < newModuleFunctions.length; i++) {
address funcImpl = _getFuncImplementation(newModuleFunctions[i]);
if (funcImpl != address(0)) {
clashingModules[clashingArrayIndex] = funcImpl;
clashingModulesFuncSelectors[clashingArrayIndex] = newModuleFunctions[i];
clashingArrayIndex++;
}
for (uint256 j = 0; j < proxyRegistryFunctions.length; j++) {
//ModulesProxyRegistry has a clashing function selector
if (proxyRegistryFunctions[j] == newModuleFunctions[i]) {
clashingProxyRegistryFuncSelectors[
clashingRegistryArrayIndex
] = proxyRegistryFunctions[j];
clashingRegistryArrayIndex++;
}
}
}
}
/// Verifies the deployed contract address is a registered module contract
/// @param _impl deployment address to verify
/// @return true if _impl address is a registered module
function isModuleRegistered(address _impl) external view returns (bool) {
return _getFirstRegisteredModuleAddress(_impl) == _impl;
}
/****************** INTERNAL FUNCTIONS ******************/
function _getFirstRegisteredModuleAddress(address _impl) internal view returns (address) {
require(
_impl.isContract(),
"ModulesProxyRegistry::_getRegisteredModuleAddress: address is not a contract"
);
bytes4[] memory functions = IFunctionsList(_impl).getFunctionsList();
for (uint256 i = 0; i < functions.length; i++) {
address _moduleImpl = _getFuncImplementation(functions[i]);
if (_moduleImpl != address(0)) {
return (_moduleImpl);
}
}
return address(0);
}
function _getFuncImplementation(bytes4 _sig) internal view returns (address) {
//TODO: add querying Registry for logic address and then delegate call to it OR use proxy memory slots like this:
bytes32 key = keccak256(abi.encode(_sig, KEY_IMPLEMENTATION));
address implementation;
assembly {
implementation := sload(key)
}
return implementation;
}
function _addModule(address _impl) internal {
require(_impl.isContract(), "ModulesProxyRegistry::_addModule: address is not a contract"); //MR01
bytes4[] memory functions = IFunctionsList(_impl).getFunctionsList();
for (uint256 i = 0; i < functions.length; i++) {
require(
_getFuncImplementation(functions[i]) == address(0),
"ModulesProxyRegistry::_addModule: function already registered - use replaceModule function"
); //MR02
require(functions[i] != bytes4(0), "does not allow empty function id"); // MR03
require(
!_isFuncClashingWithProxyFunctions(functions[i]),
"ModulesProxyRegistry::_addModule: has a function with the same signature"
); //MR09
_setModuleFuncImplementation(functions[i], _impl);
}
emit AddModule(_impl);
}
function _addModules(address[] memory _implementations) internal {
for (uint256 i = 0; i < _implementations.length; i++) {
_addModule(_implementations[i]);
}
}
function _removeModule(address _impl) internal onlyProxyOwner {
require(
_impl.isContract(),
"ModulesProxyRegistry::_removeModule: address is not a contract"
); //MR07
bytes4[] memory functions = IFunctionsList(_impl).getFunctionsList();
for (uint256 i = 0; i < functions.length; i++)
_setModuleFuncImplementation(functions[i], address(0));
emit RemoveModule(_impl);
}
function _removeModules(address[] memory _implementations) internal {
for (uint256 i = 0; i < _implementations.length; i++) {
_removeModule(_implementations[i]);
}
}
function _replaceModule(address _oldModuleImpl, address _newModuleImpl) internal {
if (_oldModuleImpl != _newModuleImpl) {
require(
_newModuleImpl.isContract(),
"ModulesProxyRegistry::_replaceModule - _newModuleImpl is not a contract"
); //MR03
require(
_oldModuleImpl.isContract(),
"ModulesProxyRegistry::_replaceModule - _oldModuleImpl is not a contract"
); //MR04
_removeModule(_oldModuleImpl);
_addModule(_newModuleImpl);
emit ReplaceModule(_oldModuleImpl, _newModuleImpl);
}
}
function _setModuleFuncImplementation(bytes4 _sig, address _impl) internal {
emit SetModuleFuncImplementation(_sig, _getFuncImplementation(_sig), _impl);
bytes32 key = keccak256(abi.encode(_sig, KEY_IMPLEMENTATION));
assembly {
sstore(key, _impl)
}
}
function _isFuncClashingWithProxyFunctions(bytes4 _sig) internal pure returns (bool) {
bytes4[] memory functionList = _getFunctionsList();
for (uint256 i = 0; i < functionList.length; i++) {
if (_sig == functionList[i])
//ModulesProxyRegistry has function with the same id
return true;
}
return false;
}
function _canAddModule(address _impl) internal view returns (bool) {
require(
_impl.isContract(),
"ModulesProxyRegistry::_canAddModule: address is not a contract"
); //MR06
bytes4[] memory functions = IFunctionsList(_impl).getFunctionsList();
for (uint256 i = 0; i < functions.length; i++)
if (_getFuncImplementation(functions[i]) != address(0)) return (false);
return true;
}
function _getFunctionsList() internal pure returns (bytes4[] memory) {
bytes4[] memory functionList = new bytes4[](13);
functionList[0] = this.getFuncImplementation.selector;
functionList[1] = this.addModule.selector;
functionList[2] = this.addModules.selector;
functionList[3] = this.removeModule.selector;
functionList[4] = this.removeModules.selector;
functionList[5] = this.replaceModule.selector;
functionList[6] = this.replaceModules.selector;
functionList[7] = this.canAddModule.selector;
functionList[8] = this.canNotAddModules.selector;
functionList[9] = this.setProxyOwner.selector;
functionList[10] = this.getProxyOwner.selector;
functionList[11] = this.checkClashingFuncSelectors.selector;
functionList[12] = this.isModuleRegistered.selector;
return functionList;
}
}