@bloxchain/contracts 1.0.0-alpha.2 → 1.0.0-alpha.20
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/README.md +7 -7
- package/abi/BaseStateMachine.abi.json +85 -45
- package/abi/EngineBlox.abi.json +73 -90
- package/abi/GuardController.abi.json +252 -806
- package/abi/{SimpleVaultDefinitions.abi.json → GuardControllerDefinitions.abi.json} +170 -28
- package/abi/IDefinition.abi.json +5 -0
- package/abi/RuntimeRBAC.abi.json +155 -218
- package/abi/RuntimeRBACDefinitions.abi.json +179 -0
- package/abi/SecureOwnable.abi.json +524 -1621
- package/abi/SecureOwnableDefinitions.abi.json +5 -0
- package/components/README.md +8 -0
- package/core/access/RuntimeRBAC.sol +255 -270
- package/core/access/interface/IRuntimeRBAC.sol +55 -84
- package/core/access/lib/definitions/RuntimeRBACDefinitions.sol +93 -2
- package/core/base/BaseStateMachine.sol +193 -107
- package/core/base/interface/IBaseStateMachine.sol +153 -153
- package/core/execution/GuardController.sol +155 -131
- package/core/execution/interface/IGuardController.sol +146 -120
- package/core/execution/lib/definitions/GuardControllerDefinitions.sol +193 -43
- package/core/lib/EngineBlox.sol +2683 -2322
- package/{interfaces → core/lib/interfaces}/IDefinition.sol +49 -49
- package/{interfaces → core/lib/interfaces}/IEventForwarder.sol +33 -33
- package/{utils → core/lib/utils}/SharedValidation.sol +61 -8
- package/core/pattern/Account.sol +84 -0
- package/core/security/SecureOwnable.sol +456 -412
- package/core/security/interface/ISecureOwnable.sol +105 -104
- package/core/security/lib/definitions/SecureOwnableDefinitions.sol +22 -6
- package/package.json +5 -5
- package/standards/README.md +12 -0
- package/standards/behavior/ICopyable.sol +34 -0
- package/standards/hooks/IOnActionHook.sol +21 -0
- package/abi/AccountBlox.abi.json +0 -5799
- package/abi/BareBlox.abi.json +0 -1284
- package/abi/RoleBlox.abi.json +0 -4209
- package/abi/SecureBlox.abi.json +0 -3828
- package/abi/SimpleRWA20.abi.json +0 -5288
- package/abi/SimpleRWA20Definitions.abi.json +0 -191
- package/abi/SimpleVault.abi.json +0 -4951
- package/core/research/BloxchainWallet.sol +0 -306
- package/core/research/erc20-blox/ERC20Blox.sol +0 -140
- package/core/research/erc20-blox/lib/definitions/ERC20BloxDefinitions.sol +0 -185
- package/interfaces/IOnActionHook.sol +0 -79
|
@@ -1,412 +1,456 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
pragma solidity 0.8.
|
|
3
|
-
|
|
4
|
-
// Contracts imports
|
|
5
|
-
import "../base/BaseStateMachine.sol";
|
|
6
|
-
import "./lib/definitions/SecureOwnableDefinitions.sol";
|
|
7
|
-
import "
|
|
8
|
-
import "
|
|
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
|
-
|
|
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
|
-
* @dev
|
|
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
|
-
* @dev
|
|
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
|
-
return
|
|
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
|
-
return
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
* @
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* @dev
|
|
269
|
-
* @param
|
|
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
|
-
function
|
|
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
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
* @
|
|
395
|
-
* @param
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
+
* **Ownership transfer vs recovery (threat model):**
|
|
36
|
+
* - `transferOwnershipRequest` snapshots `getRecovery()` into the pending tx `executionParams`. On execution,
|
|
37
|
+
* `executeTransferOwnership` receives that snapshotted address as the new owner. Rotating recovery after
|
|
38
|
+
* the request does **not** rewrite the pending payload; the beneficiary remains the recovery address
|
|
39
|
+
* at request time.
|
|
40
|
+
* - `transferOwnershipDelayedApproval` authorizes the **current** owner or **current** recovery (`getRecovery()`
|
|
41
|
+
* at approval time). It does **not** require the approver to match the snapshotted beneficiary. Integrators
|
|
42
|
+
* must treat approval as consent to execute the **stored** transfer, not “transfer to whoever is recovery now.”
|
|
43
|
+
* - `transferOwnershipCancellation` allows only the **current** recovery to cancel. If owner and broadcaster
|
|
44
|
+
* rotate recovery via `updateRecoveryRequestAndApprove` while a transfer is pending, the **previous**
|
|
45
|
+
* recovery loses cancel rights immediately; the pending tx still targets the old address until approved,
|
|
46
|
+
* cancelled by the new recovery, or superseded operationally.
|
|
47
|
+
* - Recovery and timelock updates use a request-and-approve meta-tx path without an additional timelock and
|
|
48
|
+
* are **not** blocked when an ownership transfer is pending (unlike broadcaster update requests). This is
|
|
49
|
+
* intentional: fast recovery rotation when owner and broadcaster still cooperate; operators who need a
|
|
50
|
+
* strict “recovery cannot change during pending ownership transfer” invariant must enforce it off-chain or
|
|
51
|
+
* extend this contract.
|
|
52
|
+
*
|
|
53
|
+
* This contract focuses purely on security logic while leveraging the BaseStateMachine
|
|
54
|
+
* for transaction management, meta-transactions, and state machine operations.
|
|
55
|
+
*/
|
|
56
|
+
abstract contract SecureOwnable is BaseStateMachine, ISecureOwnable {
|
|
57
|
+
using SharedValidation for *;
|
|
58
|
+
|
|
59
|
+
/// @dev Lane flags for **delayed** ownership-transfer and broadcaster-update requests only (`transferOwnershipRequest`,
|
|
60
|
+
/// `updateBroadcasterRequest`). Recovery and timelock updates use `_requestAndApproveTransaction` and do **not**
|
|
61
|
+
/// read or write these booleans. Each flag is set only after a successful `_requestTransaction` in that same tx;
|
|
62
|
+
/// clearing happens only in `_completeApprove` / `_completeCancel` in the **same** transaction as a successful
|
|
63
|
+
/// `_approveTransaction` / `_cancelTransaction`, so a revert unwinds engine state and flag writes together.
|
|
64
|
+
/// @dev Upgrading from legacy `_hasOpenRequest` / `_pendingBits` requires no pending requests.
|
|
65
|
+
bool private _hasOpenOwnershipRequest;
|
|
66
|
+
bool private _hasOpenBroadcasterRequest;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @notice Initializer to initialize SecureOwnable state
|
|
70
|
+
* @param initialOwner The initial owner address
|
|
71
|
+
* @param broadcaster The broadcaster address
|
|
72
|
+
* @param recovery The recovery address
|
|
73
|
+
* @param timeLockPeriodSec The timelock period in seconds
|
|
74
|
+
* @param eventForwarder The event forwarder address
|
|
75
|
+
*/
|
|
76
|
+
function initialize(
|
|
77
|
+
address initialOwner,
|
|
78
|
+
address broadcaster,
|
|
79
|
+
address recovery,
|
|
80
|
+
uint256 timeLockPeriodSec,
|
|
81
|
+
address eventForwarder
|
|
82
|
+
) public virtual onlyInitializing {
|
|
83
|
+
_initializeBaseStateMachine(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
84
|
+
|
|
85
|
+
// Load SecureOwnable-specific definitions
|
|
86
|
+
IDefinition.RolePermission memory secureOwnablePermissions = SecureOwnableDefinitions.getRolePermissions();
|
|
87
|
+
_loadDefinitions(
|
|
88
|
+
SecureOwnableDefinitions.getFunctionSchemas(),
|
|
89
|
+
secureOwnablePermissions.roleHashes,
|
|
90
|
+
secureOwnablePermissions.functionPermissions,
|
|
91
|
+
true // Enforce all function schemas are protected
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============ INTERFACE SUPPORT ============
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @dev See {IERC165-supportsInterface}.
|
|
99
|
+
* @notice Adds ISecureOwnable interface ID for component detection
|
|
100
|
+
*/
|
|
101
|
+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
|
102
|
+
return interfaceId == type(ISecureOwnable).interfaceId || super.supportsInterface(interfaceId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Ownership Management
|
|
106
|
+
/**
|
|
107
|
+
* @dev Requests a time-delayed transfer of the OWNER role to the **recovery address at request time**.
|
|
108
|
+
* @notice Encodes `getRecovery()` into `executionParams`; that address becomes the new owner on successful
|
|
109
|
+
* execution. Changing recovery later does not update this pending record.
|
|
110
|
+
* @return txId The transaction ID (use getTransaction(txId) for full record)
|
|
111
|
+
*/
|
|
112
|
+
function transferOwnershipRequest() public returns (uint256 txId) {
|
|
113
|
+
SharedValidation.validateRecovery(getRecovery());
|
|
114
|
+
_requireNoPendingRequest(SecureOwnableDefinitions.OWNERSHIP_TRANSFER);
|
|
115
|
+
|
|
116
|
+
EngineBlox.TxRecord memory txRecord = _requestTransaction(
|
|
117
|
+
msg.sender,
|
|
118
|
+
address(this),
|
|
119
|
+
0, // value
|
|
120
|
+
0, // no gas limit
|
|
121
|
+
SecureOwnableDefinitions.OWNERSHIP_TRANSFER,
|
|
122
|
+
SecureOwnableDefinitions.TRANSFER_OWNERSHIP_SELECTOR,
|
|
123
|
+
abi.encode(getRecovery())
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
_hasOpenOwnershipRequest = true;
|
|
127
|
+
_logAddressPairEvent(owner(), getRecovery());
|
|
128
|
+
return txRecord.txId;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @dev Approves a pending ownership transfer after `releaseTime` (timelock on the direct path).
|
|
133
|
+
* @notice Callable by **current** owner or **current** recovery. Execution still transfers ownership to
|
|
134
|
+
* the address snapshotted at request time, which may differ from `getRecovery()` at approval time.
|
|
135
|
+
* @param txId The transaction ID
|
|
136
|
+
* @return The transaction ID
|
|
137
|
+
*/
|
|
138
|
+
function transferOwnershipDelayedApproval(uint256 txId) public returns (uint256) {
|
|
139
|
+
SharedValidation.validateOwnerOrRecovery(owner(), getRecovery());
|
|
140
|
+
return _completeApprove(_approveTransaction(txId));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @dev Approves a pending ownership transfer transaction using a meta-transaction
|
|
145
|
+
* @param metaTx The meta-transaction
|
|
146
|
+
* @return The transaction ID
|
|
147
|
+
*/
|
|
148
|
+
function transferOwnershipApprovalWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
149
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
150
|
+
return _completeApprove(_approveTransactionWithMetaTx(metaTx));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @dev Cancels a pending ownership transfer transaction.
|
|
155
|
+
* @notice Only the **current** `getRecovery()` may cancel. After a recovery rotation, the prior recovery
|
|
156
|
+
* address can no longer cancel.
|
|
157
|
+
* @param txId The transaction ID
|
|
158
|
+
* @return The transaction ID
|
|
159
|
+
*/
|
|
160
|
+
function transferOwnershipCancellation(uint256 txId) public returns (uint256) {
|
|
161
|
+
SharedValidation.validateRecovery(getRecovery());
|
|
162
|
+
return _completeCancel(_cancelTransaction(txId));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @dev Cancels a pending ownership transfer transaction using a meta-transaction
|
|
167
|
+
* @param metaTx The meta-transaction
|
|
168
|
+
* @return The transaction ID
|
|
169
|
+
*/
|
|
170
|
+
function transferOwnershipCancellationWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
171
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
172
|
+
return _completeCancel(_cancelTransactionWithMetaTx(metaTx));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Broadcaster Management
|
|
176
|
+
/**
|
|
177
|
+
* @dev Requests an update to the broadcaster at a specific location (index).
|
|
178
|
+
* @notice Requires no pending broadcaster-update and no pending ownership-transfer request.
|
|
179
|
+
* @param newBroadcaster The new broadcaster address (zero address to revoke at location)
|
|
180
|
+
* @param location The index in the broadcaster role's authorized wallets set
|
|
181
|
+
* @return txId The transaction ID for the pending request (use getTransaction(txId) for full record)
|
|
182
|
+
*/
|
|
183
|
+
function updateBroadcasterRequest(address newBroadcaster, uint256 location) public returns (uint256 txId) {
|
|
184
|
+
SharedValidation.validateOwner(owner());
|
|
185
|
+
_requireNoPendingRequest(SecureOwnableDefinitions.BROADCASTER_UPDATE);
|
|
186
|
+
_requireNoPendingRequest(SecureOwnableDefinitions.OWNERSHIP_TRANSFER);
|
|
187
|
+
|
|
188
|
+
// Get the current broadcaster at the specified location. zero address if no broadcaster at location.
|
|
189
|
+
address currentBroadcaster = location < _getSecureState().roles[EngineBlox.BROADCASTER_ROLE].walletCount
|
|
190
|
+
? _getAuthorizedWalletAt(EngineBlox.BROADCASTER_ROLE, location)
|
|
191
|
+
: address(0);
|
|
192
|
+
|
|
193
|
+
EngineBlox.TxRecord memory txRecord = _requestTransaction(
|
|
194
|
+
msg.sender,
|
|
195
|
+
address(this),
|
|
196
|
+
0, // value
|
|
197
|
+
0, // gas limit
|
|
198
|
+
SecureOwnableDefinitions.BROADCASTER_UPDATE,
|
|
199
|
+
SecureOwnableDefinitions.UPDATE_BROADCASTER_SELECTOR,
|
|
200
|
+
abi.encode(newBroadcaster, location)
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
_hasOpenBroadcasterRequest = true;
|
|
204
|
+
_logAddressPairEvent(currentBroadcaster, newBroadcaster);
|
|
205
|
+
return txRecord.txId;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @dev Approves a pending broadcaster update transaction after the release time
|
|
210
|
+
* @param txId The transaction ID
|
|
211
|
+
* @return The transaction ID
|
|
212
|
+
*/
|
|
213
|
+
function updateBroadcasterDelayedApproval(uint256 txId) public returns (uint256) {
|
|
214
|
+
SharedValidation.validateOwner(owner());
|
|
215
|
+
return _completeApprove(_approveTransaction(txId));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @dev Approves a pending broadcaster update transaction using a meta-transaction
|
|
220
|
+
* @param metaTx The meta-transaction
|
|
221
|
+
* @return The transaction ID
|
|
222
|
+
*/
|
|
223
|
+
function updateBroadcasterApprovalWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
224
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
225
|
+
return _completeApprove(_approveTransactionWithMetaTx(metaTx));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @dev Cancels a pending broadcaster update transaction
|
|
230
|
+
* @param txId The transaction ID
|
|
231
|
+
* @return The transaction ID
|
|
232
|
+
*/
|
|
233
|
+
function updateBroadcasterCancellation(uint256 txId) public returns (uint256) {
|
|
234
|
+
SharedValidation.validateOwner(owner());
|
|
235
|
+
return _completeCancel(_cancelTransaction(txId));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @dev Cancels a pending broadcaster update transaction using a meta-transaction
|
|
240
|
+
* @param metaTx The meta-transaction
|
|
241
|
+
* @return The transaction ID
|
|
242
|
+
*/
|
|
243
|
+
function updateBroadcasterCancellationWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
|
|
244
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
245
|
+
return _completeCancel(_cancelTransactionWithMetaTx(metaTx));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Recovery Management
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @dev Requests and approves a recovery address update using a meta-transaction (owner signs, broadcaster submits).
|
|
252
|
+
* @notice Does **not** revert when an ownership transfer is pending. A pending transfer continues to target
|
|
253
|
+
* the recovery address snapshotted at its request until executed or cancelled by **current** recovery.
|
|
254
|
+
* @param metaTx The meta-transaction
|
|
255
|
+
* @return The transaction ID
|
|
256
|
+
*/
|
|
257
|
+
function updateRecoveryRequestAndApprove(
|
|
258
|
+
EngineBlox.MetaTransaction memory metaTx
|
|
259
|
+
) public returns (uint256) {
|
|
260
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
261
|
+
EngineBlox.TxRecord memory txRecord = _requestAndApproveTransaction(metaTx);
|
|
262
|
+
return txRecord.txId;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// TimeLock Management
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @dev Requests and approves a time lock period update using a meta-transaction
|
|
269
|
+
* @param metaTx The meta-transaction
|
|
270
|
+
* @return The transaction ID
|
|
271
|
+
*/
|
|
272
|
+
function updateTimeLockRequestAndApprove(
|
|
273
|
+
EngineBlox.MetaTransaction memory metaTx
|
|
274
|
+
) public returns (uint256) {
|
|
275
|
+
_validateBroadcasterAndOwnerSigner(metaTx);
|
|
276
|
+
EngineBlox.TxRecord memory txRecord = _requestAndApproveTransaction(metaTx);
|
|
277
|
+
return txRecord.txId;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Execution Functions
|
|
281
|
+
/**
|
|
282
|
+
* @dev External function that can only be called by the contract itself to execute ownership transfer.
|
|
283
|
+
* @param newOwner The new owner; for the OWNERSHIP_TRANSFER flow this is the recovery address encoded at
|
|
284
|
+
* request time (see `transferOwnershipRequest`), not necessarily `getRecovery()` at execution time.
|
|
285
|
+
*/
|
|
286
|
+
function executeTransferOwnership(address newOwner) external {
|
|
287
|
+
_validateExecuteBySelf();
|
|
288
|
+
_transferOwnership(newOwner);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @dev External function that can only be called by the contract itself to execute broadcaster update
|
|
293
|
+
* @param newBroadcaster The new broadcaster address (zero address to revoke at location)
|
|
294
|
+
* @param location The index in the broadcaster role's authorized wallets set
|
|
295
|
+
*/
|
|
296
|
+
function executeBroadcasterUpdate(address newBroadcaster, uint256 location) external {
|
|
297
|
+
_validateExecuteBySelf();
|
|
298
|
+
_updateBroadcaster(newBroadcaster, location);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @dev External function that can only be called by the contract itself to execute recovery update
|
|
303
|
+
* @param newRecoveryAddress The new recovery address
|
|
304
|
+
*/
|
|
305
|
+
function executeRecoveryUpdate(address newRecoveryAddress) external {
|
|
306
|
+
_validateExecuteBySelf();
|
|
307
|
+
_updateRecoveryAddress(newRecoveryAddress);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @dev External function that can only be called by the contract itself to execute timelock update
|
|
312
|
+
* @param newTimeLockPeriodSec The new timelock period in seconds
|
|
313
|
+
*/
|
|
314
|
+
function executeTimeLockUpdate(uint256 newTimeLockPeriodSec) external {
|
|
315
|
+
_validateExecuteBySelf();
|
|
316
|
+
_updateTimeLockPeriod(newTimeLockPeriodSec);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============ INTERNAL FUNCTIONS ============
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @dev Validates that the caller is the broadcaster and that the meta-tx signer is the owner.
|
|
324
|
+
* @param metaTx The meta-transaction to validate
|
|
325
|
+
*/
|
|
326
|
+
function _validateBroadcasterAndOwnerSigner(EngineBlox.MetaTransaction memory metaTx) internal view {
|
|
327
|
+
_validateBroadcaster(msg.sender);
|
|
328
|
+
SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @dev Completes ownership/broadcaster flow after approval: clears the matching pending flag and returns txId.
|
|
333
|
+
* @param updatedRecord The updated transaction record from approval
|
|
334
|
+
* @return txId The transaction ID
|
|
335
|
+
*/
|
|
336
|
+
function _completeApprove(EngineBlox.TxRecord memory updatedRecord) internal returns (uint256 txId) {
|
|
337
|
+
_clearPendingFlagForOperation(updatedRecord.params.operationType);
|
|
338
|
+
return updatedRecord.txId;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @dev Completes ownership/broadcaster flow after cancellation: clears the matching pending flag and returns txId.
|
|
343
|
+
* @param updatedRecord The updated transaction record from cancellation
|
|
344
|
+
* @return txId The transaction ID
|
|
345
|
+
*/
|
|
346
|
+
function _completeCancel(EngineBlox.TxRecord memory updatedRecord) internal returns (uint256 txId) {
|
|
347
|
+
_clearPendingFlagForOperation(updatedRecord.params.operationType);
|
|
348
|
+
return updatedRecord.txId;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @dev Reverts if the pending flag for `requestOperationType` is already set (one lane per call).
|
|
353
|
+
* `OWNERSHIP_TRANSFER` checks only `_hasOpenOwnershipRequest` (a broadcaster update may still be pending).
|
|
354
|
+
* `BROADCASTER_UPDATE` checks only `_hasOpenBroadcasterRequest`. Callers that need both lanes idle
|
|
355
|
+
* (e.g. `updateBroadcasterRequest`) invoke this once per operation type.
|
|
356
|
+
* @param requestOperationType Lane to validate (`OWNERSHIP_TRANSFER` or `BROADCASTER_UPDATE`).
|
|
357
|
+
*/
|
|
358
|
+
function _requireNoPendingRequest(bytes32 requestOperationType) internal view {
|
|
359
|
+
if (requestOperationType == SecureOwnableDefinitions.OWNERSHIP_TRANSFER) {
|
|
360
|
+
if (_hasOpenOwnershipRequest) revert SharedValidation.PendingSecureRequest();
|
|
361
|
+
} else if (requestOperationType == SecureOwnableDefinitions.BROADCASTER_UPDATE) {
|
|
362
|
+
if (_hasOpenBroadcasterRequest) revert SharedValidation.PendingSecureRequest();
|
|
363
|
+
} else {
|
|
364
|
+
revert();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* @dev Clears the pending flag for a completed or cancelled secure op (approve/cancel paths).
|
|
370
|
+
* @param operationType The tx record's `operationType` (`OWNERSHIP_TRANSFER` or `BROADCASTER_UPDATE`).
|
|
371
|
+
*/
|
|
372
|
+
function _clearPendingFlagForOperation(bytes32 operationType) private {
|
|
373
|
+
if (operationType == SecureOwnableDefinitions.OWNERSHIP_TRANSFER) {
|
|
374
|
+
_hasOpenOwnershipRequest = false;
|
|
375
|
+
} else if (operationType == SecureOwnableDefinitions.BROADCASTER_UPDATE) {
|
|
376
|
+
_hasOpenBroadcasterRequest = false;
|
|
377
|
+
} else {
|
|
378
|
+
revert();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @dev Transfers ownership of the contract
|
|
384
|
+
* @param newOwner The new owner of the contract
|
|
385
|
+
*/
|
|
386
|
+
function _transferOwnership(address newOwner) internal virtual {
|
|
387
|
+
address oldOwner = owner();
|
|
388
|
+
_updateWallet(EngineBlox.OWNER_ROLE, newOwner, oldOwner);
|
|
389
|
+
_logAddressPairEvent(oldOwner, newOwner);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @dev Updates the broadcaster role at a specific index (location)
|
|
394
|
+
* @param newBroadcaster The new broadcaster address (zero address to revoke)
|
|
395
|
+
* @param location The index in the broadcaster role's authorized wallets set
|
|
396
|
+
*
|
|
397
|
+
* Logic:
|
|
398
|
+
* - If a broadcaster exists at `location` and `newBroadcaster` is non-zero,
|
|
399
|
+
* update that slot from old to new (role remains full).
|
|
400
|
+
* - If no broadcaster exists at `location` and `newBroadcaster` is non-zero,
|
|
401
|
+
* assign `newBroadcaster` to the broadcaster role (respecting maxWallets).
|
|
402
|
+
* - If `newBroadcaster` is the zero address and a broadcaster exists at `location`,
|
|
403
|
+
* revoke that broadcaster from the role.
|
|
404
|
+
*/
|
|
405
|
+
function _updateBroadcaster(address newBroadcaster, uint256 location) internal virtual {
|
|
406
|
+
EngineBlox.Role storage role = _getSecureState().roles[EngineBlox.BROADCASTER_ROLE];
|
|
407
|
+
|
|
408
|
+
address oldBroadcaster;
|
|
409
|
+
uint256 length = role.walletCount;
|
|
410
|
+
|
|
411
|
+
if (location < length) {
|
|
412
|
+
oldBroadcaster = _getAuthorizedWalletAt(EngineBlox.BROADCASTER_ROLE, location);
|
|
413
|
+
} else {
|
|
414
|
+
oldBroadcaster = address(0);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Case 1: Revoke existing broadcaster at location
|
|
418
|
+
if (newBroadcaster == address(0)) {
|
|
419
|
+
if (oldBroadcaster != address(0)) {
|
|
420
|
+
_revokeWallet(EngineBlox.BROADCASTER_ROLE, oldBroadcaster);
|
|
421
|
+
_logAddressPairEvent(oldBroadcaster, address(0));
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Case 2: Update existing broadcaster at location
|
|
427
|
+
if (oldBroadcaster != address(0)) {
|
|
428
|
+
_updateWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster, oldBroadcaster);
|
|
429
|
+
_logAddressPairEvent(oldBroadcaster, newBroadcaster);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Case 3: No broadcaster at location, assign a new one (will respect maxWallets)
|
|
434
|
+
_assignWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster);
|
|
435
|
+
_logAddressPairEvent(address(0), newBroadcaster);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* @dev Updates the recovery address
|
|
440
|
+
* @param newRecoveryAddress The new recovery address
|
|
441
|
+
*/
|
|
442
|
+
function _updateRecoveryAddress(address newRecoveryAddress) internal virtual {
|
|
443
|
+
address oldRecovery = getRecovery();
|
|
444
|
+
_updateWallet(EngineBlox.RECOVERY_ROLE, newRecoveryAddress, oldRecovery);
|
|
445
|
+
_logAddressPairEvent(oldRecovery, newRecoveryAddress);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* @dev Emits ComponentEvent with ABI-encoded (address, address) payload. Reused to reduce contract size.
|
|
450
|
+
* @param a First address
|
|
451
|
+
* @param b Second address
|
|
452
|
+
*/
|
|
453
|
+
function _logAddressPairEvent(address a, address b) internal {
|
|
454
|
+
_logComponentEvent(abi.encode(a, b));
|
|
455
|
+
}
|
|
456
|
+
}
|