@bloxchain/contracts 1.0.0-alpha.18 → 1.0.0-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abi/GuardControllerDefinitions.abi.json +13 -0
- package/core/access/RuntimeRBAC.sol +1 -1
- package/core/execution/lib/definitions/GuardControllerDefinitions.sol +25 -7
- package/core/lib/EngineBlox.sol +20 -0
- package/core/pattern/Account.sol +10 -1
- package/core/security/SecureOwnable.sol +424 -394
- package/package.json +1 -1
|
@@ -51,6 +51,19 @@
|
|
|
51
51
|
"stateMutability": "view",
|
|
52
52
|
"type": "function"
|
|
53
53
|
},
|
|
54
|
+
{
|
|
55
|
+
"inputs": [],
|
|
56
|
+
"name": "CONTROLLER_CONFIG_OPERATION",
|
|
57
|
+
"outputs": [
|
|
58
|
+
{
|
|
59
|
+
"internalType": "bytes32",
|
|
60
|
+
"name": "",
|
|
61
|
+
"type": "bytes32"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"stateMutability": "view",
|
|
65
|
+
"type": "function"
|
|
66
|
+
},
|
|
54
67
|
{
|
|
55
68
|
"inputs": [],
|
|
56
69
|
"name": "CONTROLLER_OPERATION",
|
|
@@ -134,7 +134,7 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
134
134
|
function _executeRoleConfigBatch(IRuntimeRBAC.RoleConfigAction[] calldata actions) internal {
|
|
135
135
|
_validateBatchSize(actions.length);
|
|
136
136
|
|
|
137
|
-
for (uint256 i = 0; i < actions.length; i
|
|
137
|
+
for (uint256 i = 0; i < actions.length; ++i) {
|
|
138
138
|
IRuntimeRBAC.RoleConfigAction calldata action = actions[i];
|
|
139
139
|
|
|
140
140
|
if (action.actionType == IRuntimeRBAC.RoleConfigActionType.CREATE_ROLE) {
|
|
@@ -16,7 +16,7 @@ import "../../interface/IGuardController.sol";
|
|
|
16
16
|
* and role permissions for GuardController's public execution functions.
|
|
17
17
|
*
|
|
18
18
|
* Key Features:
|
|
19
|
-
* - Registers all
|
|
19
|
+
* - Registers all 9 GuardController public execution functions
|
|
20
20
|
* - Defines role permissions for OWNER_ROLE and BROADCASTER_ROLE
|
|
21
21
|
* - Supports time-delay and meta-transaction workflows
|
|
22
22
|
* - Matches EngineBloxDefinitions pattern for consistency
|
|
@@ -33,6 +33,8 @@ library GuardControllerDefinitions {
|
|
|
33
33
|
|
|
34
34
|
// Operation Type Constants
|
|
35
35
|
bytes32 public constant CONTROLLER_OPERATION = keccak256("CONTROLLER_OPERATION");
|
|
36
|
+
// Guard config batch only (whitelist / register-unregister function); distinct execution operation type bitmap.
|
|
37
|
+
bytes32 public constant CONTROLLER_CONFIG_OPERATION = keccak256("CONTROLLER_CONFIG_OPERATION");
|
|
36
38
|
|
|
37
39
|
// Function Selector Constants
|
|
38
40
|
// GuardController: executeWithTimeLock(address,uint256,bytes4,bytes,uint256,bytes32)
|
|
@@ -86,7 +88,7 @@ library GuardControllerDefinitions {
|
|
|
86
88
|
*
|
|
87
89
|
* Function schemas define:
|
|
88
90
|
* - GuardController public execution functions
|
|
89
|
-
* - What operation types they belong to (CONTROLLER_OPERATION)
|
|
91
|
+
* - What operation types they belong to (CONTROLLER_OPERATION vs CONTROLLER_CONFIG_OPERATION)
|
|
90
92
|
* - What actions are supported (time-delay request/approve/cancel, meta-tx approve/cancel/request-and-approve)
|
|
91
93
|
* - Whether they are protected
|
|
92
94
|
*
|
|
@@ -96,7 +98,7 @@ library GuardControllerDefinitions {
|
|
|
96
98
|
* - Role permissions are defined in getRolePermissions() matching EngineBloxDefinitions pattern
|
|
97
99
|
*/
|
|
98
100
|
function getFunctionSchemas() public pure returns (EngineBlox.FunctionSchema[] memory) {
|
|
99
|
-
EngineBlox.FunctionSchema[] memory schemas = new EngineBlox.FunctionSchema[](
|
|
101
|
+
EngineBlox.FunctionSchema[] memory schemas = new EngineBlox.FunctionSchema[](9);
|
|
100
102
|
|
|
101
103
|
// ============ TIME-DELAY WORKFLOW ACTIONS ============
|
|
102
104
|
// Request action for executeWithTimeLock
|
|
@@ -144,6 +146,9 @@ library GuardControllerDefinitions {
|
|
|
144
146
|
requestAndApproveExecutionHandlerForSelectors[0] = REQUEST_AND_APPROVE_EXECUTION_SELECTOR;
|
|
145
147
|
bytes4[] memory guardConfigBatchExecuteHandlerForSelectors = new bytes4[](1);
|
|
146
148
|
guardConfigBatchExecuteHandlerForSelectors[0] = GUARD_CONFIG_BATCH_EXECUTE_SELECTOR;
|
|
149
|
+
|
|
150
|
+
bytes4[] memory executeWithPaymentHandlerForSelectors = new bytes4[](1);
|
|
151
|
+
executeWithPaymentHandlerForSelectors[0] = EXECUTE_WITH_PAYMENT_SELECTOR;
|
|
147
152
|
|
|
148
153
|
// Handler selectors point to execution selectors
|
|
149
154
|
bytes4[] memory guardConfigHandlerForSelectors = new bytes4[](1);
|
|
@@ -225,8 +230,8 @@ library GuardControllerDefinitions {
|
|
|
225
230
|
schemas[6] = EngineBlox.FunctionSchema({
|
|
226
231
|
functionSignature: "guardConfigBatchRequestAndApprove(((uint256,uint256,uint8,(address,address,uint256,uint256,bytes32,bytes4,bytes),bytes32,bytes,(address,uint256,address,uint256)),(uint256,uint256,address,bytes4,uint8,uint256,uint256,address),bytes32,bytes,bytes))",
|
|
227
232
|
functionSelector: GUARD_CONFIG_BATCH_META_SELECTOR,
|
|
228
|
-
operationType:
|
|
229
|
-
operationName: "
|
|
233
|
+
operationType: CONTROLLER_CONFIG_OPERATION,
|
|
234
|
+
operationName: "CONTROLLER_CONFIG_OPERATION",
|
|
230
235
|
supportedActionsBitmap: EngineBlox.createBitmapFromActions(metaTxRequestApproveActions),
|
|
231
236
|
enforceHandlerRelations: true,
|
|
232
237
|
isProtected: true,
|
|
@@ -241,14 +246,27 @@ library GuardControllerDefinitions {
|
|
|
241
246
|
schemas[7] = EngineBlox.FunctionSchema({
|
|
242
247
|
functionSignature: "executeGuardConfigBatch((uint8,bytes)[])",
|
|
243
248
|
functionSelector: GUARD_CONFIG_BATCH_EXECUTE_SELECTOR,
|
|
244
|
-
operationType:
|
|
245
|
-
operationName: "
|
|
249
|
+
operationType: CONTROLLER_CONFIG_OPERATION,
|
|
250
|
+
operationName: "CONTROLLER_CONFIG_OPERATION",
|
|
246
251
|
supportedActionsBitmap: EngineBlox.createBitmapFromActions(guardConfigExecutionActions),
|
|
247
252
|
enforceHandlerRelations: false,
|
|
248
253
|
isProtected: true,
|
|
249
254
|
handlerForSelectors: guardConfigBatchExecuteHandlerForSelectors
|
|
250
255
|
});
|
|
251
256
|
|
|
257
|
+
// Schema 8: GuardController.executeWithPayment (same time-delay request action as executeWithTimeLock;
|
|
258
|
+
// OWNER_ROLE grant for this selector may be added manually if the flow is enabled)
|
|
259
|
+
schemas[8] = EngineBlox.FunctionSchema({
|
|
260
|
+
functionSignature: "executeWithPayment(address,uint256,bytes4,bytes,uint256,bytes32,(address,uint256,address,uint256))",
|
|
261
|
+
functionSelector: EXECUTE_WITH_PAYMENT_SELECTOR,
|
|
262
|
+
operationType: CONTROLLER_OPERATION,
|
|
263
|
+
operationName: "CONTROLLER_OPERATION",
|
|
264
|
+
supportedActionsBitmap: EngineBlox.createBitmapFromActions(timeDelayRequestActions),
|
|
265
|
+
enforceHandlerRelations: false,
|
|
266
|
+
isProtected: true,
|
|
267
|
+
handlerForSelectors: executeWithPaymentHandlerForSelectors
|
|
268
|
+
});
|
|
269
|
+
|
|
252
270
|
return schemas;
|
|
253
271
|
}
|
|
254
272
|
|
package/core/lib/EngineBlox.sol
CHANGED
|
@@ -376,6 +376,16 @@ library EngineBlox {
|
|
|
376
376
|
// Validate both execution and handler selector permissions (same as txRequest)
|
|
377
377
|
_validateExecutionAndHandlerPermissions(self, msg.sender, executionSelector, handlerSelector, TxAction.EXECUTE_TIME_DELAY_REQUEST);
|
|
378
378
|
|
|
379
|
+
// Request-time validation for attached payment details.
|
|
380
|
+
// This prevents creating persistent PENDING records that later fail during
|
|
381
|
+
// `executeAttachedPayment` due to missing/zero payment fields.
|
|
382
|
+
if (paymentDetails.nativeTokenAmount > 0 || paymentDetails.erc20TokenAmount > 0) {
|
|
383
|
+
SharedValidation.validateNotZeroAddress(paymentDetails.recipient);
|
|
384
|
+
}
|
|
385
|
+
if (paymentDetails.erc20TokenAmount > 0) {
|
|
386
|
+
SharedValidation.validateNotZeroAddress(paymentDetails.erc20TokenAddress);
|
|
387
|
+
}
|
|
388
|
+
|
|
379
389
|
return _txRequest(
|
|
380
390
|
self,
|
|
381
391
|
requester,
|
|
@@ -574,6 +584,16 @@ library EngineBlox {
|
|
|
574
584
|
// Validate both execution and handler selector permissions
|
|
575
585
|
_validateExecutionAndHandlerPermissions(self, msg.sender, metaTx.txRecord.params.executionSelector, metaTx.params.handlerSelector, TxAction.EXECUTE_META_REQUEST_AND_APPROVE);
|
|
576
586
|
|
|
587
|
+
// Request-time validation for attached payment details.
|
|
588
|
+
// `requestAndApprove` creates the request and executes via the same meta-tx flow,
|
|
589
|
+
// so we validate here to avoid persisting bad PENDING records.
|
|
590
|
+
if (metaTx.txRecord.payment.nativeTokenAmount > 0 || metaTx.txRecord.payment.erc20TokenAmount > 0) {
|
|
591
|
+
SharedValidation.validateNotZeroAddress(metaTx.txRecord.payment.recipient);
|
|
592
|
+
}
|
|
593
|
+
if (metaTx.txRecord.payment.erc20TokenAmount > 0) {
|
|
594
|
+
SharedValidation.validateNotZeroAddress(metaTx.txRecord.payment.erc20TokenAddress);
|
|
595
|
+
}
|
|
596
|
+
|
|
577
597
|
TxRecord memory txRecord = _txRequest(
|
|
578
598
|
self,
|
|
579
599
|
metaTx.txRecord.params.requester,
|
package/core/pattern/Account.sol
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
pragma solidity 0.8.34;
|
|
3
3
|
|
|
4
4
|
import "../execution/GuardController.sol";
|
|
5
|
+
import "../execution/interface/IGuardController.sol";
|
|
5
6
|
import "../access/RuntimeRBAC.sol";
|
|
7
|
+
import "../access/interface/IRuntimeRBAC.sol";
|
|
6
8
|
import "../security/SecureOwnable.sol";
|
|
9
|
+
import "../security/interface/ISecureOwnable.sol";
|
|
7
10
|
import "../lib/utils/SharedValidation.sol";
|
|
8
11
|
|
|
9
12
|
/**
|
|
@@ -51,9 +54,15 @@ abstract contract Account is GuardController, RuntimeRBAC, SecureOwnable {
|
|
|
51
54
|
|
|
52
55
|
/**
|
|
53
56
|
* @dev See {IERC165-supportsInterface}.
|
|
57
|
+
* @notice GuardController, RuntimeRBAC, and SecureOwnable each extend BaseStateMachine directly; a single
|
|
58
|
+
* `super` chain only walks one branch. We OR the three component interface IDs here, then delegate
|
|
59
|
+
* once to `super` for IBaseStateMachine / ERC165 — avoids tripling BaseStateMachine+ERC165 work.
|
|
54
60
|
*/
|
|
55
61
|
function supportsInterface(bytes4 interfaceId) public view virtual override(GuardController, RuntimeRBAC, SecureOwnable) returns (bool) {
|
|
56
|
-
return
|
|
62
|
+
return interfaceId == type(IGuardController).interfaceId
|
|
63
|
+
|| interfaceId == type(IRuntimeRBAC).interfaceId
|
|
64
|
+
|| interfaceId == type(ISecureOwnable).interfaceId
|
|
65
|
+
|| super.supportsInterface(interfaceId);
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
/**
|
|
@@ -1,394 +1,424 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
pragma solidity 0.8.34;
|
|
3
|
-
|
|
4
|
-
// Contracts imports
|
|
5
|
-
import "../base/BaseStateMachine.sol";
|
|
6
|
-
import "./lib/definitions/SecureOwnableDefinitions.sol";
|
|
7
|
-
import "../lib/interfaces/IDefinition.sol";
|
|
8
|
-
import "../lib/utils/SharedValidation.sol";
|
|
9
|
-
import "./interface/ISecureOwnable.sol";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @title SecureOwnable
|
|
13
|
-
* @dev Security-focused contract extending BaseStateMachine with ownership management
|
|
14
|
-
*
|
|
15
|
-
* SecureOwnable provides security-specific functionality built on top of the base state machine:
|
|
16
|
-
* - Multi-role security model with Owner, Broadcaster, and Recovery roles
|
|
17
|
-
* - Secure ownership transfer with time-locked operations
|
|
18
|
-
* - Broadcaster and recovery address management
|
|
19
|
-
* - Time-lock period configuration
|
|
20
|
-
*
|
|
21
|
-
* The contract implements four primary secure operation types:
|
|
22
|
-
* 1. OWNERSHIP_TRANSFER - For securely transferring contract ownership
|
|
23
|
-
* 2. BROADCASTER_UPDATE - For changing the broadcaster address
|
|
24
|
-
* 3. RECOVERY_UPDATE - For updating the recovery address
|
|
25
|
-
* 4. TIMELOCK_UPDATE - For modifying the time lock period
|
|
26
|
-
*
|
|
27
|
-
* Each operation follows a request -> approval workflow with appropriate time locks
|
|
28
|
-
* and authorization checks. Operations can be cancelled within specific time windows.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* @
|
|
47
|
-
* @param
|
|
48
|
-
* @param
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
address
|
|
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
|
-
0
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
* @
|
|
312
|
-
* @
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
pragma solidity 0.8.34;
|
|
3
|
+
|
|
4
|
+
// Contracts imports
|
|
5
|
+
import "../base/BaseStateMachine.sol";
|
|
6
|
+
import "./lib/definitions/SecureOwnableDefinitions.sol";
|
|
7
|
+
import "../lib/interfaces/IDefinition.sol";
|
|
8
|
+
import "../lib/utils/SharedValidation.sol";
|
|
9
|
+
import "./interface/ISecureOwnable.sol";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @title SecureOwnable
|
|
13
|
+
* @dev Security-focused contract extending BaseStateMachine with ownership management
|
|
14
|
+
*
|
|
15
|
+
* SecureOwnable provides security-specific functionality built on top of the base state machine:
|
|
16
|
+
* - Multi-role security model with Owner, Broadcaster, and Recovery roles
|
|
17
|
+
* - Secure ownership transfer with time-locked operations
|
|
18
|
+
* - Broadcaster and recovery address management
|
|
19
|
+
* - Time-lock period configuration
|
|
20
|
+
*
|
|
21
|
+
* The contract implements four primary secure operation types:
|
|
22
|
+
* 1. OWNERSHIP_TRANSFER - For securely transferring contract ownership
|
|
23
|
+
* 2. BROADCASTER_UPDATE - For changing the broadcaster address
|
|
24
|
+
* 3. RECOVERY_UPDATE - For updating the recovery address
|
|
25
|
+
* 4. TIMELOCK_UPDATE - For modifying the time lock period
|
|
26
|
+
*
|
|
27
|
+
* Each operation follows a request -> approval workflow with appropriate time locks
|
|
28
|
+
* and authorization checks. Operations can be cancelled within specific time windows.
|
|
29
|
+
*
|
|
30
|
+
* Pending secure requests use separate flags for ownership transfer and broadcaster update.
|
|
31
|
+
* A new ownership-transfer request is allowed if no ownership transfer is already pending
|
|
32
|
+
* (a broadcaster update may still be pending). A new broadcaster-update request is allowed only
|
|
33
|
+
* when neither type has a pending request.
|
|
34
|
+
*
|
|
35
|
+
* This contract focuses purely on security logic while leveraging the BaseStateMachine
|
|
36
|
+
* for transaction management, meta-transactions, and state machine operations.
|
|
37
|
+
*/
|
|
38
|
+
abstract contract SecureOwnable is BaseStateMachine, ISecureOwnable {
|
|
39
|
+
using SharedValidation for *;
|
|
40
|
+
|
|
41
|
+
/// @dev Tracks pending secure txs by type. Upgrading from legacy `_hasOpenRequest` / `_pendingBits` requires no pending requests.
|
|
42
|
+
bool private _hasOpenOwnershipRequest;
|
|
43
|
+
bool private _hasOpenBroadcasterRequest;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @notice Initializer to initialize SecureOwnable state
|
|
47
|
+
* @param initialOwner The initial owner address
|
|
48
|
+
* @param broadcaster The broadcaster address
|
|
49
|
+
* @param recovery The recovery address
|
|
50
|
+
* @param timeLockPeriodSec The timelock period in seconds
|
|
51
|
+
* @param eventForwarder The event forwarder address
|
|
52
|
+
*/
|
|
53
|
+
function initialize(
|
|
54
|
+
address initialOwner,
|
|
55
|
+
address broadcaster,
|
|
56
|
+
address recovery,
|
|
57
|
+
uint256 timeLockPeriodSec,
|
|
58
|
+
address eventForwarder
|
|
59
|
+
) public virtual onlyInitializing {
|
|
60
|
+
_initializeBaseStateMachine(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
61
|
+
|
|
62
|
+
// Load SecureOwnable-specific definitions
|
|
63
|
+
IDefinition.RolePermission memory secureOwnablePermissions = SecureOwnableDefinitions.getRolePermissions();
|
|
64
|
+
_loadDefinitions(
|
|
65
|
+
SecureOwnableDefinitions.getFunctionSchemas(),
|
|
66
|
+
secureOwnablePermissions.roleHashes,
|
|
67
|
+
secureOwnablePermissions.functionPermissions,
|
|
68
|
+
true // Enforce all function schemas are protected
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============ INTERFACE SUPPORT ============
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @dev See {IERC165-supportsInterface}.
|
|
76
|
+
* @notice Adds ISecureOwnable interface ID for component detection
|
|
77
|
+
*/
|
|
78
|
+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
|
79
|
+
return interfaceId == type(ISecureOwnable).interfaceId || super.supportsInterface(interfaceId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Ownership Management
|
|
83
|
+
/**
|
|
84
|
+
* @dev Requests a transfer of ownership
|
|
85
|
+
* @return txId The transaction ID (use getTransaction(txId) for full record)
|
|
86
|
+
*/
|
|
87
|
+
function transferOwnershipRequest() public returns (uint256 txId) {
|
|
88
|
+
SharedValidation.validateRecovery(getRecovery());
|
|
89
|
+
_requireNoPendingRequest(SecureOwnableDefinitions.OWNERSHIP_TRANSFER);
|
|
90
|
+
|
|
91
|
+
EngineBlox.TxRecord memory txRecord = _requestTransaction(
|
|
92
|
+
msg.sender,
|
|
93
|
+
address(this),
|
|
94
|
+
0, // value
|
|
95
|
+
0, // no gas limit
|
|
96
|
+
SecureOwnableDefinitions.OWNERSHIP_TRANSFER,
|
|
97
|
+
SecureOwnableDefinitions.TRANSFER_OWNERSHIP_SELECTOR,
|
|
98
|
+
abi.encode(getRecovery())
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
_hasOpenOwnershipRequest = true;
|
|
102
|
+
_logAddressPairEvent(owner(), getRecovery());
|
|
103
|
+
return txRecord.txId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @dev Approves a pending ownership transfer transaction after the release time
|
|
108
|
+
* @param txId The transaction ID
|
|
109
|
+
* @return The transaction ID
|
|
110
|
+
*/
|
|
111
|
+
function transferOwnershipDelayedApproval(uint256 txId) public returns (uint256) {
|
|
112
|
+
SharedValidation.validateOwnerOrRecovery(owner(), getRecovery());
|
|
113
|
+
return _completeApprove(_approveTransaction(txId));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @dev Approves a pending ownership transfer transaction using a meta-transaction
|
|
118
|
+
* @param metaTx The meta-transaction
|
|
119
|
+
* @return The transaction ID
|
|
120
|
+
*/
|
|
121
|
+
function transferOwnershipApprovalWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
122
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
123
|
+
return _completeApprove(_approveTransactionWithMetaTx(metaTx));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @dev Cancels a pending ownership transfer transaction
|
|
128
|
+
* @param txId The transaction ID
|
|
129
|
+
* @return The transaction ID
|
|
130
|
+
*/
|
|
131
|
+
function transferOwnershipCancellation(uint256 txId) public returns (uint256) {
|
|
132
|
+
SharedValidation.validateRecovery(getRecovery());
|
|
133
|
+
return _completeCancel(_cancelTransaction(txId));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @dev Cancels a pending ownership transfer transaction using a meta-transaction
|
|
138
|
+
* @param metaTx The meta-transaction
|
|
139
|
+
* @return The transaction ID
|
|
140
|
+
*/
|
|
141
|
+
function transferOwnershipCancellationWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
142
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
143
|
+
return _completeCancel(_cancelTransactionWithMetaTx(metaTx));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Broadcaster Management
|
|
147
|
+
/**
|
|
148
|
+
* @dev Requests an update to the broadcaster at a specific location (index).
|
|
149
|
+
* @notice Requires no pending broadcaster-update and no pending ownership-transfer request.
|
|
150
|
+
* @param newBroadcaster The new broadcaster address (zero address to revoke at location)
|
|
151
|
+
* @param location The index in the broadcaster role's authorized wallets set
|
|
152
|
+
* @return txId The transaction ID for the pending request (use getTransaction(txId) for full record)
|
|
153
|
+
*/
|
|
154
|
+
function updateBroadcasterRequest(address newBroadcaster, uint256 location) public returns (uint256 txId) {
|
|
155
|
+
SharedValidation.validateOwner(owner());
|
|
156
|
+
_requireNoPendingRequest(SecureOwnableDefinitions.BROADCASTER_UPDATE);
|
|
157
|
+
_requireNoPendingRequest(SecureOwnableDefinitions.OWNERSHIP_TRANSFER);
|
|
158
|
+
|
|
159
|
+
// Get the current broadcaster at the specified location. zero address if no broadcaster at location.
|
|
160
|
+
address currentBroadcaster = location < _getSecureState().roles[EngineBlox.BROADCASTER_ROLE].walletCount
|
|
161
|
+
? _getAuthorizedWalletAt(EngineBlox.BROADCASTER_ROLE, location)
|
|
162
|
+
: address(0);
|
|
163
|
+
|
|
164
|
+
EngineBlox.TxRecord memory txRecord = _requestTransaction(
|
|
165
|
+
msg.sender,
|
|
166
|
+
address(this),
|
|
167
|
+
0, // value
|
|
168
|
+
0, // gas limit
|
|
169
|
+
SecureOwnableDefinitions.BROADCASTER_UPDATE,
|
|
170
|
+
SecureOwnableDefinitions.UPDATE_BROADCASTER_SELECTOR,
|
|
171
|
+
abi.encode(newBroadcaster, location)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
_hasOpenBroadcasterRequest = true;
|
|
175
|
+
_logAddressPairEvent(currentBroadcaster, newBroadcaster);
|
|
176
|
+
return txRecord.txId;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @dev Approves a pending broadcaster update transaction after the release time
|
|
181
|
+
* @param txId The transaction ID
|
|
182
|
+
* @return The transaction ID
|
|
183
|
+
*/
|
|
184
|
+
function updateBroadcasterDelayedApproval(uint256 txId) public returns (uint256) {
|
|
185
|
+
SharedValidation.validateOwner(owner());
|
|
186
|
+
return _completeApprove(_approveTransaction(txId));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @dev Approves a pending broadcaster update transaction using a meta-transaction
|
|
191
|
+
* @param metaTx The meta-transaction
|
|
192
|
+
* @return The transaction ID
|
|
193
|
+
*/
|
|
194
|
+
function updateBroadcasterApprovalWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
195
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
196
|
+
return _completeApprove(_approveTransactionWithMetaTx(metaTx));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @dev Cancels a pending broadcaster update transaction
|
|
201
|
+
* @param txId The transaction ID
|
|
202
|
+
* @return The transaction ID
|
|
203
|
+
*/
|
|
204
|
+
function updateBroadcasterCancellation(uint256 txId) public returns (uint256) {
|
|
205
|
+
SharedValidation.validateOwner(owner());
|
|
206
|
+
return _completeCancel(_cancelTransaction(txId));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @dev Cancels a pending broadcaster update transaction using a meta-transaction
|
|
211
|
+
* @param metaTx The meta-transaction
|
|
212
|
+
* @return The transaction ID
|
|
213
|
+
*/
|
|
214
|
+
function updateBroadcasterCancellationWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
215
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
216
|
+
return _completeCancel(_cancelTransactionWithMetaTx(metaTx));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Recovery Management
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @dev Requests and approves a recovery address update using a meta-transaction
|
|
223
|
+
* @param metaTx The meta-transaction
|
|
224
|
+
* @return The transaction ID
|
|
225
|
+
*/
|
|
226
|
+
function updateRecoveryRequestAndApprove(
|
|
227
|
+
EngineBlox.MetaTransaction memory metaTx
|
|
228
|
+
) public returns (uint256) {
|
|
229
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
230
|
+
EngineBlox.TxRecord memory txRecord = _requestAndApproveTransaction(metaTx);
|
|
231
|
+
return txRecord.txId;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// TimeLock Management
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @dev Requests and approves a time lock period update using a meta-transaction
|
|
238
|
+
* @param metaTx The meta-transaction
|
|
239
|
+
* @return The transaction ID
|
|
240
|
+
*/
|
|
241
|
+
function updateTimeLockRequestAndApprove(
|
|
242
|
+
EngineBlox.MetaTransaction memory metaTx
|
|
243
|
+
) public returns (uint256) {
|
|
244
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
245
|
+
EngineBlox.TxRecord memory txRecord = _requestAndApproveTransaction(metaTx);
|
|
246
|
+
return txRecord.txId;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Execution Functions
|
|
250
|
+
/**
|
|
251
|
+
* @dev External function that can only be called by the contract itself to execute ownership transfer
|
|
252
|
+
* @param newOwner The new owner address
|
|
253
|
+
*/
|
|
254
|
+
function executeTransferOwnership(address newOwner) external {
|
|
255
|
+
_validateExecuteBySelf();
|
|
256
|
+
_transferOwnership(newOwner);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @dev External function that can only be called by the contract itself to execute broadcaster update
|
|
261
|
+
* @param newBroadcaster The new broadcaster address (zero address to revoke at location)
|
|
262
|
+
* @param location The index in the broadcaster role's authorized wallets set
|
|
263
|
+
*/
|
|
264
|
+
function executeBroadcasterUpdate(address newBroadcaster, uint256 location) external {
|
|
265
|
+
_validateExecuteBySelf();
|
|
266
|
+
_updateBroadcaster(newBroadcaster, location);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @dev External function that can only be called by the contract itself to execute recovery update
|
|
271
|
+
* @param newRecoveryAddress The new recovery address
|
|
272
|
+
*/
|
|
273
|
+
function executeRecoveryUpdate(address newRecoveryAddress) external {
|
|
274
|
+
_validateExecuteBySelf();
|
|
275
|
+
_updateRecoveryAddress(newRecoveryAddress);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @dev External function that can only be called by the contract itself to execute timelock update
|
|
280
|
+
* @param newTimeLockPeriodSec The new timelock period in seconds
|
|
281
|
+
*/
|
|
282
|
+
function executeTimeLockUpdate(uint256 newTimeLockPeriodSec) external {
|
|
283
|
+
_validateExecuteBySelf();
|
|
284
|
+
_updateTimeLockPeriod(newTimeLockPeriodSec);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============ INTERNAL FUNCTIONS ============
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @dev Validates that the caller is the broadcaster and that the meta-tx signer is the owner.
|
|
292
|
+
* @param metaTx The meta-transaction to validate
|
|
293
|
+
*/
|
|
294
|
+
function _validateBroadcasterAndOwnerSigner(EngineBlox.MetaTransaction memory metaTx) internal view {
|
|
295
|
+
_validateBroadcaster(msg.sender);
|
|
296
|
+
SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @dev Completes ownership/broadcaster flow after approval: clears the matching pending flag and returns txId.
|
|
301
|
+
* @param updatedRecord The updated transaction record from approval
|
|
302
|
+
* @return txId The transaction ID
|
|
303
|
+
*/
|
|
304
|
+
function _completeApprove(EngineBlox.TxRecord memory updatedRecord) internal returns (uint256 txId) {
|
|
305
|
+
_clearPendingFlagForOperation(updatedRecord.params.operationType);
|
|
306
|
+
return updatedRecord.txId;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @dev Completes ownership/broadcaster flow after cancellation: clears the matching pending flag and returns txId.
|
|
311
|
+
* @param updatedRecord The updated transaction record from cancellation
|
|
312
|
+
* @return txId The transaction ID
|
|
313
|
+
*/
|
|
314
|
+
function _completeCancel(EngineBlox.TxRecord memory updatedRecord) internal returns (uint256 txId) {
|
|
315
|
+
_clearPendingFlagForOperation(updatedRecord.params.operationType);
|
|
316
|
+
return updatedRecord.txId;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @dev Reverts if the pending flag for `requestOperationType` is already set (one lane per call).
|
|
321
|
+
* `OWNERSHIP_TRANSFER` checks only `_hasOpenOwnershipRequest` (a broadcaster update may still be pending).
|
|
322
|
+
* `BROADCASTER_UPDATE` checks only `_hasOpenBroadcasterRequest`. Callers that need both lanes idle
|
|
323
|
+
* (e.g. `updateBroadcasterRequest`) invoke this once per operation type.
|
|
324
|
+
* @param requestOperationType Lane to validate (`OWNERSHIP_TRANSFER` or `BROADCASTER_UPDATE`).
|
|
325
|
+
*/
|
|
326
|
+
function _requireNoPendingRequest(bytes32 requestOperationType) internal view {
|
|
327
|
+
if (requestOperationType == SecureOwnableDefinitions.OWNERSHIP_TRANSFER) {
|
|
328
|
+
if (_hasOpenOwnershipRequest) revert SharedValidation.PendingSecureRequest();
|
|
329
|
+
} else if (requestOperationType == SecureOwnableDefinitions.BROADCASTER_UPDATE) {
|
|
330
|
+
if (_hasOpenBroadcasterRequest) revert SharedValidation.PendingSecureRequest();
|
|
331
|
+
} else {
|
|
332
|
+
revert();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @dev Clears the pending flag for a completed or cancelled secure op (approve/cancel paths).
|
|
338
|
+
* @param operationType The tx record's `operationType` (`OWNERSHIP_TRANSFER` or `BROADCASTER_UPDATE`).
|
|
339
|
+
*/
|
|
340
|
+
function _clearPendingFlagForOperation(bytes32 operationType) private {
|
|
341
|
+
if (operationType == SecureOwnableDefinitions.OWNERSHIP_TRANSFER) {
|
|
342
|
+
_hasOpenOwnershipRequest = false;
|
|
343
|
+
} else if (operationType == SecureOwnableDefinitions.BROADCASTER_UPDATE) {
|
|
344
|
+
_hasOpenBroadcasterRequest = false;
|
|
345
|
+
} else {
|
|
346
|
+
revert();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @dev Transfers ownership of the contract
|
|
352
|
+
* @param newOwner The new owner of the contract
|
|
353
|
+
*/
|
|
354
|
+
function _transferOwnership(address newOwner) internal virtual {
|
|
355
|
+
address oldOwner = owner();
|
|
356
|
+
_updateWallet(EngineBlox.OWNER_ROLE, newOwner, oldOwner);
|
|
357
|
+
_logAddressPairEvent(oldOwner, newOwner);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* @dev Updates the broadcaster role at a specific index (location)
|
|
362
|
+
* @param newBroadcaster The new broadcaster address (zero address to revoke)
|
|
363
|
+
* @param location The index in the broadcaster role's authorized wallets set
|
|
364
|
+
*
|
|
365
|
+
* Logic:
|
|
366
|
+
* - If a broadcaster exists at `location` and `newBroadcaster` is non-zero,
|
|
367
|
+
* update that slot from old to new (role remains full).
|
|
368
|
+
* - If no broadcaster exists at `location` and `newBroadcaster` is non-zero,
|
|
369
|
+
* assign `newBroadcaster` to the broadcaster role (respecting maxWallets).
|
|
370
|
+
* - If `newBroadcaster` is the zero address and a broadcaster exists at `location`,
|
|
371
|
+
* revoke that broadcaster from the role.
|
|
372
|
+
*/
|
|
373
|
+
function _updateBroadcaster(address newBroadcaster, uint256 location) internal virtual {
|
|
374
|
+
EngineBlox.Role storage role = _getSecureState().roles[EngineBlox.BROADCASTER_ROLE];
|
|
375
|
+
|
|
376
|
+
address oldBroadcaster;
|
|
377
|
+
uint256 length = role.walletCount;
|
|
378
|
+
|
|
379
|
+
if (location < length) {
|
|
380
|
+
oldBroadcaster = _getAuthorizedWalletAt(EngineBlox.BROADCASTER_ROLE, location);
|
|
381
|
+
} else {
|
|
382
|
+
oldBroadcaster = address(0);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Case 1: Revoke existing broadcaster at location
|
|
386
|
+
if (newBroadcaster == address(0)) {
|
|
387
|
+
if (oldBroadcaster != address(0)) {
|
|
388
|
+
_revokeWallet(EngineBlox.BROADCASTER_ROLE, oldBroadcaster);
|
|
389
|
+
_logAddressPairEvent(oldBroadcaster, address(0));
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Case 2: Update existing broadcaster at location
|
|
395
|
+
if (oldBroadcaster != address(0)) {
|
|
396
|
+
_updateWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster, oldBroadcaster);
|
|
397
|
+
_logAddressPairEvent(oldBroadcaster, newBroadcaster);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Case 3: No broadcaster at location, assign a new one (will respect maxWallets)
|
|
402
|
+
_assignWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster);
|
|
403
|
+
_logAddressPairEvent(address(0), newBroadcaster);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* @dev Updates the recovery address
|
|
408
|
+
* @param newRecoveryAddress The new recovery address
|
|
409
|
+
*/
|
|
410
|
+
function _updateRecoveryAddress(address newRecoveryAddress) internal virtual {
|
|
411
|
+
address oldRecovery = getRecovery();
|
|
412
|
+
_updateWallet(EngineBlox.RECOVERY_ROLE, newRecoveryAddress, oldRecovery);
|
|
413
|
+
_logAddressPairEvent(oldRecovery, newRecoveryAddress);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* @dev Emits ComponentEvent with ABI-encoded (address, address) payload. Reused to reduce contract size.
|
|
418
|
+
* @param a First address
|
|
419
|
+
* @param b Second address
|
|
420
|
+
*/
|
|
421
|
+
function _logAddressPairEvent(address a, address b) internal {
|
|
422
|
+
_logComponentEvent(abi.encode(a, b));
|
|
423
|
+
}
|
|
424
|
+
}
|