@bloxchain/contracts 1.0.0-alpha.16 → 1.0.0-alpha.18
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 +25 -29
- package/core/access/RuntimeRBAC.sol +40 -6
- package/core/access/interface/IRuntimeRBAC.sol +2 -2
- package/core/base/BaseStateMachine.sol +1 -0
- package/core/execution/GuardController.sol +7 -7
- package/core/execution/interface/IGuardController.sol +3 -3
- package/core/lib/EngineBlox.sol +262 -85
- package/core/lib/utils/SharedValidation.sol +6 -4
- package/package.json +1 -1
package/abi/EngineBlox.abi.json
CHANGED
|
@@ -349,6 +349,28 @@
|
|
|
349
349
|
"name": "MetaTxExpired",
|
|
350
350
|
"type": "error"
|
|
351
351
|
},
|
|
352
|
+
{
|
|
353
|
+
"inputs": [
|
|
354
|
+
{
|
|
355
|
+
"internalType": "uint256",
|
|
356
|
+
"name": "txId",
|
|
357
|
+
"type": "uint256"
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
"name": "MetaTxPaymentMismatchStoredTx",
|
|
361
|
+
"type": "error"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"inputs": [
|
|
365
|
+
{
|
|
366
|
+
"internalType": "uint256",
|
|
367
|
+
"name": "txId",
|
|
368
|
+
"type": "uint256"
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
"name": "MetaTxRecordMismatchStoredTx",
|
|
372
|
+
"type": "error"
|
|
373
|
+
},
|
|
352
374
|
{
|
|
353
375
|
"inputs": [
|
|
354
376
|
{
|
|
@@ -667,38 +689,12 @@
|
|
|
667
689
|
},
|
|
668
690
|
{
|
|
669
691
|
"inputs": [],
|
|
670
|
-
"name": "
|
|
671
|
-
"outputs": [
|
|
672
|
-
{
|
|
673
|
-
"internalType": "uint8",
|
|
674
|
-
"name": "",
|
|
675
|
-
"type": "uint8"
|
|
676
|
-
}
|
|
677
|
-
],
|
|
678
|
-
"stateMutability": "view",
|
|
679
|
-
"type": "function"
|
|
680
|
-
},
|
|
681
|
-
{
|
|
682
|
-
"inputs": [],
|
|
683
|
-
"name": "VERSION_MINOR",
|
|
692
|
+
"name": "VERSION",
|
|
684
693
|
"outputs": [
|
|
685
694
|
{
|
|
686
|
-
"internalType": "
|
|
695
|
+
"internalType": "string",
|
|
687
696
|
"name": "",
|
|
688
|
-
"type": "
|
|
689
|
-
}
|
|
690
|
-
],
|
|
691
|
-
"stateMutability": "view",
|
|
692
|
-
"type": "function"
|
|
693
|
-
},
|
|
694
|
-
{
|
|
695
|
-
"inputs": [],
|
|
696
|
-
"name": "VERSION_PATCH",
|
|
697
|
-
"outputs": [
|
|
698
|
-
{
|
|
699
|
-
"internalType": "uint8",
|
|
700
|
-
"name": "",
|
|
701
|
-
"type": "uint8"
|
|
697
|
+
"type": "string"
|
|
702
698
|
}
|
|
703
699
|
],
|
|
704
700
|
"stateMutability": "view",
|
|
@@ -12,18 +12,28 @@ import "./interface/IRuntimeRBAC.sol";
|
|
|
12
12
|
/**
|
|
13
13
|
* @title RuntimeRBAC
|
|
14
14
|
* @dev Minimal Runtime Role-Based Access Control system based on EngineBlox
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* This contract provides essential runtime RBAC functionality:
|
|
17
17
|
* - Creation of non-protected roles
|
|
18
18
|
* - Basic wallet assignment to roles
|
|
19
19
|
* - Function permission management per role
|
|
20
20
|
* - Integration with EngineBlox for secure operations
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
22
|
* Key Features:
|
|
23
23
|
* - Only non-protected roles can be created dynamically
|
|
24
24
|
* - Protected roles (OWNER, BROADCASTER, RECOVERY) are managed by SecureOwnable
|
|
25
25
|
* - Minimal interface for core RBAC operations
|
|
26
26
|
* - Essential role management functions only
|
|
27
|
+
*
|
|
28
|
+
* @custom:security PROTECTED-ROLE POLICY (defense in layers):
|
|
29
|
+
* - RuntimeRBAC is **unauthorized** to modify protected roles (wallet add/revoke/remove).
|
|
30
|
+
* - For ADD_WALLET and REVOKE_WALLET we call _requireRoleNotProtected so batch ops cannot
|
|
31
|
+
* change who holds system roles. For REMOVE_ROLE we rely on EngineBlox.removeRole, which
|
|
32
|
+
* enforces the same policy at the library layer (cannot remove protected roles).
|
|
33
|
+
* - The **only** place to modify system wallets (protected roles) is the SecureOwnable
|
|
34
|
+
* security component (e.g. transferOwnershipRequest, broadcaster/recovery changes).
|
|
35
|
+
* - This layering is intentional: RBAC cannot touch protected roles; SecureOwnable is the
|
|
36
|
+
* single source of truth for system wallet changes.
|
|
27
37
|
*/
|
|
28
38
|
abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
29
39
|
using EngineBlox for EngineBlox.SecureOperationState;
|
|
@@ -71,7 +81,7 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
71
81
|
/**
|
|
72
82
|
* @dev Requests and approves a RBAC configuration batch using a meta-transaction
|
|
73
83
|
* @param metaTx The meta-transaction
|
|
74
|
-
* @return The transaction
|
|
84
|
+
* @return The transaction ID of the applied batch
|
|
75
85
|
* @notice OWNER signs, BROADCASTER executes according to RuntimeRBACDefinitions
|
|
76
86
|
*/
|
|
77
87
|
function roleConfigBatchRequestAndApprove(
|
|
@@ -85,6 +95,13 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
85
95
|
/**
|
|
86
96
|
* @dev External function that can only be called by the contract itself to execute a RBAC configuration batch
|
|
87
97
|
* @param actions Encoded role configuration actions
|
|
98
|
+
*
|
|
99
|
+
* ## Role config batch ordering (required to avoid revert and gas waste)
|
|
100
|
+
*
|
|
101
|
+
* Actions must be ordered so that dependencies are satisfied:
|
|
102
|
+
* - **CREATE_ROLE** must appear before **ADD_WALLET** or **ADD_FUNCTION_TO_ROLE** for the same role; otherwise the role does not exist and the add will revert.
|
|
103
|
+
* - **REMOVE_ROLE** should be used only for an existing role; use **REVOKE_WALLET** first if the role has assigned wallets (optional but recommended for clarity).
|
|
104
|
+
* - For a given role, typical order: CREATE_ROLE → ADD_WALLET / ADD_FUNCTION_TO_ROLE as needed; to remove: REVOKE_WALLET (and REMOVE_FUNCTION_FROM_ROLE) as needed → REMOVE_ROLE.
|
|
88
105
|
*/
|
|
89
106
|
function executeRoleConfigBatch(IRuntimeRBAC.RoleConfigAction[] calldata actions) external {
|
|
90
107
|
_validateExecuteBySelf();
|
|
@@ -95,6 +112,9 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
95
112
|
|
|
96
113
|
/**
|
|
97
114
|
* @dev Reverts if the role is protected (prevents editing OWNER, BROADCASTER, RECOVERY via batch).
|
|
115
|
+
* Used for ADD_WALLET and REVOKE_WALLET so RuntimeRBAC cannot change who holds system roles.
|
|
116
|
+
* REMOVE_ROLE is not checked here; EngineBlox.removeRole enforces protected-role policy at
|
|
117
|
+
* the library layer. See contract-level @custom:security PROTECTED-ROLE POLICY.
|
|
98
118
|
* @param roleHash The role hash to check
|
|
99
119
|
*/
|
|
100
120
|
function _requireRoleNotProtected(bytes32 roleHash) internal view {
|
|
@@ -106,6 +126,10 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
106
126
|
/**
|
|
107
127
|
* @dev Internal helper to execute a RBAC configuration batch
|
|
108
128
|
* @param actions Encoded role configuration actions
|
|
129
|
+
*
|
|
130
|
+
* @custom:order Required ordering to avoid revert and gas waste:
|
|
131
|
+
* 1. CREATE_ROLE before any ADD_WALLET or ADD_FUNCTION_TO_ROLE for that role.
|
|
132
|
+
* 2. REMOVE_ROLE only for a role that exists; prefer REVOKE_WALLET (and REMOVE_FUNCTION_FROM_ROLE) before REMOVE_ROLE when the role has members.
|
|
109
133
|
*/
|
|
110
134
|
function _executeRoleConfigBatch(IRuntimeRBAC.RoleConfigAction[] calldata actions) internal {
|
|
111
135
|
_validateBatchSize(actions.length);
|
|
@@ -142,7 +166,11 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
142
166
|
}
|
|
143
167
|
|
|
144
168
|
/**
|
|
145
|
-
* @dev Executes REMOVE_ROLE: removes a role by hash
|
|
169
|
+
* @dev Executes REMOVE_ROLE: removes a role by hash.
|
|
170
|
+
* Protected-role check is enforced in EngineBlox.removeRole (library layer); RuntimeRBAC
|
|
171
|
+
* does not duplicate it here. SecureOwnable is the only component authorized to change
|
|
172
|
+
* system wallets; RBAC is unauthorized to modify protected roles. See @custom:security
|
|
173
|
+
* PROTECTED-ROLE POLICY on the contract.
|
|
146
174
|
* @param data ABI-encoded (bytes32 roleHash)
|
|
147
175
|
*/
|
|
148
176
|
function _executeRemoveRole(bytes calldata data) internal {
|
|
@@ -174,8 +202,11 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
174
202
|
}
|
|
175
203
|
|
|
176
204
|
/**
|
|
177
|
-
* @dev Executes ADD_FUNCTION_TO_ROLE: adds a function permission to a role
|
|
205
|
+
* @dev Executes ADD_FUNCTION_TO_ROLE: adds a function permission to a role.
|
|
178
206
|
* @param data ABI-encoded (bytes32 roleHash, FunctionPermission functionPermission)
|
|
207
|
+
* @custom:security By design we allow adding function permissions to protected roles (OWNER, BROADCASTER, RECOVERY)
|
|
208
|
+
* to retain flexibility to grant new function permissions to system roles; only wallet add/revoke
|
|
209
|
+
* are restricted on protected roles.
|
|
179
210
|
*/
|
|
180
211
|
function _executeAddFunctionToRole(bytes calldata data) internal {
|
|
181
212
|
(
|
|
@@ -187,8 +218,11 @@ abstract contract RuntimeRBAC is BaseStateMachine, IRuntimeRBAC {
|
|
|
187
218
|
}
|
|
188
219
|
|
|
189
220
|
/**
|
|
190
|
-
* @dev Executes REMOVE_FUNCTION_FROM_ROLE: removes a function permission from a role
|
|
221
|
+
* @dev Executes REMOVE_FUNCTION_FROM_ROLE: removes a function permission from a role.
|
|
191
222
|
* @param data ABI-encoded (bytes32 roleHash, bytes4 functionSelector)
|
|
223
|
+
* @custom:security By design we allow removing function permissions from protected roles (OWNER, BROADCASTER, RECOVERY)
|
|
224
|
+
* to retain flexibility to adjust which functions system roles can call; only wallet add/revoke
|
|
225
|
+
* are restricted on protected roles.
|
|
192
226
|
*/
|
|
193
227
|
function _executeRemoveFunctionFromRole(bytes calldata data) internal {
|
|
194
228
|
(bytes32 roleHash, bytes4 functionSelector) = abi.decode(data, (bytes32, bytes4));
|
|
@@ -12,7 +12,7 @@ import "../../lib/EngineBlox.sol";
|
|
|
12
12
|
*
|
|
13
13
|
* Key Features:
|
|
14
14
|
* - Batch-based role configuration (atomic operations)
|
|
15
|
-
* -
|
|
15
|
+
* - Role and permission management (function schema registration is handled by GuardController)
|
|
16
16
|
* - Integration with EngineBlox for secure operations
|
|
17
17
|
* - Query functions for role and permission inspection
|
|
18
18
|
*
|
|
@@ -47,7 +47,7 @@ interface IRuntimeRBAC {
|
|
|
47
47
|
/**
|
|
48
48
|
* @dev Requests and approves a RBAC configuration batch using a meta-transaction
|
|
49
49
|
* @param metaTx The meta-transaction
|
|
50
|
-
* @return The transaction
|
|
50
|
+
* @return The transaction ID of the applied batch
|
|
51
51
|
*/
|
|
52
52
|
function roleConfigBatchRequestAndApprove(
|
|
53
53
|
EngineBlox.MetaTransaction memory metaTx
|
|
@@ -520,6 +520,7 @@ abstract contract BaseStateMachine is Initializable, ERC165Upgradeable, Reentran
|
|
|
520
520
|
* @dev Gets function schema information
|
|
521
521
|
* @param functionSelector The function selector to get information for
|
|
522
522
|
* @return The full FunctionSchema struct (functionSignature, functionSelector, operationType, operationName, supportedActionsBitmap, enforceHandlerRelations, isProtected, handlerForSelectors)
|
|
523
|
+
* @notice Reverts with ResourceNotFound if the schema does not exist
|
|
523
524
|
*/
|
|
524
525
|
function getFunctionSchema(bytes4 functionSelector) external view returns (EngineBlox.FunctionSchema memory) {
|
|
525
526
|
_validateAnyRole();
|
|
@@ -32,7 +32,7 @@ import "./interface/IGuardController.sol";
|
|
|
32
32
|
*
|
|
33
33
|
* Usage Flow:
|
|
34
34
|
* 1. Deploy GuardController (or combine with RuntimeRBAC/SecureOwnable for role management)
|
|
35
|
-
* 2. Function schemas
|
|
35
|
+
* 2. Function schemas are registered via definitions at init or via GuardController guard config batch (REGISTER_FUNCTION)
|
|
36
36
|
* 3. Create roles and assign function permissions with action bitmaps (via RuntimeRBAC if combined)
|
|
37
37
|
* 4. Assign wallets to roles (via RuntimeRBAC if combined)
|
|
38
38
|
* 5. Configure target whitelists per function selector (REQUIRED for execution)
|
|
@@ -173,7 +173,7 @@ abstract contract GuardController is BaseStateMachine {
|
|
|
173
173
|
/**
|
|
174
174
|
* @dev Approves and executes a time-locked transaction
|
|
175
175
|
* @param txId The transaction ID
|
|
176
|
-
* @return
|
|
176
|
+
* @return txId The transaction ID
|
|
177
177
|
* @notice Requires STANDARD execution type and EXECUTE_TIME_DELAY_APPROVE permission for the execution function
|
|
178
178
|
*/
|
|
179
179
|
function approveTimeLockExecution(
|
|
@@ -191,7 +191,7 @@ abstract contract GuardController is BaseStateMachine {
|
|
|
191
191
|
/**
|
|
192
192
|
* @dev Cancels a time-locked transaction
|
|
193
193
|
* @param txId The transaction ID
|
|
194
|
-
* @return The
|
|
194
|
+
* @return The transaction ID
|
|
195
195
|
* @notice Requires STANDARD execution type and EXECUTE_TIME_DELAY_CANCEL permission for the execution function
|
|
196
196
|
*/
|
|
197
197
|
function cancelTimeLockExecution(
|
|
@@ -209,7 +209,7 @@ abstract contract GuardController is BaseStateMachine {
|
|
|
209
209
|
/**
|
|
210
210
|
* @dev Approves a time-locked transaction using a meta-transaction
|
|
211
211
|
* @param metaTx The meta-transaction containing the transaction record and signature
|
|
212
|
-
* @return The
|
|
212
|
+
* @return The transaction ID
|
|
213
213
|
* @notice Requires STANDARD execution type and EXECUTE_META_APPROVE permission for the execution function
|
|
214
214
|
*/
|
|
215
215
|
function approveTimeLockExecutionWithMetaTx(
|
|
@@ -226,7 +226,7 @@ abstract contract GuardController is BaseStateMachine {
|
|
|
226
226
|
/**
|
|
227
227
|
* @dev Cancels a time-locked transaction using a meta-transaction
|
|
228
228
|
* @param metaTx The meta-transaction containing the transaction record and signature
|
|
229
|
-
* @return The
|
|
229
|
+
* @return The transaction ID
|
|
230
230
|
* @notice Requires STANDARD execution type and EXECUTE_META_CANCEL permission for the execution function
|
|
231
231
|
*/
|
|
232
232
|
function cancelTimeLockExecutionWithMetaTx(
|
|
@@ -243,7 +243,7 @@ abstract contract GuardController is BaseStateMachine {
|
|
|
243
243
|
/**
|
|
244
244
|
* @dev Requests and approves a transaction in one step using a meta-transaction
|
|
245
245
|
* @param metaTx The meta-transaction containing the transaction record and signature
|
|
246
|
-
* @return The transaction
|
|
246
|
+
* @return The transaction ID
|
|
247
247
|
* @notice Requires STANDARD execution type
|
|
248
248
|
* @notice Validates function schema and permissions for the execution function (same as executeWithTimeLock)
|
|
249
249
|
* @notice Requires EXECUTE_META_REQUEST_AND_APPROVE permission for the execution function selector
|
|
@@ -304,7 +304,7 @@ abstract contract GuardController is BaseStateMachine {
|
|
|
304
304
|
/**
|
|
305
305
|
* @dev Requests and approves a Guard configuration batch using a meta-transaction
|
|
306
306
|
* @param metaTx The meta-transaction
|
|
307
|
-
* @return The transaction
|
|
307
|
+
* @return The transaction ID
|
|
308
308
|
* @notice OWNER signs, BROADCASTER executes according to GuardControllerDefinitions
|
|
309
309
|
*/
|
|
310
310
|
function guardConfigBatchRequestAndApprove(
|
|
@@ -5,10 +5,10 @@ import "../../lib/EngineBlox.sol";
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @title IGuardController
|
|
8
|
-
* @dev Interface for GuardController contract that
|
|
8
|
+
* @dev Interface for GuardController contract that AccountBlox and other contracts delegate to
|
|
9
9
|
* @notice This interface defines only GuardController-specific methods
|
|
10
|
-
* @notice Functions from BaseStateMachine (createMetaTxParams, generateUnsignedMetaTransaction*, getTransaction,
|
|
11
|
-
* @notice Functions from RuntimeRBAC (
|
|
10
|
+
* @notice Functions from BaseStateMachine (createMetaTxParams, generateUnsignedMetaTransaction*, getTransaction, getFunctionSchema, owner, getBroadcasters, getRecovery) should be accessed via IBaseStateMachine
|
|
11
|
+
* @notice Functions from RuntimeRBAC (role management: createNewRole, addWalletToRole, revokeWallet, etc.) should be accessed via IRuntimeRBAC. Function schema registration is performed via GuardController (guard config batch), not RuntimeRBAC.
|
|
12
12
|
* @custom:security-contact security@particlecrypto.com
|
|
13
13
|
*/
|
|
14
14
|
interface IGuardController {
|
package/core/lib/EngineBlox.sol
CHANGED
|
@@ -3,7 +3,6 @@ pragma solidity 0.8.34;
|
|
|
3
3
|
|
|
4
4
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
5
5
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
6
|
-
import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
|
7
6
|
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
8
7
|
|
|
9
8
|
// Local imports
|
|
@@ -33,9 +32,7 @@ import "./interfaces/IEventForwarder.sol";
|
|
|
33
32
|
library EngineBlox {
|
|
34
33
|
// ============ VERSION INFORMATION ============
|
|
35
34
|
bytes32 public constant PROTOCOL_NAME_HASH = keccak256("Bloxchain");
|
|
36
|
-
|
|
37
|
-
uint8 public constant VERSION_MINOR = 0;
|
|
38
|
-
uint8 public constant VERSION_PATCH = 0;
|
|
35
|
+
string public constant VERSION = "1.0.0";
|
|
39
36
|
|
|
40
37
|
// ============ SYSTEM SAFETY LIMITS ============
|
|
41
38
|
// These constants define the safety range limits for system operations
|
|
@@ -53,7 +50,6 @@ library EngineBlox {
|
|
|
53
50
|
/// @dev Maximum total number of functions allowed in the system (prevents gas exhaustion in function operations)
|
|
54
51
|
uint256 public constant MAX_FUNCTIONS = 2000;
|
|
55
52
|
|
|
56
|
-
using MessageHashUtils for bytes32;
|
|
57
53
|
using SharedValidation for *;
|
|
58
54
|
using EnumerableSet for EnumerableSet.UintSet;
|
|
59
55
|
using EnumerableSet for EnumerableSet.Bytes32Set;
|
|
@@ -152,9 +148,11 @@ library EngineBlox {
|
|
|
152
148
|
bytes32 operationType;
|
|
153
149
|
string operationName;
|
|
154
150
|
uint16 supportedActionsBitmap; // Bitmap for TxAction enum (9 bits max)
|
|
155
|
-
|
|
151
|
+
/// @dev When true (strict mode): handlerForSelectors in role permissions must match this schema's handlerForSelectors at use time.
|
|
152
|
+
/// When false (flexible mode): no such check; forward references and unregistered selectors in handlerForSelectors are allowed at registration.
|
|
153
|
+
bool enforceHandlerRelations;
|
|
156
154
|
bool isProtected;
|
|
157
|
-
bytes4[] handlerForSelectors;
|
|
155
|
+
bytes4[] handlerForSelectors;
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
// ============ DEFINITION STRUCTS ============
|
|
@@ -205,8 +203,29 @@ library EngineBlox {
|
|
|
205
203
|
bytes4 public constant NATIVE_TRANSFER_SELECTOR = bytes4(keccak256("__bloxchain_native_transfer__()"));
|
|
206
204
|
bytes32 public constant NATIVE_TRANSFER_OPERATION = keccak256("NATIVE_TRANSFER");
|
|
207
205
|
|
|
208
|
-
// EIP-712 Type Hashes
|
|
209
|
-
|
|
206
|
+
// EIP-712 Type Hashes (selective meta-tx payload: MetaTxRecord = txId + params + payment only)
|
|
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
|
+
);
|
|
226
|
+
bytes32 private constant TX_PARAMS_TYPE_HASH = keccak256("TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)");
|
|
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)");
|
|
228
|
+
bytes32 private constant PAYMENT_DETAILS_TYPE_HASH = keccak256("PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
|
|
210
229
|
bytes32 private constant DOMAIN_SEPARATOR_TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
|
211
230
|
|
|
212
231
|
|
|
@@ -483,6 +502,8 @@ library EngineBlox {
|
|
|
483
502
|
// Validate both execution and handler selector permissions
|
|
484
503
|
_validateExecutionAndHandlerPermissions(self, msg.sender, metaTx.txRecord.params.executionSelector, metaTx.params.handlerSelector, TxAction.EXECUTE_META_CANCEL);
|
|
485
504
|
_validateTxStatus(self, txId, TxStatus.PENDING);
|
|
505
|
+
_validateMetaTxMatchRecord(self, txId, metaTx.txRecord);
|
|
506
|
+
_validateMetaTxPaymentMatchRecord(self, txId, metaTx.txRecord);
|
|
486
507
|
if (!verifySignature(self, metaTx)) revert SharedValidation.InvalidSignature(metaTx.signature);
|
|
487
508
|
|
|
488
509
|
incrementSignerNonce(self, metaTx.params.signer);
|
|
@@ -513,10 +534,16 @@ library EngineBlox {
|
|
|
513
534
|
* @return The updated TxRecord.
|
|
514
535
|
* @notice This function skips permission validation and should only be called from functions
|
|
515
536
|
* that have already validated permissions.
|
|
537
|
+
* @custom:security TIMELOCK: The releaseTime (timelock) is intentionally NOT enforced on this path.
|
|
538
|
+
* This is by design: the meta-tx workflow allows authorized signers to approve execution
|
|
539
|
+
* without waiting for releaseTime, providing a hybrid synergy between timelock workflows
|
|
540
|
+
* (direct path enforces releaseTime) and meta-tx workflows (delegated, time-flexible approval).
|
|
516
541
|
*/
|
|
517
542
|
function _txApprovalWithMetaTx(SecureOperationState storage self, MetaTransaction memory metaTx) private returns (TxRecord memory) {
|
|
518
543
|
uint256 txId = metaTx.txRecord.txId;
|
|
519
544
|
_validateTxStatus(self, txId, TxStatus.PENDING);
|
|
545
|
+
_validateMetaTxMatchRecord(self, txId, metaTx.txRecord);
|
|
546
|
+
_validateMetaTxPaymentMatchRecord(self, txId, metaTx.txRecord);
|
|
520
547
|
if (!verifySignature(self, metaTx)) revert SharedValidation.InvalidSignature(metaTx.signature);
|
|
521
548
|
|
|
522
549
|
incrementSignerNonce(self, metaTx.params.signer);
|
|
@@ -838,6 +865,9 @@ library EngineBlox {
|
|
|
838
865
|
* @param self The SecureOperationState to modify.
|
|
839
866
|
* @param roleHash The hash of the role to remove.
|
|
840
867
|
* @notice Security: Cannot remove protected roles to maintain system integrity.
|
|
868
|
+
* @custom:security PROTECTED-ROLE POLICY: This library enforces the protected-role check for
|
|
869
|
+
* REMOVE_ROLE. RuntimeRBAC does not duplicate this check; defense is in layers. The
|
|
870
|
+
* only component authorized to modify system wallets (protected roles) is SecureOwnable.
|
|
841
871
|
*/
|
|
842
872
|
function removeRole(
|
|
843
873
|
SecureOperationState storage self,
|
|
@@ -868,19 +898,27 @@ library EngineBlox {
|
|
|
868
898
|
self.walletRoles[wallets[i]].remove(roleHash);
|
|
869
899
|
}
|
|
870
900
|
|
|
871
|
-
// Clear the role
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
//
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
901
|
+
// Clear the role's authorizedWallets set so storage is clean if role is recreated with same name
|
|
902
|
+
for (uint256 i = 0; i < walletCount; i++) {
|
|
903
|
+
roleData.authorizedWallets.remove(wallets[i]);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Clear function permissions and functionSelectorsSet (same reason: no stale data on role recreation)
|
|
907
|
+
uint256 selectorCount = roleData.functionSelectorsSet.length();
|
|
908
|
+
bytes32[] memory selectors = new bytes32[](selectorCount);
|
|
909
|
+
for (uint256 i = 0; i < selectorCount; i++) {
|
|
910
|
+
selectors[i] = roleData.functionSelectorsSet.at(i);
|
|
911
|
+
}
|
|
912
|
+
for (uint256 i = 0; i < selectorCount; i++) {
|
|
913
|
+
roleData.functionSelectorsSet.remove(selectors[i]);
|
|
914
|
+
delete roleData.functionPermissions[bytes4(selectors[i])];
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Delete role and remove from supported set (cleanup above ensures no stale RBAC data)
|
|
918
|
+
delete self.roles[roleHash];
|
|
881
919
|
if (!self.supportedRolesSet.remove(roleHash)) {
|
|
882
920
|
revert SharedValidation.ResourceNotFound(roleHash);
|
|
883
|
-
}
|
|
921
|
+
}
|
|
884
922
|
}
|
|
885
923
|
|
|
886
924
|
/**
|
|
@@ -1120,9 +1158,12 @@ library EngineBlox {
|
|
|
1120
1158
|
* @param functionSelector Hash identifier for the function.
|
|
1121
1159
|
* @param operationName The name of the operation type.
|
|
1122
1160
|
* @param supportedActionsBitmap Bitmap of permissions required to execute this function.
|
|
1123
|
-
* @param enforceHandlerRelations When true, handlerForSelectors in permissions must match schema
|
|
1161
|
+
* @param enforceHandlerRelations When true (strict mode), handlerForSelectors in role permissions must match this schema's handlerForSelectors at use time. When false (flexible mode), forward references are allowed.
|
|
1124
1162
|
* @param isProtected Whether the function schema is protected from removal.
|
|
1125
|
-
* @param handlerForSelectors Non-empty array required - execution selectors must contain self-reference, handler selectors must point to execution selectors
|
|
1163
|
+
* @param handlerForSelectors Non-empty array required - execution selectors must contain self-reference, handler selectors must point to execution selectors.
|
|
1164
|
+
* @custom:security OPERATIONAL MODES: We do not require handlerForSelectors[i] to be in supportedFunctionsSet at registration.
|
|
1165
|
+
* - Strict mode (enforceHandlerRelations == true): at use time (_validateHandlerForSelectors) we require role permissions' handlerForSelectors to match this schema's handlerForSelectors; registration order is flexible.
|
|
1166
|
+
* - Flexible mode (enforceHandlerRelations == false): validation is skipped; forward references and unregistered selectors are allowed by design. Callers select the mode per schema.
|
|
1126
1167
|
*/
|
|
1127
1168
|
function registerFunction(
|
|
1128
1169
|
SecureOperationState storage self,
|
|
@@ -1144,12 +1185,10 @@ library EngineBlox {
|
|
|
1144
1185
|
// Derive operation type from operation name
|
|
1145
1186
|
bytes32 derivedOperationType = keccak256(bytes(operationName));
|
|
1146
1187
|
|
|
1147
|
-
// Validate handlerForSelectors: non-empty and all selectors are non-zero
|
|
1148
|
-
//
|
|
1149
|
-
//
|
|
1150
|
-
//
|
|
1151
|
-
// handler selectors must point to valid execution selectors.
|
|
1152
|
-
// - bytes4(0) is never allowed in this array.
|
|
1188
|
+
// Validate handlerForSelectors: non-empty and all selectors are non-zero.
|
|
1189
|
+
// We do NOT require handlerForSelectors[i] to be in supportedFunctionsSet here.
|
|
1190
|
+
// Operational mode is controlled by enforceHandlerRelations: strict mode validates at use time;
|
|
1191
|
+
// flexible mode allows forward references and unregistered selectors by design. See @custom:security OPERATIONAL MODES above.
|
|
1153
1192
|
if (handlerForSelectors.length == 0) {
|
|
1154
1193
|
revert SharedValidation.OperationFailed();
|
|
1155
1194
|
}
|
|
@@ -1665,39 +1704,84 @@ library EngineBlox {
|
|
|
1665
1704
|
}
|
|
1666
1705
|
|
|
1667
1706
|
/**
|
|
1668
|
-
* @dev Generates
|
|
1707
|
+
* @dev Generates the EIP-712 message hash for the meta-transaction.
|
|
1708
|
+
* Uses selective MetaTxRecord (txId, params, payment only) with standard EIP-712 type hashes
|
|
1709
|
+
* so that eth_signTypedData_v4 (and equivalent) can reproduce the same digest when given
|
|
1710
|
+
* matching domain + types:
|
|
1711
|
+
*
|
|
1712
|
+
* - primaryType: MetaTransaction
|
|
1713
|
+
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
|
|
1714
|
+
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
|
|
1715
|
+
*
|
|
1716
|
+
* Integrators MAY:
|
|
1717
|
+
* - use typed-data signing (eth_signTypedData_v4 / signTypedData) with the above domain/types, or
|
|
1718
|
+
* - sign the resulting digest as a raw hash (e.g. account.sign({ hash: contractDigest })).
|
|
1719
|
+
*
|
|
1720
|
+
* In all cases, on-chain verification uses recoverSigner(messageHash, signature) which applies
|
|
1721
|
+
* ecrecover(messageHash, v, r, s) with no personal_sign / EIP-191 prefix.
|
|
1722
|
+
*
|
|
1723
|
+
* The resulting digest is also written into the `message` field of helper-built `MetaTransaction`
|
|
1724
|
+
* structs so integrators can use it directly without recomputing the hash client-side.
|
|
1669
1725
|
* @param metaTx The meta-transaction to generate the hash for
|
|
1670
|
-
* @return The
|
|
1726
|
+
* @return The EIP-712 digest (no prefix; use standard recovery)
|
|
1671
1727
|
*/
|
|
1672
1728
|
function generateMessageHash(MetaTransaction memory metaTx) private view returns (bytes32) {
|
|
1673
|
-
bytes32 domainSeparator = keccak256(
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1729
|
+
bytes32 domainSeparator = keccak256(
|
|
1730
|
+
abi.encode(
|
|
1731
|
+
DOMAIN_SEPARATOR_TYPE_HASH,
|
|
1732
|
+
PROTOCOL_NAME_HASH,
|
|
1733
|
+
keccak256(bytes(VERSION)),
|
|
1734
|
+
block.chainid,
|
|
1735
|
+
address(this)
|
|
1736
|
+
)
|
|
1737
|
+
);
|
|
1738
|
+
|
|
1739
|
+
TxParams memory tp = metaTx.txRecord.params;
|
|
1740
|
+
bytes32 txParamsStructHash = keccak256(abi.encode(
|
|
1741
|
+
TX_PARAMS_TYPE_HASH,
|
|
1742
|
+
tp.requester,
|
|
1743
|
+
tp.target,
|
|
1744
|
+
tp.value,
|
|
1745
|
+
tp.gasLimit,
|
|
1746
|
+
tp.operationType,
|
|
1747
|
+
tp.executionSelector,
|
|
1748
|
+
keccak256(tp.executionParams)
|
|
1749
|
+
));
|
|
1750
|
+
|
|
1751
|
+
PaymentDetails memory payment = metaTx.txRecord.payment;
|
|
1752
|
+
bytes32 paymentStructHash = keccak256(abi.encode(
|
|
1753
|
+
PAYMENT_DETAILS_TYPE_HASH,
|
|
1754
|
+
payment.recipient,
|
|
1755
|
+
payment.nativeTokenAmount,
|
|
1756
|
+
payment.erc20TokenAddress,
|
|
1757
|
+
payment.erc20TokenAmount
|
|
1758
|
+
));
|
|
1759
|
+
|
|
1760
|
+
bytes32 metaTxRecordStructHash = keccak256(abi.encode(
|
|
1761
|
+
META_TX_RECORD_TYPE_HASH,
|
|
1762
|
+
metaTx.txRecord.txId,
|
|
1763
|
+
txParamsStructHash,
|
|
1764
|
+
paymentStructHash
|
|
1765
|
+
));
|
|
1766
|
+
|
|
1767
|
+
MetaTxParams memory mp = metaTx.params;
|
|
1768
|
+
bytes32 metaTxParamsStructHash = keccak256(abi.encode(
|
|
1769
|
+
META_TX_PARAMS_TYPE_HASH,
|
|
1770
|
+
mp.chainId,
|
|
1771
|
+
mp.nonce,
|
|
1772
|
+
mp.handlerContract,
|
|
1773
|
+
mp.handlerSelector,
|
|
1774
|
+
uint8(mp.action),
|
|
1775
|
+
mp.deadline,
|
|
1776
|
+
mp.maxGasPrice,
|
|
1777
|
+
mp.signer
|
|
1679
1778
|
));
|
|
1680
1779
|
|
|
1681
1780
|
bytes32 structHash = keccak256(abi.encode(
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
metaTx.txRecord.params.target,
|
|
1687
|
-
metaTx.txRecord.params.value,
|
|
1688
|
-
metaTx.txRecord.params.gasLimit,
|
|
1689
|
-
metaTx.txRecord.params.operationType,
|
|
1690
|
-
metaTx.txRecord.params.executionSelector,
|
|
1691
|
-
keccak256(metaTx.txRecord.params.executionParams)
|
|
1692
|
-
)),
|
|
1693
|
-
metaTx.params.chainId,
|
|
1694
|
-
metaTx.params.nonce,
|
|
1695
|
-
metaTx.params.handlerContract,
|
|
1696
|
-
metaTx.params.handlerSelector,
|
|
1697
|
-
uint8(metaTx.params.action),
|
|
1698
|
-
metaTx.params.deadline,
|
|
1699
|
-
metaTx.params.maxGasPrice,
|
|
1700
|
-
metaTx.params.signer
|
|
1781
|
+
META_TX_TYPE_HASH,
|
|
1782
|
+
metaTxRecordStructHash,
|
|
1783
|
+
metaTxParamsStructHash,
|
|
1784
|
+
keccak256(metaTx.data)
|
|
1701
1785
|
));
|
|
1702
1786
|
|
|
1703
1787
|
return keccak256(abi.encodePacked(
|
|
@@ -1708,10 +1792,24 @@ library EngineBlox {
|
|
|
1708
1792
|
}
|
|
1709
1793
|
|
|
1710
1794
|
/**
|
|
1711
|
-
* @dev Recovers the signer
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
1795
|
+
* @dev Recovers the signer from the EIP-712 digest and signature. Uses standard EIP-712 recovery (no message prefix).
|
|
1796
|
+
*
|
|
1797
|
+
* Integrators have two equivalent options:
|
|
1798
|
+
* - Use typed-data signing (eth_signTypedData_v4 / signTypedData) with:
|
|
1799
|
+
* - primaryType: MetaTransaction
|
|
1800
|
+
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
|
|
1801
|
+
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
|
|
1802
|
+
* In this case the wallet computes the same digest as generateMessageHash and signs it.
|
|
1803
|
+
* - Sign the digest returned by generateMessageHash as a raw hash—e.g.
|
|
1804
|
+
* account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—with no
|
|
1805
|
+
* EIP-191/personal prefix.
|
|
1806
|
+
*
|
|
1807
|
+
* In all cases, this function applies ecrecover(messageHash, v, r, s) over the raw EIP-712 digest.
|
|
1808
|
+
* personal_sign / EIP-191-prefixed signatures remain incompatible.
|
|
1809
|
+
*
|
|
1810
|
+
* @param messageHash The EIP-712 digest (keccak256("\x19\x01" || domainSeparator || structHash))
|
|
1811
|
+
* @param signature The signature (r, s, v)
|
|
1812
|
+
* @return The address of the signer
|
|
1715
1813
|
*/
|
|
1716
1814
|
function recoverSigner(bytes32 messageHash, bytes memory signature) public pure returns (address) {
|
|
1717
1815
|
SharedValidation.validateSignatureLength(signature);
|
|
@@ -1740,7 +1838,7 @@ library EngineBlox {
|
|
|
1740
1838
|
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}
|
|
1741
1839
|
SharedValidation.validateSignatureParams(s, v);
|
|
1742
1840
|
|
|
1743
|
-
address signer = ecrecover(messageHash
|
|
1841
|
+
address signer = ecrecover(messageHash, v, r, s);
|
|
1744
1842
|
SharedValidation.validateRecoveredSigner(signer);
|
|
1745
1843
|
|
|
1746
1844
|
return signer;
|
|
@@ -2109,14 +2207,72 @@ library EngineBlox {
|
|
|
2109
2207
|
}
|
|
2110
2208
|
|
|
2111
2209
|
/**
|
|
2112
|
-
* @dev Validates that
|
|
2113
|
-
*
|
|
2114
|
-
*
|
|
2115
|
-
* @param
|
|
2116
|
-
* @param
|
|
2117
|
-
* @param
|
|
2118
|
-
* @notice
|
|
2119
|
-
|
|
2210
|
+
* @dev Validates that the meta-transaction txRecord matches the stored record for the given txId.
|
|
2211
|
+
* Ensures the signer's intent (as reflected in the stored tx from the request phase) is what
|
|
2212
|
+
* is approved or cancelled; override by meta-tx payload is not allowed for approve/cancel flows.
|
|
2213
|
+
* @param self The SecureOperationState containing the stored tx record.
|
|
2214
|
+
* @param txId The transaction ID.
|
|
2215
|
+
* @param metaTxRecord The TxRecord from the meta-transaction calldata.
|
|
2216
|
+
* @notice Reverts with MetaTxRecordMismatchStoredTx if any execution-affecting or permission-affecting field differs.
|
|
2217
|
+
*/
|
|
2218
|
+
function _validateMetaTxMatchRecord(
|
|
2219
|
+
SecureOperationState storage self,
|
|
2220
|
+
uint256 txId,
|
|
2221
|
+
TxRecord memory metaTxRecord
|
|
2222
|
+
) internal view {
|
|
2223
|
+
TxRecord storage stored = self.txRecords[txId];
|
|
2224
|
+
TxParams storage sp = stored.params;
|
|
2225
|
+
TxParams memory mp = metaTxRecord.params;
|
|
2226
|
+
if (
|
|
2227
|
+
sp.executionSelector != mp.executionSelector ||
|
|
2228
|
+
sp.target != mp.target ||
|
|
2229
|
+
sp.value != mp.value ||
|
|
2230
|
+
sp.requester != mp.requester ||
|
|
2231
|
+
sp.gasLimit != mp.gasLimit ||
|
|
2232
|
+
sp.operationType != mp.operationType ||
|
|
2233
|
+
keccak256(sp.executionParams) != keccak256(mp.executionParams) ||
|
|
2234
|
+
stored.releaseTime != metaTxRecord.releaseTime
|
|
2235
|
+
) {
|
|
2236
|
+
revert SharedValidation.MetaTxRecordMismatchStoredTx(txId);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
/**
|
|
2241
|
+
* @dev Validates that the meta-transaction payment matches the stored record for the given txId.
|
|
2242
|
+
* Ensures the signed payment (recipient, amounts, token) equals what will be executed.
|
|
2243
|
+
* @param self The SecureOperationState containing the stored tx record.
|
|
2244
|
+
* @param txId The transaction ID.
|
|
2245
|
+
* @param metaTxRecord The TxRecord from the meta-transaction calldata.
|
|
2246
|
+
* @notice Reverts with MetaTxPaymentMismatchStoredTx if any payment field differs from stored.
|
|
2247
|
+
*/
|
|
2248
|
+
function _validateMetaTxPaymentMatchRecord(
|
|
2249
|
+
SecureOperationState storage self,
|
|
2250
|
+
uint256 txId,
|
|
2251
|
+
TxRecord memory metaTxRecord
|
|
2252
|
+
) internal view {
|
|
2253
|
+
PaymentDetails storage storedPayment = self.txRecords[txId].payment;
|
|
2254
|
+
PaymentDetails memory metaPayment = metaTxRecord.payment;
|
|
2255
|
+
if (
|
|
2256
|
+
storedPayment.recipient != metaPayment.recipient ||
|
|
2257
|
+
storedPayment.nativeTokenAmount != metaPayment.nativeTokenAmount ||
|
|
2258
|
+
storedPayment.erc20TokenAddress != metaPayment.erc20TokenAddress ||
|
|
2259
|
+
storedPayment.erc20TokenAmount != metaPayment.erc20TokenAmount
|
|
2260
|
+
) {
|
|
2261
|
+
revert SharedValidation.MetaTxPaymentMismatchStoredTx(txId);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
/**
|
|
2266
|
+
* @dev Validates that a wallet has permission for both execution selector and handler selector for a given action.
|
|
2267
|
+
* @param self The SecureOperationState to check.
|
|
2268
|
+
* @param wallet The wallet address to check permissions for.
|
|
2269
|
+
* @param executionSelector The execution function selector (underlying operation).
|
|
2270
|
+
* @param handlerSelector The handler/calling function selector.
|
|
2271
|
+
* @param action The action to validate permissions for.
|
|
2272
|
+
* @notice This function consolidates the repeated dual permission check pattern to reduce contract size.
|
|
2273
|
+
* @notice Reverts with NoPermission if either permission check fails.
|
|
2274
|
+
* @notice Strict mode enforces that the handler's *schema-level* handlerForSelectors flow allows the execution selector;
|
|
2275
|
+
* it does not bind this relation to a specific role's FunctionPermission, which may further narrow pairings.
|
|
2120
2276
|
*/
|
|
2121
2277
|
function _validateExecutionAndHandlerPermissions(
|
|
2122
2278
|
SecureOperationState storage self,
|
|
@@ -2137,27 +2293,37 @@ library EngineBlox {
|
|
|
2137
2293
|
if (!hasActionPermission(self, wallet, handlerSelector, action)) {
|
|
2138
2294
|
revert SharedValidation.NoPermission(wallet);
|
|
2139
2295
|
}
|
|
2296
|
+
|
|
2297
|
+
// In strict mode, enforce that the executionSelector is part of the handlerSelector's schema-level flow.
|
|
2298
|
+
// Handler schemas declare which execution selectors they are allowed to trigger globally; role permissions
|
|
2299
|
+
// can still narrow which selectors a given wallet may use via FunctionPermission.handlerForSelectors.
|
|
2300
|
+
FunctionSchema storage handlerSchema = self.functions[handlerSelector];
|
|
2301
|
+
if (handlerSchema.enforceHandlerRelations) {
|
|
2302
|
+
if (!_schemaHasHandlerSelector(handlerSchema, executionSelector)) {
|
|
2303
|
+
revert SharedValidation.HandlerForSelectorMismatch(
|
|
2304
|
+
executionSelector,
|
|
2305
|
+
handlerSelector
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2140
2309
|
}
|
|
2141
2310
|
|
|
2142
2311
|
/**
|
|
2143
|
-
* @dev Validates that all handlerForSelectors are present in the schema's handlerForSelectors array
|
|
2312
|
+
* @dev Validates that all handlerForSelectors are present in the schema's handlerForSelectors array.
|
|
2313
|
+
* When schema.enforceHandlerRelations is false (flexible mode), validation is skipped and this function returns immediately.
|
|
2314
|
+
* When true (strict mode), every permission handlerForSelector must appear in the schema's handlerForSelectors.
|
|
2144
2315
|
* @param self The SecureOperationState to validate against
|
|
2145
2316
|
* @param functionSelector The function selector for which the permission is defined
|
|
2146
2317
|
* @param handlerForSelectors The handlerForSelectors array from the permission to validate
|
|
2147
|
-
* @notice Reverts with HandlerForSelectorMismatch if any handlerForSelector is not found in the schema's array
|
|
2148
|
-
* @notice Special case: Execution function permissions should include functionSelector in handlerForSelectors (self-reference)
|
|
2318
|
+
* @notice Reverts with HandlerForSelectorMismatch if any handlerForSelector is not found in the schema's array (strict mode only).
|
|
2319
|
+
* @notice Special case: Execution function permissions should include functionSelector in handlerForSelectors (self-reference).
|
|
2149
2320
|
*/
|
|
2150
2321
|
function _validateHandlerForSelectors(
|
|
2151
2322
|
SecureOperationState storage self,
|
|
2152
2323
|
bytes4 functionSelector,
|
|
2153
2324
|
bytes4[] memory handlerForSelectors
|
|
2154
2325
|
) internal view {
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
// Ensure the function schema exists
|
|
2158
|
-
if (!self.supportedFunctionsSet.contains(functionSelectorHash)) {
|
|
2159
|
-
revert SharedValidation.ResourceNotFound(functionSelectorHash);
|
|
2160
|
-
}
|
|
2326
|
+
_validateFunctionSchemaExists(self, functionSelector);
|
|
2161
2327
|
|
|
2162
2328
|
FunctionSchema storage schema = self.functions[functionSelector];
|
|
2163
2329
|
|
|
@@ -2170,14 +2336,7 @@ library EngineBlox {
|
|
|
2170
2336
|
for (uint256 j = 0; j < handlerForSelectors.length; j++) {
|
|
2171
2337
|
bytes4 handlerForSelector = handlerForSelectors[j];
|
|
2172
2338
|
|
|
2173
|
-
|
|
2174
|
-
for (uint256 i = 0; i < schema.handlerForSelectors.length; i++) {
|
|
2175
|
-
if (schema.handlerForSelectors[i] == handlerForSelector) {
|
|
2176
|
-
found = true;
|
|
2177
|
-
break;
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
if (!found) {
|
|
2339
|
+
if (!_schemaHasHandlerSelector(schema, handlerForSelector)) {
|
|
2181
2340
|
revert SharedValidation.HandlerForSelectorMismatch(
|
|
2182
2341
|
bytes4(0), // Cannot return array, use 0 as placeholder
|
|
2183
2342
|
handlerForSelector
|
|
@@ -2186,6 +2345,24 @@ library EngineBlox {
|
|
|
2186
2345
|
}
|
|
2187
2346
|
}
|
|
2188
2347
|
|
|
2348
|
+
/**
|
|
2349
|
+
* @dev Checks whether a given handler selector is present in a function schema's handlerForSelectors array.
|
|
2350
|
+
* @param schema The function schema to inspect.
|
|
2351
|
+
* @param handlerSelector The handler selector to search for.
|
|
2352
|
+
* @return True if the handler selector is present, false otherwise.
|
|
2353
|
+
*/
|
|
2354
|
+
function _schemaHasHandlerSelector(
|
|
2355
|
+
FunctionSchema storage schema,
|
|
2356
|
+
bytes4 handlerSelector
|
|
2357
|
+
) internal view returns (bool) {
|
|
2358
|
+
for (uint256 i = 0; i < schema.handlerForSelectors.length; i++) {
|
|
2359
|
+
if (schema.handlerForSelectors[i] == handlerSelector) {
|
|
2360
|
+
return true;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
return false;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2189
2366
|
/**
|
|
2190
2367
|
* @dev Validates meta-transaction permissions for a function permission
|
|
2191
2368
|
* @param self The secure operation state
|
|
@@ -73,6 +73,8 @@ library SharedValidation {
|
|
|
73
73
|
error InvalidVValue(uint8 v);
|
|
74
74
|
error ECDSAInvalidSignature(address recoveredSigner);
|
|
75
75
|
error GasPriceExceedsMax(uint256 currentGasPrice, uint256 maxGasPrice);
|
|
76
|
+
error MetaTxRecordMismatchStoredTx(uint256 txId);
|
|
77
|
+
error MetaTxPaymentMismatchStoredTx(uint256 txId);
|
|
76
78
|
|
|
77
79
|
// Consolidated resource errors
|
|
78
80
|
error ResourceNotFound(bytes32 resourceId);
|
|
@@ -405,12 +407,12 @@ library SharedValidation {
|
|
|
405
407
|
// ============ UTILITY FUNCTIONS ============
|
|
406
408
|
|
|
407
409
|
/**
|
|
408
|
-
* @dev Validates that the first value is
|
|
409
|
-
* @param from The first value (
|
|
410
|
-
* @param to The second value (
|
|
410
|
+
* @dev Validates that the first value is not greater than the second (allows inclusive range: from <= to)
|
|
411
|
+
* @param from The first value (must be <= 'to' for a valid range)
|
|
412
|
+
* @param to The second value (must be >= 'from')
|
|
411
413
|
*/
|
|
412
414
|
function validateLessThan(uint256 from, uint256 to) internal pure {
|
|
413
|
-
if (from
|
|
415
|
+
if (from > to) revert InvalidRange(from, to);
|
|
414
416
|
}
|
|
415
417
|
|
|
416
418
|
/**
|