@bloxchain/contracts 1.0.0-alpha → 1.0.0-alpha.10

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.
Files changed (55) hide show
  1. package/README.md +55 -18
  2. package/abi/{ControlBlox.abi.json → AccountBlox.abi.json} +699 -2974
  3. package/abi/BareBlox.abi.json +127 -90
  4. package/abi/BaseStateMachine.abi.json +127 -90
  5. package/abi/EngineBlox.abi.json +11 -31
  6. package/abi/GuardController.abi.json +217 -895
  7. package/abi/GuardControllerDefinitions.abi.json +380 -0
  8. package/abi/IDefinition.abi.json +19 -0
  9. package/abi/RoleBlox.abi.json +818 -2404
  10. package/abi/RuntimeRBAC.abi.json +122 -328
  11. package/abi/RuntimeRBACDefinitions.abi.json +243 -0
  12. package/abi/SecureBlox.abi.json +620 -1952
  13. package/abi/SecureOwnable.abi.json +469 -1801
  14. package/abi/SecureOwnableDefinitions.abi.json +57 -0
  15. package/abi/SimpleRWA20.abi.json +486 -1999
  16. package/abi/SimpleRWA20Definitions.abi.json +19 -0
  17. package/abi/SimpleVault.abi.json +884 -2685
  18. package/abi/SimpleVaultDefinitions.abi.json +19 -0
  19. package/components/README.md +8 -0
  20. package/core/access/RuntimeRBAC.sol +184 -0
  21. package/core/access/interface/IRuntimeRBAC.sol +55 -0
  22. package/{contracts/core → core}/access/lib/definitions/RuntimeRBACDefinitions.sol +121 -1
  23. package/{contracts/core → core}/base/BaseStateMachine.sol +187 -54
  24. package/{contracts/core → core}/base/interface/IBaseStateMachine.sol +7 -0
  25. package/{contracts/core → core}/execution/GuardController.sol +89 -155
  26. package/{contracts/core → core}/execution/interface/IGuardController.sol +52 -12
  27. package/{contracts/core → core}/execution/lib/definitions/GuardControllerDefinitions.sol +91 -2
  28. package/{contracts/core → core}/lib/EngineBlox.sol +167 -64
  29. package/{contracts → core/lib}/interfaces/IDefinition.sol +15 -6
  30. package/{contracts → core/lib}/interfaces/IEventForwarder.sol +1 -1
  31. package/{contracts → core/lib}/utils/SharedValidation.sol +490 -486
  32. package/core/pattern/Account.sol +75 -0
  33. package/core/research/BloxchainWallet.sol +133 -0
  34. package/core/research/FactoryBlox/FactoryBlox.sol +344 -0
  35. package/core/research/FactoryBlox/FactoryBloxDefinitions.sol +144 -0
  36. package/core/research/erc1155-blox/ERC1155Blox.sol +170 -0
  37. package/core/research/erc1155-blox/lib/definitions/ERC1155BloxDefinitions.sol +203 -0
  38. package/core/research/erc20-blox/ERC20Blox.sol +135 -0
  39. package/core/research/erc20-blox/lib/definitions/ERC20BloxDefinitions.sol +185 -0
  40. package/core/research/erc721-blox/ERC721Blox.sol +131 -0
  41. package/core/research/erc721-blox/lib/definitions/ERC721BloxDefinitions.sol +172 -0
  42. package/core/research/lending-blox/.gitkeep +1 -0
  43. package/core/research/p2p-blox/P2PBlox.sol +266 -0
  44. package/core/research/p2p-blox/README.md +85 -0
  45. package/core/research/p2p-blox/lib/definitions/P2PBloxDefinitions.sol +19 -0
  46. package/{contracts/core → core}/security/SecureOwnable.sol +390 -419
  47. package/{contracts/core → core}/security/interface/ISecureOwnable.sol +27 -40
  48. package/{contracts/core → core}/security/lib/definitions/SecureOwnableDefinitions.sol +786 -757
  49. package/package.json +49 -47
  50. package/standards/README.md +12 -0
  51. package/standards/behavior/ICopyable.sol +36 -0
  52. package/standards/hooks/IOnActionHook.sol +21 -0
  53. package/contracts/core/access/RuntimeRBAC.sol +0 -344
  54. package/contracts/core/access/interface/IRuntimeRBAC.sol +0 -108
  55. package/contracts/interfaces/IOnActionHook.sol +0 -79
@@ -1,419 +1,390 @@
1
- // SPDX-License-Identifier: MPL-2.0
2
- pragma solidity 0.8.33;
3
-
4
- // Contracts imports
5
- import "../base/BaseStateMachine.sol";
6
- import "./lib/definitions/SecureOwnableDefinitions.sol";
7
- import "../../interfaces/IDefinition.sol";
8
- import "../../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
- * This contract focuses purely on security logic while leveraging the BaseStateMachine
31
- * for transaction management, meta-transactions, and state machine operations.
32
- */
33
- abstract contract SecureOwnable is BaseStateMachine, ISecureOwnable {
34
- using SharedValidation for *;
35
-
36
-
37
- // Request flags
38
- bool private _hasOpenOwnershipRequest;
39
- bool private _hasOpenBroadcasterRequest;
40
-
41
- event OwnershipTransferRequest(address currentOwner, address newOwner);
42
- event OwnershipTransferCancelled(uint256 txId);
43
- event OwnershipTransferUpdated(address oldOwner, address newOwner);
44
- event BroadcasterUpdateRequest(address currentBroadcaster, address newBroadcaster);
45
- event BroadcasterUpdateCancelled(uint256 txId);
46
- event BroadcasterUpdated(address oldBroadcaster, address newBroadcaster);
47
- event RecoveryAddressUpdated(address oldRecovery, address newRecovery);
48
- event TimeLockPeriodUpdated(uint256 oldPeriod, uint256 newPeriod);
49
-
50
-
51
- /**
52
- * @notice Initializer to initialize SecureOwnable state
53
- * @param initialOwner The initial owner address
54
- * @param broadcaster The broadcaster address
55
- * @param recovery The recovery address
56
- * @param timeLockPeriodSec The timelock period in seconds
57
- * @param eventForwarder The event forwarder address
58
- */
59
- function initialize(
60
- address initialOwner,
61
- address broadcaster,
62
- address recovery,
63
- uint256 timeLockPeriodSec,
64
- address eventForwarder
65
- ) public virtual onlyInitializing {
66
- // Initialize base state machine (only if not already initialized)
67
- if (!_secureState.initialized) {
68
- _initializeBaseStateMachine(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
69
- }
70
-
71
- // Load SecureOwnable-specific definitions
72
- IDefinition.RolePermission memory secureOwnablePermissions = SecureOwnableDefinitions.getRolePermissions();
73
- _loadDefinitions(
74
- SecureOwnableDefinitions.getFunctionSchemas(),
75
- secureOwnablePermissions.roleHashes,
76
- secureOwnablePermissions.functionPermissions
77
- );
78
- }
79
-
80
- // ============ INTERFACE SUPPORT ============
81
-
82
- /**
83
- * @dev See {IERC165-supportsInterface}.
84
- * @notice Adds ISecureOwnable interface ID for component detection
85
- */
86
- function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
87
- return interfaceId == type(ISecureOwnable).interfaceId || super.supportsInterface(interfaceId);
88
- }
89
-
90
- // Ownership Management
91
- /**
92
- * @dev Requests a transfer of ownership
93
- * @return The transaction record
94
- */
95
- function transferOwnershipRequest() public returns (EngineBlox.TxRecord memory) {
96
- SharedValidation.validateRecovery(getRecovery());
97
- if (_hasOpenOwnershipRequest) revert SharedValidation.ResourceAlreadyExists(bytes32(uint256(0)));
98
-
99
- EngineBlox.TxRecord memory txRecord = _requestTransaction(
100
- msg.sender,
101
- address(this),
102
- 0, // value
103
- 0, // no gas limit
104
- SecureOwnableDefinitions.OWNERSHIP_TRANSFER,
105
- SecureOwnableDefinitions.TRANSFER_OWNERSHIP_SELECTOR,
106
- abi.encode(getRecovery())
107
- );
108
-
109
- _hasOpenOwnershipRequest = true;
110
- emit OwnershipTransferRequest(owner(), getRecovery());
111
- return txRecord;
112
- }
113
-
114
- /**
115
- * @dev Approves a pending ownership transfer transaction after the release time
116
- * @param txId The transaction ID
117
- * @return The updated transaction record
118
- */
119
- function transferOwnershipDelayedApproval(uint256 txId) public returns (EngineBlox.TxRecord memory) {
120
- SharedValidation.validateOwnerOrRecovery(owner(), getRecovery());
121
-
122
- EngineBlox.TxRecord memory updatedRecord = _approveTransaction(txId);
123
- _hasOpenOwnershipRequest = false;
124
- return updatedRecord;
125
- }
126
-
127
- /**
128
- * @dev Approves a pending ownership transfer transaction using a meta-transaction
129
- * @param metaTx The meta-transaction
130
- * @return The updated transaction record
131
- */
132
- function transferOwnershipApprovalWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (EngineBlox.TxRecord memory) {
133
- _validateBroadcaster(msg.sender);
134
- SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
135
-
136
- EngineBlox.TxRecord memory updatedRecord = _approveTransactionWithMetaTx(metaTx);
137
- _hasOpenOwnershipRequest = false;
138
- return updatedRecord;
139
- }
140
-
141
- /**
142
- * @dev Cancels a pending ownership transfer transaction
143
- * @param txId The transaction ID
144
- * @return The updated transaction record
145
- */
146
- function transferOwnershipCancellation(uint256 txId) public returns (EngineBlox.TxRecord memory) {
147
- SharedValidation.validateRecovery(getRecovery());
148
- EngineBlox.TxRecord memory updatedRecord = _cancelTransaction(txId);
149
- _hasOpenOwnershipRequest = false;
150
- emit OwnershipTransferCancelled(txId);
151
- return updatedRecord;
152
- }
153
-
154
- /**
155
- * @dev Cancels a pending ownership transfer transaction using a meta-transaction
156
- * @param metaTx The meta-transaction
157
- * @return The updated transaction record
158
- */
159
- function transferOwnershipCancellationWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (EngineBlox.TxRecord memory) {
160
- _validateBroadcaster(msg.sender);
161
- SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
162
-
163
- EngineBlox.TxRecord memory updatedRecord = _cancelTransactionWithMetaTx(metaTx);
164
- _hasOpenOwnershipRequest = false;
165
- emit OwnershipTransferCancelled(updatedRecord.txId);
166
- return updatedRecord;
167
- }
168
-
169
- // Broadcaster Management
170
- /**
171
- * @dev Updates the broadcaster address
172
- * @param newBroadcaster The new broadcaster address
173
- * @return The execution options
174
- */
175
- function updateBroadcasterRequest(address newBroadcaster) public returns (EngineBlox.TxRecord memory) {
176
- SharedValidation.validateOwner(owner());
177
- if (_hasOpenBroadcasterRequest) revert SharedValidation.ResourceAlreadyExists(bytes32(uint256(0)));
178
- address[] memory broadcasters = getBroadcasters();
179
- address currentBroadcaster = broadcasters.length > 0 ? broadcasters[0] : address(0);
180
- SharedValidation.validateAddressUpdate(newBroadcaster, currentBroadcaster);
181
-
182
- EngineBlox.TxRecord memory txRecord = _requestTransaction(
183
- msg.sender,
184
- address(this),
185
- 0, // value
186
- 0, // gas limit
187
- SecureOwnableDefinitions.BROADCASTER_UPDATE,
188
- SecureOwnableDefinitions.UPDATE_BROADCASTER_SELECTOR,
189
- abi.encode(newBroadcaster)
190
- );
191
-
192
- _hasOpenBroadcasterRequest = true;
193
- emit BroadcasterUpdateRequest(currentBroadcaster, newBroadcaster);
194
- return txRecord;
195
- }
196
-
197
- /**
198
- * @dev Approves a pending broadcaster update transaction after the release time
199
- * @param txId The transaction ID
200
- * @return The updated transaction record
201
- */
202
- function updateBroadcasterDelayedApproval(uint256 txId) public returns (EngineBlox.TxRecord memory) {
203
- SharedValidation.validateOwner(owner());
204
- EngineBlox.TxRecord memory updatedRecord = _approveTransaction(txId);
205
- _hasOpenBroadcasterRequest = false;
206
- return updatedRecord;
207
- }
208
-
209
- /**
210
- * @dev Approves a pending broadcaster update transaction using a meta-transaction
211
- * @param metaTx The meta-transaction
212
- * @return The updated transaction record
213
- */
214
- function updateBroadcasterApprovalWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (EngineBlox.TxRecord memory) {
215
- _validateBroadcaster(msg.sender);
216
- SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
217
-
218
- EngineBlox.TxRecord memory updatedRecord = _approveTransactionWithMetaTx(metaTx);
219
- _hasOpenBroadcasterRequest = false;
220
- return updatedRecord;
221
- }
222
-
223
- /**
224
- * @dev Cancels a pending broadcaster update transaction
225
- * @param txId The transaction ID
226
- * @return The updated transaction record
227
- */
228
- function updateBroadcasterCancellation(uint256 txId) public returns (EngineBlox.TxRecord memory) {
229
- SharedValidation.validateOwner(owner());
230
- EngineBlox.TxRecord memory updatedRecord = _cancelTransaction(txId);
231
- _hasOpenBroadcasterRequest = false;
232
- emit BroadcasterUpdateCancelled(txId);
233
- return updatedRecord;
234
- }
235
-
236
- /**
237
- * @dev Cancels a pending broadcaster update transaction using a meta-transaction
238
- * @param metaTx The meta-transaction
239
- * @return The updated transaction record
240
- */
241
- function updateBroadcasterCancellationWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (EngineBlox.TxRecord memory) {
242
- _validateBroadcaster(msg.sender);
243
- SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
244
-
245
- EngineBlox.TxRecord memory updatedRecord = _cancelTransactionWithMetaTx(metaTx);
246
- _hasOpenBroadcasterRequest = false;
247
- emit BroadcasterUpdateCancelled(updatedRecord.txId);
248
- return updatedRecord;
249
- }
250
-
251
- // Recovery Management
252
- /**
253
- * @dev Creates execution params for updating the recovery address
254
- * @param newRecoveryAddress The new recovery address
255
- * @return The execution params
256
- */
257
- function updateRecoveryExecutionParams(
258
- address newRecoveryAddress
259
- ) public view returns (bytes memory) {
260
- SharedValidation.validateAddressUpdate(newRecoveryAddress, getRecovery());
261
- return abi.encode(newRecoveryAddress);
262
- }
263
-
264
- /**
265
- * @dev Requests and approves a recovery address update using a meta-transaction
266
- * @param metaTx The meta-transaction
267
- * @return The transaction record
268
- */
269
- function updateRecoveryRequestAndApprove(
270
- EngineBlox.MetaTransaction memory metaTx
271
- ) public returns (EngineBlox.TxRecord memory) {
272
- _validateBroadcaster(msg.sender);
273
- SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
274
-
275
- return _requestAndApproveTransaction(metaTx);
276
- }
277
-
278
- // TimeLock Management
279
- /**
280
- * @dev Creates execution params for updating the time lock period
281
- * @param newTimeLockPeriodSec The new time lock period in seconds
282
- * @return The execution params
283
- */
284
- function updateTimeLockExecutionParams(
285
- uint256 newTimeLockPeriodSec
286
- ) public view returns (bytes memory) {
287
- SharedValidation.validateTimeLockUpdate(newTimeLockPeriodSec, getTimeLockPeriodSec());
288
- return abi.encode(newTimeLockPeriodSec);
289
- }
290
-
291
- /**
292
- * @dev Requests and approves a time lock period update using a meta-transaction
293
- * @param metaTx The meta-transaction
294
- * @return The transaction record
295
- */
296
- function updateTimeLockRequestAndApprove(
297
- EngineBlox.MetaTransaction memory metaTx
298
- ) public returns (EngineBlox.TxRecord memory) {
299
- _validateBroadcaster(msg.sender);
300
- SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
301
-
302
- return _requestAndApproveTransaction(metaTx);
303
- }
304
-
305
- // Execution Functions
306
- /**
307
- * @dev External function that can only be called by the contract itself to execute ownership transfer
308
- * @param newOwner The new owner address
309
- */
310
- function executeTransferOwnership(address newOwner) external {
311
- SharedValidation.validateInternalCall(address(this));
312
- _transferOwnership(newOwner);
313
- }
314
-
315
- /**
316
- * @dev External function that can only be called by the contract itself to execute broadcaster update
317
- * @param newBroadcaster The new broadcaster address
318
- */
319
- function executeBroadcasterUpdate(address newBroadcaster) external {
320
- SharedValidation.validateInternalCall(address(this));
321
- _updateBroadcaster(newBroadcaster, 0);
322
- }
323
-
324
- /**
325
- * @dev External function that can only be called by the contract itself to execute recovery update
326
- * @param newRecoveryAddress The new recovery address
327
- */
328
- function executeRecoveryUpdate(address newRecoveryAddress) external {
329
- SharedValidation.validateInternalCall(address(this));
330
- _updateRecoveryAddress(newRecoveryAddress);
331
- }
332
-
333
- /**
334
- * @dev External function that can only be called by the contract itself to execute timelock update
335
- * @param newTimeLockPeriodSec The new timelock period in seconds
336
- */
337
- function executeTimeLockUpdate(uint256 newTimeLockPeriodSec) external {
338
- SharedValidation.validateInternalCall(address(this));
339
- _updateTimeLockPeriod(newTimeLockPeriodSec);
340
- }
341
-
342
- // ============ INTERNAL FUNCTIONS ============
343
-
344
- /**
345
- * @dev Transfers ownership of the contract
346
- * @param newOwner The new owner of the contract
347
- */
348
- function _transferOwnership(address newOwner) internal virtual {
349
- address oldOwner = owner();
350
- _updateAssignedWallet(EngineBlox.OWNER_ROLE, newOwner, oldOwner);
351
- emit OwnershipTransferUpdated(oldOwner, newOwner);
352
- }
353
-
354
- /**
355
- * @dev Updates the broadcaster role at a specific index (location)
356
- * @param newBroadcaster The new broadcaster address (zero address to revoke)
357
- * @param location The index in the broadcaster role's authorized wallets set
358
- *
359
- * Logic:
360
- * - If a broadcaster exists at `location` and `newBroadcaster` is non-zero,
361
- * update that slot from old to new (role remains full).
362
- * - If no broadcaster exists at `location` and `newBroadcaster` is non-zero,
363
- * assign `newBroadcaster` to the broadcaster role (respecting maxWallets).
364
- * - If `newBroadcaster` is the zero address and a broadcaster exists at `location`,
365
- * revoke that broadcaster from the role.
366
- */
367
- function _updateBroadcaster(address newBroadcaster, uint256 location) internal virtual {
368
- EngineBlox.Role storage role = _getSecureState().roles[EngineBlox.BROADCASTER_ROLE];
369
-
370
- address oldBroadcaster;
371
- uint256 length = role.walletCount;
372
-
373
- if (location < length) {
374
- oldBroadcaster = _getAuthorizedWalletAt(EngineBlox.BROADCASTER_ROLE, location);
375
- } else {
376
- oldBroadcaster = address(0);
377
- }
378
-
379
- // Case 1: Revoke existing broadcaster at location
380
- if (newBroadcaster == address(0)) {
381
- if (oldBroadcaster != address(0)) {
382
- _revokeWallet(EngineBlox.BROADCASTER_ROLE, oldBroadcaster);
383
- emit BroadcasterUpdated(oldBroadcaster, address(0));
384
- }
385
- return;
386
- }
387
-
388
- // Case 2: Update existing broadcaster at location
389
- if (oldBroadcaster != address(0)) {
390
- _updateAssignedWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster, oldBroadcaster);
391
- emit BroadcasterUpdated(oldBroadcaster, newBroadcaster);
392
- return;
393
- }
394
-
395
- // Case 3: No broadcaster at location, assign a new one (will respect maxWallets)
396
- _assignWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster);
397
- emit BroadcasterUpdated(address(0), newBroadcaster);
398
- }
399
-
400
- /**
401
- * @dev Updates the recovery address
402
- * @param newRecoveryAddress The new recovery address
403
- */
404
- function _updateRecoveryAddress(address newRecoveryAddress) internal virtual {
405
- address oldRecovery = getRecovery();
406
- _updateAssignedWallet(EngineBlox.RECOVERY_ROLE, newRecoveryAddress, oldRecovery);
407
- emit RecoveryAddressUpdated(oldRecovery, newRecoveryAddress);
408
- }
409
-
410
- /**
411
- * @dev Updates the time lock period
412
- * @param newTimeLockPeriodSec The new time lock period in seconds
413
- */
414
- function _updateTimeLockPeriod(uint256 newTimeLockPeriodSec) internal virtual override {
415
- uint256 oldPeriod = getTimeLockPeriodSec();
416
- super._updateTimeLockPeriod(newTimeLockPeriodSec);
417
- emit TimeLockPeriodUpdated(oldPeriod, newTimeLockPeriodSec);
418
- }
419
- }
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ pragma solidity 0.8.33;
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
+ * At most one ownership-transfer or broadcaster-update request may be pending at a time:
31
+ * a pending request of either type blocks new requests until it is approved or cancelled.
32
+ *
33
+ * This contract focuses purely on security logic while leveraging the BaseStateMachine
34
+ * for transaction management, meta-transactions, and state machine operations.
35
+ */
36
+ abstract contract SecureOwnable is BaseStateMachine, ISecureOwnable {
37
+ using SharedValidation for *;
38
+
39
+ /// @dev True while any pending ownership transfer or broadcaster update request exists; blocks new requests until handled.
40
+ bool private _hasOpenRequest;
41
+
42
+ /**
43
+ * @notice Initializer to initialize SecureOwnable state
44
+ * @param initialOwner The initial owner address
45
+ * @param broadcaster The broadcaster address
46
+ * @param recovery The recovery address
47
+ * @param timeLockPeriodSec The timelock period in seconds
48
+ * @param eventForwarder The event forwarder address
49
+ */
50
+ function initialize(
51
+ address initialOwner,
52
+ address broadcaster,
53
+ address recovery,
54
+ uint256 timeLockPeriodSec,
55
+ address eventForwarder
56
+ ) public virtual onlyInitializing {
57
+ // Initialize base state machine (only if not already initialized)
58
+ if (!_secureState.initialized) {
59
+ _initializeBaseStateMachine(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
60
+ }
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 // Allow protected schemas for factory settings
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();
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
+ _hasOpenRequest = true;
102
+ _logComponentEvent(abi.encode(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
+ * @param newBroadcaster The new broadcaster address (zero address to revoke at location)
150
+ * @param location The index in the broadcaster role's authorized wallets set
151
+ * @return txId The transaction ID for the pending request (use getTransaction(txId) for full record)
152
+ */
153
+ function updateBroadcasterRequest(address newBroadcaster, uint256 location) public returns (uint256 txId) {
154
+ SharedValidation.validateOwner(owner());
155
+ _requireNoPendingRequest();
156
+
157
+ // Get the current broadcaster at the specified location. zero address if no broadcaster at location.
158
+ address currentBroadcaster = location < _getSecureState().roles[EngineBlox.BROADCASTER_ROLE].walletCount
159
+ ? _getAuthorizedWalletAt(EngineBlox.BROADCASTER_ROLE, location)
160
+ : address(0);
161
+
162
+ EngineBlox.TxRecord memory txRecord = _requestTransaction(
163
+ msg.sender,
164
+ address(this),
165
+ 0, // value
166
+ 0, // gas limit
167
+ SecureOwnableDefinitions.BROADCASTER_UPDATE,
168
+ SecureOwnableDefinitions.UPDATE_BROADCASTER_SELECTOR,
169
+ abi.encode(newBroadcaster, location)
170
+ );
171
+
172
+ _hasOpenRequest = true;
173
+ _logComponentEvent(abi.encode(currentBroadcaster, newBroadcaster));
174
+ return txRecord.txId;
175
+ }
176
+
177
+ /**
178
+ * @dev Approves a pending broadcaster update transaction after the release time
179
+ * @param txId The transaction ID
180
+ * @return The transaction ID
181
+ */
182
+ function updateBroadcasterDelayedApproval(uint256 txId) public returns (uint256) {
183
+ SharedValidation.validateOwner(owner());
184
+ return _completeApprove(_approveTransaction(txId));
185
+ }
186
+
187
+ /**
188
+ * @dev Approves a pending broadcaster update transaction using a meta-transaction
189
+ * @param metaTx The meta-transaction
190
+ * @return The transaction ID
191
+ */
192
+ function updateBroadcasterApprovalWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
193
+ _validateBroadcasterAndOwnerSigner(metaTx);
194
+ return _completeApprove(_approveTransactionWithMetaTx(metaTx));
195
+ }
196
+
197
+ /**
198
+ * @dev Cancels a pending broadcaster update transaction
199
+ * @param txId The transaction ID
200
+ * @return The transaction ID
201
+ */
202
+ function updateBroadcasterCancellation(uint256 txId) public returns (uint256) {
203
+ SharedValidation.validateOwner(owner());
204
+ return _completeCancel(_cancelTransaction(txId));
205
+ }
206
+
207
+ /**
208
+ * @dev Cancels a pending broadcaster update transaction using a meta-transaction
209
+ * @param metaTx The meta-transaction
210
+ * @return The transaction ID
211
+ */
212
+ function updateBroadcasterCancellationWithMetaTx(EngineBlox.MetaTransaction memory metaTx) public returns (uint256) {
213
+ _validateBroadcasterAndOwnerSigner(metaTx);
214
+ return _completeCancel(_cancelTransactionWithMetaTx(metaTx));
215
+ }
216
+
217
+ // Recovery Management
218
+
219
+ /**
220
+ * @dev Requests and approves a recovery address update using a meta-transaction
221
+ * @param metaTx The meta-transaction
222
+ * @return The transaction ID
223
+ */
224
+ function updateRecoveryRequestAndApprove(
225
+ EngineBlox.MetaTransaction memory metaTx
226
+ ) public returns (uint256) {
227
+ _validateBroadcasterAndOwnerSigner(metaTx);
228
+ EngineBlox.TxRecord memory txRecord = _requestAndApproveTransaction(metaTx);
229
+ return txRecord.txId;
230
+ }
231
+
232
+ // TimeLock Management
233
+
234
+ /**
235
+ * @dev Requests and approves a time lock period update using a meta-transaction
236
+ * @param metaTx The meta-transaction
237
+ * @return The transaction ID
238
+ */
239
+ function updateTimeLockRequestAndApprove(
240
+ EngineBlox.MetaTransaction memory metaTx
241
+ ) public returns (uint256) {
242
+ _validateBroadcasterAndOwnerSigner(metaTx);
243
+ EngineBlox.TxRecord memory txRecord = _requestAndApproveTransaction(metaTx);
244
+ return txRecord.txId;
245
+ }
246
+
247
+ // Execution Functions
248
+ /**
249
+ * @dev External function that can only be called by the contract itself to execute ownership transfer
250
+ * @param newOwner The new owner address
251
+ */
252
+ function executeTransferOwnership(address newOwner) external {
253
+ _validateExecuteBySelf();
254
+ _transferOwnership(newOwner);
255
+ }
256
+
257
+ /**
258
+ * @dev External function that can only be called by the contract itself to execute broadcaster update
259
+ * @param newBroadcaster The new broadcaster address (zero address to revoke at location)
260
+ * @param location The index in the broadcaster role's authorized wallets set
261
+ */
262
+ function executeBroadcasterUpdate(address newBroadcaster, uint256 location) external {
263
+ _validateExecuteBySelf();
264
+ _updateBroadcaster(newBroadcaster, location);
265
+ }
266
+
267
+ /**
268
+ * @dev External function that can only be called by the contract itself to execute recovery update
269
+ * @param newRecoveryAddress The new recovery address
270
+ */
271
+ function executeRecoveryUpdate(address newRecoveryAddress) external {
272
+ _validateExecuteBySelf();
273
+ _updateRecoveryAddress(newRecoveryAddress);
274
+ }
275
+
276
+ /**
277
+ * @dev External function that can only be called by the contract itself to execute timelock update
278
+ * @param newTimeLockPeriodSec The new timelock period in seconds
279
+ */
280
+ function executeTimeLockUpdate(uint256 newTimeLockPeriodSec) external {
281
+ _validateExecuteBySelf();
282
+ _updateTimeLockPeriod(newTimeLockPeriodSec);
283
+ }
284
+
285
+ // ============ INTERNAL FUNCTIONS ============
286
+
287
+ /**
288
+ * @dev Reverts if an ownership-transfer or broadcaster-update request is already pending.
289
+ */
290
+ function _requireNoPendingRequest() internal view {
291
+ if (_hasOpenRequest) revert SharedValidation.PendingSecureRequest();
292
+ }
293
+
294
+ /**
295
+ * @dev Validates that the caller is the broadcaster and that the meta-tx signer is the owner.
296
+ * @param metaTx The meta-transaction to validate
297
+ */
298
+ function _validateBroadcasterAndOwnerSigner(EngineBlox.MetaTransaction memory metaTx) internal view {
299
+ _validateBroadcaster(msg.sender);
300
+ SharedValidation.validateOwnerIsSigner(metaTx.params.signer, owner());
301
+ }
302
+
303
+ /**
304
+ * @dev Completes ownership/broadcaster flow after approval: resets flag and returns txId.
305
+ * @param updatedRecord The updated transaction record from approval
306
+ * @return txId The transaction ID
307
+ */
308
+ function _completeApprove(EngineBlox.TxRecord memory updatedRecord) internal returns (uint256 txId) {
309
+ _hasOpenRequest = false;
310
+ return updatedRecord.txId;
311
+ }
312
+
313
+ /**
314
+ * @dev Completes ownership/broadcaster flow after cancellation: resets flag, logs txId, returns txId.
315
+ * @param updatedRecord The updated transaction record from cancellation
316
+ * @return txId The transaction ID
317
+ */
318
+ function _completeCancel(EngineBlox.TxRecord memory updatedRecord) internal returns (uint256 txId) {
319
+ _hasOpenRequest = false;
320
+ _logComponentEvent(abi.encode(updatedRecord.txId));
321
+ return updatedRecord.txId;
322
+ }
323
+
324
+ /**
325
+ * @dev Transfers ownership of the contract
326
+ * @param newOwner The new owner of the contract
327
+ */
328
+ function _transferOwnership(address newOwner) internal virtual {
329
+ address oldOwner = owner();
330
+ _updateAssignedWallet(EngineBlox.OWNER_ROLE, newOwner, oldOwner);
331
+ _logComponentEvent(abi.encode(oldOwner, newOwner));
332
+ }
333
+
334
+ /**
335
+ * @dev Updates the broadcaster role at a specific index (location)
336
+ * @param newBroadcaster The new broadcaster address (zero address to revoke)
337
+ * @param location The index in the broadcaster role's authorized wallets set
338
+ *
339
+ * Logic:
340
+ * - If a broadcaster exists at `location` and `newBroadcaster` is non-zero,
341
+ * update that slot from old to new (role remains full).
342
+ * - If no broadcaster exists at `location` and `newBroadcaster` is non-zero,
343
+ * assign `newBroadcaster` to the broadcaster role (respecting maxWallets).
344
+ * - If `newBroadcaster` is the zero address and a broadcaster exists at `location`,
345
+ * revoke that broadcaster from the role.
346
+ */
347
+ function _updateBroadcaster(address newBroadcaster, uint256 location) internal virtual {
348
+ EngineBlox.Role storage role = _getSecureState().roles[EngineBlox.BROADCASTER_ROLE];
349
+
350
+ address oldBroadcaster;
351
+ uint256 length = role.walletCount;
352
+
353
+ if (location < length) {
354
+ oldBroadcaster = _getAuthorizedWalletAt(EngineBlox.BROADCASTER_ROLE, location);
355
+ } else {
356
+ oldBroadcaster = address(0);
357
+ }
358
+
359
+ // Case 1: Revoke existing broadcaster at location
360
+ if (newBroadcaster == address(0)) {
361
+ if (oldBroadcaster != address(0)) {
362
+ _revokeWallet(EngineBlox.BROADCASTER_ROLE, oldBroadcaster);
363
+ _logComponentEvent(abi.encode(oldBroadcaster, address(0)));
364
+ }
365
+ return;
366
+ }
367
+
368
+ // Case 2: Update existing broadcaster at location
369
+ if (oldBroadcaster != address(0)) {
370
+ _updateAssignedWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster, oldBroadcaster);
371
+ _logComponentEvent(abi.encode(oldBroadcaster, newBroadcaster));
372
+ return;
373
+ }
374
+
375
+ // Case 3: No broadcaster at location, assign a new one (will respect maxWallets)
376
+ _assignWallet(EngineBlox.BROADCASTER_ROLE, newBroadcaster);
377
+ _logComponentEvent(abi.encode(address(0), newBroadcaster));
378
+ }
379
+
380
+ /**
381
+ * @dev Updates the recovery address
382
+ * @param newRecoveryAddress The new recovery address
383
+ */
384
+ function _updateRecoveryAddress(address newRecoveryAddress) internal virtual {
385
+ address oldRecovery = getRecovery();
386
+ _updateAssignedWallet(EngineBlox.RECOVERY_ROLE, newRecoveryAddress, oldRecovery);
387
+ _logComponentEvent(abi.encode(oldRecovery, newRecoveryAddress));
388
+ }
389
+
390
+ }