@bloxchain/contracts 1.0.0-alpha.17 → 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/EngineBlox.abi.json +3 -29
- 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 +80 -27
- package/core/pattern/Account.sol +10 -1
- package/core/security/SecureOwnable.sol +424 -394
- package/package.json +1 -1
package/abi/EngineBlox.abi.json
CHANGED
|
@@ -689,38 +689,12 @@
|
|
|
689
689
|
},
|
|
690
690
|
{
|
|
691
691
|
"inputs": [],
|
|
692
|
-
"name": "
|
|
692
|
+
"name": "VERSION",
|
|
693
693
|
"outputs": [
|
|
694
694
|
{
|
|
695
|
-
"internalType": "
|
|
696
|
-
"name": "",
|
|
697
|
-
"type": "uint8"
|
|
698
|
-
}
|
|
699
|
-
],
|
|
700
|
-
"stateMutability": "view",
|
|
701
|
-
"type": "function"
|
|
702
|
-
},
|
|
703
|
-
{
|
|
704
|
-
"inputs": [],
|
|
705
|
-
"name": "VERSION_MINOR",
|
|
706
|
-
"outputs": [
|
|
707
|
-
{
|
|
708
|
-
"internalType": "uint8",
|
|
695
|
+
"internalType": "string",
|
|
709
696
|
"name": "",
|
|
710
|
-
"type": "
|
|
711
|
-
}
|
|
712
|
-
],
|
|
713
|
-
"stateMutability": "view",
|
|
714
|
-
"type": "function"
|
|
715
|
-
},
|
|
716
|
-
{
|
|
717
|
-
"inputs": [],
|
|
718
|
-
"name": "VERSION_PATCH",
|
|
719
|
-
"outputs": [
|
|
720
|
-
{
|
|
721
|
-
"internalType": "uint8",
|
|
722
|
-
"name": "",
|
|
723
|
-
"type": "uint8"
|
|
697
|
+
"type": "string"
|
|
724
698
|
}
|
|
725
699
|
],
|
|
726
700
|
"stateMutability": "view",
|
|
@@ -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
|
@@ -32,9 +32,7 @@ import "./interfaces/IEventForwarder.sol";
|
|
|
32
32
|
library EngineBlox {
|
|
33
33
|
// ============ VERSION INFORMATION ============
|
|
34
34
|
bytes32 public constant PROTOCOL_NAME_HASH = keccak256("Bloxchain");
|
|
35
|
-
|
|
36
|
-
uint8 public constant VERSION_MINOR = 0;
|
|
37
|
-
uint8 public constant VERSION_PATCH = 0;
|
|
35
|
+
string public constant VERSION = "1.0.0";
|
|
38
36
|
|
|
39
37
|
// ============ SYSTEM SAFETY LIMITS ============
|
|
40
38
|
// These constants define the safety range limits for system operations
|
|
@@ -206,8 +204,25 @@ library EngineBlox {
|
|
|
206
204
|
bytes32 public constant NATIVE_TRANSFER_OPERATION = keccak256("NATIVE_TRANSFER");
|
|
207
205
|
|
|
208
206
|
// EIP-712 Type Hashes (selective meta-tx payload: MetaTxRecord = txId + params + payment only)
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
// These follow the canonical EIP-712 convention so that eth_signTypedData_v4 and equivalent
|
|
208
|
+
// wallet typed-data signers can reproduce the same hashes when given matching type definitions.
|
|
209
|
+
//
|
|
210
|
+
// Canonical primary type string for MetaTransaction (primary type + all referenced types,
|
|
211
|
+
// appended in alphabetical order by type name
|
|
212
|
+
bytes32 private constant META_TX_TYPE_HASH = keccak256(
|
|
213
|
+
"MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)"
|
|
214
|
+
"MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)"
|
|
215
|
+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
|
|
216
|
+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
|
|
217
|
+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Canonical primary type string for MetaTxRecord (primary type + its referenced types).
|
|
221
|
+
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256(
|
|
222
|
+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
|
|
223
|
+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
|
|
224
|
+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
|
|
225
|
+
);
|
|
211
226
|
bytes32 private constant TX_PARAMS_TYPE_HASH = keccak256("TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)");
|
|
212
227
|
bytes32 private constant META_TX_PARAMS_TYPE_HASH = keccak256("MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)");
|
|
213
228
|
bytes32 private constant PAYMENT_DETAILS_TYPE_HASH = keccak256("PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
|
|
@@ -361,6 +376,16 @@ library EngineBlox {
|
|
|
361
376
|
// Validate both execution and handler selector permissions (same as txRequest)
|
|
362
377
|
_validateExecutionAndHandlerPermissions(self, msg.sender, executionSelector, handlerSelector, TxAction.EXECUTE_TIME_DELAY_REQUEST);
|
|
363
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
|
+
|
|
364
389
|
return _txRequest(
|
|
365
390
|
self,
|
|
366
391
|
requester,
|
|
@@ -559,6 +584,16 @@ library EngineBlox {
|
|
|
559
584
|
// Validate both execution and handler selector permissions
|
|
560
585
|
_validateExecutionAndHandlerPermissions(self, msg.sender, metaTx.txRecord.params.executionSelector, metaTx.params.handlerSelector, TxAction.EXECUTE_META_REQUEST_AND_APPROVE);
|
|
561
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
|
+
|
|
562
597
|
TxRecord memory txRecord = _txRequest(
|
|
563
598
|
self,
|
|
564
599
|
metaTx.txRecord.params.requester,
|
|
@@ -1690,27 +1725,36 @@ library EngineBlox {
|
|
|
1690
1725
|
|
|
1691
1726
|
/**
|
|
1692
1727
|
* @dev Generates the EIP-712 message hash for the meta-transaction.
|
|
1693
|
-
* Uses selective MetaTxRecord (txId, params, payment only)
|
|
1694
|
-
*
|
|
1695
|
-
*
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
1728
|
+
* Uses selective MetaTxRecord (txId, params, payment only) with standard EIP-712 type hashes
|
|
1729
|
+
* so that eth_signTypedData_v4 (and equivalent) can reproduce the same digest when given
|
|
1730
|
+
* matching domain + types:
|
|
1731
|
+
*
|
|
1732
|
+
* - primaryType: MetaTransaction
|
|
1733
|
+
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
|
|
1734
|
+
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
|
|
1735
|
+
*
|
|
1736
|
+
* Integrators MAY:
|
|
1737
|
+
* - use typed-data signing (eth_signTypedData_v4 / signTypedData) with the above domain/types, or
|
|
1738
|
+
* - sign the resulting digest as a raw hash (e.g. account.sign({ hash: contractDigest })).
|
|
1739
|
+
*
|
|
1740
|
+
* In all cases, on-chain verification uses recoverSigner(messageHash, signature) which applies
|
|
1741
|
+
* ecrecover(messageHash, v, r, s) with no personal_sign / EIP-191 prefix.
|
|
1742
|
+
*
|
|
1743
|
+
* The resulting digest is also written into the `message` field of helper-built `MetaTransaction`
|
|
1744
|
+
* structs so integrators can use it directly without recomputing the hash client-side.
|
|
1703
1745
|
* @param metaTx The meta-transaction to generate the hash for
|
|
1704
1746
|
* @return The EIP-712 digest (no prefix; use standard recovery)
|
|
1705
1747
|
*/
|
|
1706
1748
|
function generateMessageHash(MetaTransaction memory metaTx) private view returns (bytes32) {
|
|
1707
|
-
bytes32 domainSeparator = keccak256(
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1749
|
+
bytes32 domainSeparator = keccak256(
|
|
1750
|
+
abi.encode(
|
|
1751
|
+
DOMAIN_SEPARATOR_TYPE_HASH,
|
|
1752
|
+
PROTOCOL_NAME_HASH,
|
|
1753
|
+
keccak256(bytes(VERSION)),
|
|
1754
|
+
block.chainid,
|
|
1755
|
+
address(this)
|
|
1756
|
+
)
|
|
1757
|
+
);
|
|
1714
1758
|
|
|
1715
1759
|
TxParams memory tp = metaTx.txRecord.params;
|
|
1716
1760
|
bytes32 txParamsStructHash = keccak256(abi.encode(
|
|
@@ -1769,11 +1813,20 @@ library EngineBlox {
|
|
|
1769
1813
|
|
|
1770
1814
|
/**
|
|
1771
1815
|
* @dev Recovers the signer from the EIP-712 digest and signature. Uses standard EIP-712 recovery (no message prefix).
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
*
|
|
1775
|
-
*
|
|
1776
|
-
*
|
|
1816
|
+
*
|
|
1817
|
+
* Integrators have two equivalent options:
|
|
1818
|
+
* - Use typed-data signing (eth_signTypedData_v4 / signTypedData) with:
|
|
1819
|
+
* - primaryType: MetaTransaction
|
|
1820
|
+
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
|
|
1821
|
+
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
|
|
1822
|
+
* In this case the wallet computes the same digest as generateMessageHash and signs it.
|
|
1823
|
+
* - Sign the digest returned by generateMessageHash as a raw hash—e.g.
|
|
1824
|
+
* account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—with no
|
|
1825
|
+
* EIP-191/personal prefix.
|
|
1826
|
+
*
|
|
1827
|
+
* In all cases, this function applies ecrecover(messageHash, v, r, s) over the raw EIP-712 digest.
|
|
1828
|
+
* personal_sign / EIP-191-prefixed signatures remain incompatible.
|
|
1829
|
+
*
|
|
1777
1830
|
* @param messageHash The EIP-712 digest (keccak256("\x19\x01" || domainSeparator || structHash))
|
|
1778
1831
|
* @param signature The signature (r, s, v)
|
|
1779
1832
|
* @return The address of the signer
|
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
|
+
}
|