@bloxchain/contracts 1.0.0-alpha.16 → 1.0.0-alpha.17
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 +22 -0
- 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 +220 -76
- 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
|
{
|
|
@@ -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
|
|
@@ -53,7 +52,6 @@ library EngineBlox {
|
|
|
53
52
|
/// @dev Maximum total number of functions allowed in the system (prevents gas exhaustion in function operations)
|
|
54
53
|
uint256 public constant MAX_FUNCTIONS = 2000;
|
|
55
54
|
|
|
56
|
-
using MessageHashUtils for bytes32;
|
|
57
55
|
using SharedValidation for *;
|
|
58
56
|
using EnumerableSet for EnumerableSet.UintSet;
|
|
59
57
|
using EnumerableSet for EnumerableSet.Bytes32Set;
|
|
@@ -152,9 +150,11 @@ library EngineBlox {
|
|
|
152
150
|
bytes32 operationType;
|
|
153
151
|
string operationName;
|
|
154
152
|
uint16 supportedActionsBitmap; // Bitmap for TxAction enum (9 bits max)
|
|
155
|
-
|
|
153
|
+
/// @dev When true (strict mode): handlerForSelectors in role permissions must match this schema's handlerForSelectors at use time.
|
|
154
|
+
/// When false (flexible mode): no such check; forward references and unregistered selectors in handlerForSelectors are allowed at registration.
|
|
155
|
+
bool enforceHandlerRelations;
|
|
156
156
|
bool isProtected;
|
|
157
|
-
bytes4[] handlerForSelectors;
|
|
157
|
+
bytes4[] handlerForSelectors;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// ============ DEFINITION STRUCTS ============
|
|
@@ -205,8 +205,12 @@ library EngineBlox {
|
|
|
205
205
|
bytes4 public constant NATIVE_TRANSFER_SELECTOR = bytes4(keccak256("__bloxchain_native_transfer__()"));
|
|
206
206
|
bytes32 public constant NATIVE_TRANSFER_OPERATION = keccak256("NATIVE_TRANSFER");
|
|
207
207
|
|
|
208
|
-
// EIP-712 Type Hashes
|
|
209
|
-
bytes32 private constant
|
|
208
|
+
// EIP-712 Type Hashes (selective meta-tx payload: MetaTxRecord = txId + params + payment only)
|
|
209
|
+
bytes32 private constant META_TX_TYPE_HASH = keccak256("MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
|
|
210
|
+
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256("MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
|
|
211
|
+
bytes32 private constant TX_PARAMS_TYPE_HASH = keccak256("TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)");
|
|
212
|
+
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
|
+
bytes32 private constant PAYMENT_DETAILS_TYPE_HASH = keccak256("PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
|
|
210
214
|
bytes32 private constant DOMAIN_SEPARATOR_TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
|
211
215
|
|
|
212
216
|
|
|
@@ -483,6 +487,8 @@ library EngineBlox {
|
|
|
483
487
|
// Validate both execution and handler selector permissions
|
|
484
488
|
_validateExecutionAndHandlerPermissions(self, msg.sender, metaTx.txRecord.params.executionSelector, metaTx.params.handlerSelector, TxAction.EXECUTE_META_CANCEL);
|
|
485
489
|
_validateTxStatus(self, txId, TxStatus.PENDING);
|
|
490
|
+
_validateMetaTxMatchRecord(self, txId, metaTx.txRecord);
|
|
491
|
+
_validateMetaTxPaymentMatchRecord(self, txId, metaTx.txRecord);
|
|
486
492
|
if (!verifySignature(self, metaTx)) revert SharedValidation.InvalidSignature(metaTx.signature);
|
|
487
493
|
|
|
488
494
|
incrementSignerNonce(self, metaTx.params.signer);
|
|
@@ -513,10 +519,16 @@ library EngineBlox {
|
|
|
513
519
|
* @return The updated TxRecord.
|
|
514
520
|
* @notice This function skips permission validation and should only be called from functions
|
|
515
521
|
* that have already validated permissions.
|
|
522
|
+
* @custom:security TIMELOCK: The releaseTime (timelock) is intentionally NOT enforced on this path.
|
|
523
|
+
* This is by design: the meta-tx workflow allows authorized signers to approve execution
|
|
524
|
+
* without waiting for releaseTime, providing a hybrid synergy between timelock workflows
|
|
525
|
+
* (direct path enforces releaseTime) and meta-tx workflows (delegated, time-flexible approval).
|
|
516
526
|
*/
|
|
517
527
|
function _txApprovalWithMetaTx(SecureOperationState storage self, MetaTransaction memory metaTx) private returns (TxRecord memory) {
|
|
518
528
|
uint256 txId = metaTx.txRecord.txId;
|
|
519
529
|
_validateTxStatus(self, txId, TxStatus.PENDING);
|
|
530
|
+
_validateMetaTxMatchRecord(self, txId, metaTx.txRecord);
|
|
531
|
+
_validateMetaTxPaymentMatchRecord(self, txId, metaTx.txRecord);
|
|
520
532
|
if (!verifySignature(self, metaTx)) revert SharedValidation.InvalidSignature(metaTx.signature);
|
|
521
533
|
|
|
522
534
|
incrementSignerNonce(self, metaTx.params.signer);
|
|
@@ -838,6 +850,9 @@ library EngineBlox {
|
|
|
838
850
|
* @param self The SecureOperationState to modify.
|
|
839
851
|
* @param roleHash The hash of the role to remove.
|
|
840
852
|
* @notice Security: Cannot remove protected roles to maintain system integrity.
|
|
853
|
+
* @custom:security PROTECTED-ROLE POLICY: This library enforces the protected-role check for
|
|
854
|
+
* REMOVE_ROLE. RuntimeRBAC does not duplicate this check; defense is in layers. The
|
|
855
|
+
* only component authorized to modify system wallets (protected roles) is SecureOwnable.
|
|
841
856
|
*/
|
|
842
857
|
function removeRole(
|
|
843
858
|
SecureOperationState storage self,
|
|
@@ -868,19 +883,27 @@ library EngineBlox {
|
|
|
868
883
|
self.walletRoles[wallets[i]].remove(roleHash);
|
|
869
884
|
}
|
|
870
885
|
|
|
871
|
-
// Clear the role
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
//
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
886
|
+
// Clear the role's authorizedWallets set so storage is clean if role is recreated with same name
|
|
887
|
+
for (uint256 i = 0; i < walletCount; i++) {
|
|
888
|
+
roleData.authorizedWallets.remove(wallets[i]);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Clear function permissions and functionSelectorsSet (same reason: no stale data on role recreation)
|
|
892
|
+
uint256 selectorCount = roleData.functionSelectorsSet.length();
|
|
893
|
+
bytes32[] memory selectors = new bytes32[](selectorCount);
|
|
894
|
+
for (uint256 i = 0; i < selectorCount; i++) {
|
|
895
|
+
selectors[i] = roleData.functionSelectorsSet.at(i);
|
|
896
|
+
}
|
|
897
|
+
for (uint256 i = 0; i < selectorCount; i++) {
|
|
898
|
+
roleData.functionSelectorsSet.remove(selectors[i]);
|
|
899
|
+
delete roleData.functionPermissions[bytes4(selectors[i])];
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Delete role and remove from supported set (cleanup above ensures no stale RBAC data)
|
|
903
|
+
delete self.roles[roleHash];
|
|
881
904
|
if (!self.supportedRolesSet.remove(roleHash)) {
|
|
882
905
|
revert SharedValidation.ResourceNotFound(roleHash);
|
|
883
|
-
}
|
|
906
|
+
}
|
|
884
907
|
}
|
|
885
908
|
|
|
886
909
|
/**
|
|
@@ -1120,9 +1143,12 @@ library EngineBlox {
|
|
|
1120
1143
|
* @param functionSelector Hash identifier for the function.
|
|
1121
1144
|
* @param operationName The name of the operation type.
|
|
1122
1145
|
* @param supportedActionsBitmap Bitmap of permissions required to execute this function.
|
|
1123
|
-
* @param enforceHandlerRelations When true, handlerForSelectors in permissions must match schema
|
|
1146
|
+
* @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
1147
|
* @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
|
|
1148
|
+
* @param handlerForSelectors Non-empty array required - execution selectors must contain self-reference, handler selectors must point to execution selectors.
|
|
1149
|
+
* @custom:security OPERATIONAL MODES: We do not require handlerForSelectors[i] to be in supportedFunctionsSet at registration.
|
|
1150
|
+
* - Strict mode (enforceHandlerRelations == true): at use time (_validateHandlerForSelectors) we require role permissions' handlerForSelectors to match this schema's handlerForSelectors; registration order is flexible.
|
|
1151
|
+
* - Flexible mode (enforceHandlerRelations == false): validation is skipped; forward references and unregistered selectors are allowed by design. Callers select the mode per schema.
|
|
1126
1152
|
*/
|
|
1127
1153
|
function registerFunction(
|
|
1128
1154
|
SecureOperationState storage self,
|
|
@@ -1144,12 +1170,10 @@ library EngineBlox {
|
|
|
1144
1170
|
// Derive operation type from operation name
|
|
1145
1171
|
bytes32 derivedOperationType = keccak256(bytes(operationName));
|
|
1146
1172
|
|
|
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.
|
|
1173
|
+
// Validate handlerForSelectors: non-empty and all selectors are non-zero.
|
|
1174
|
+
// We do NOT require handlerForSelectors[i] to be in supportedFunctionsSet here.
|
|
1175
|
+
// Operational mode is controlled by enforceHandlerRelations: strict mode validates at use time;
|
|
1176
|
+
// flexible mode allows forward references and unregistered selectors by design. See @custom:security OPERATIONAL MODES above.
|
|
1153
1177
|
if (handlerForSelectors.length == 0) {
|
|
1154
1178
|
revert SharedValidation.OperationFailed();
|
|
1155
1179
|
}
|
|
@@ -1665,9 +1689,19 @@ library EngineBlox {
|
|
|
1665
1689
|
}
|
|
1666
1690
|
|
|
1667
1691
|
/**
|
|
1668
|
-
* @dev Generates
|
|
1692
|
+
* @dev Generates the EIP-712 message hash for the meta-transaction.
|
|
1693
|
+
* Uses selective MetaTxRecord (txId, params, payment only).
|
|
1694
|
+
* Integrators must sign this digest as a raw hash with no EIP-191 or personal_sign prefix—
|
|
1695
|
+
* e.g. account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—so that
|
|
1696
|
+
* signatures match the raw ecrecover(messageHash, v, r, s) verification in recoverSigner.
|
|
1697
|
+
* Do NOT use personal_sign or generic eth_signTypedData_v4; the contract uses
|
|
1698
|
+
* abi.encodePacked for the version string and a custom META_TX_TYPE_HASH, so those produce
|
|
1699
|
+
* incompatible hashes.
|
|
1700
|
+
* The resulting digest is also written into the `message` field of the helper-built
|
|
1701
|
+
* `MetaTransaction` structs (see `createMetaTransactionForSigning`) so integrators can use
|
|
1702
|
+
* it directly without recomputing the hash client-side.
|
|
1669
1703
|
* @param metaTx The meta-transaction to generate the hash for
|
|
1670
|
-
* @return The
|
|
1704
|
+
* @return The EIP-712 digest (no prefix; use standard recovery)
|
|
1671
1705
|
*/
|
|
1672
1706
|
function generateMessageHash(MetaTransaction memory metaTx) private view returns (bytes32) {
|
|
1673
1707
|
bytes32 domainSeparator = keccak256(abi.encode(
|
|
@@ -1678,26 +1712,52 @@ library EngineBlox {
|
|
|
1678
1712
|
address(this)
|
|
1679
1713
|
));
|
|
1680
1714
|
|
|
1715
|
+
TxParams memory tp = metaTx.txRecord.params;
|
|
1716
|
+
bytes32 txParamsStructHash = keccak256(abi.encode(
|
|
1717
|
+
TX_PARAMS_TYPE_HASH,
|
|
1718
|
+
tp.requester,
|
|
1719
|
+
tp.target,
|
|
1720
|
+
tp.value,
|
|
1721
|
+
tp.gasLimit,
|
|
1722
|
+
tp.operationType,
|
|
1723
|
+
tp.executionSelector,
|
|
1724
|
+
keccak256(tp.executionParams)
|
|
1725
|
+
));
|
|
1726
|
+
|
|
1727
|
+
PaymentDetails memory payment = metaTx.txRecord.payment;
|
|
1728
|
+
bytes32 paymentStructHash = keccak256(abi.encode(
|
|
1729
|
+
PAYMENT_DETAILS_TYPE_HASH,
|
|
1730
|
+
payment.recipient,
|
|
1731
|
+
payment.nativeTokenAmount,
|
|
1732
|
+
payment.erc20TokenAddress,
|
|
1733
|
+
payment.erc20TokenAmount
|
|
1734
|
+
));
|
|
1735
|
+
|
|
1736
|
+
bytes32 metaTxRecordStructHash = keccak256(abi.encode(
|
|
1737
|
+
META_TX_RECORD_TYPE_HASH,
|
|
1738
|
+
metaTx.txRecord.txId,
|
|
1739
|
+
txParamsStructHash,
|
|
1740
|
+
paymentStructHash
|
|
1741
|
+
));
|
|
1742
|
+
|
|
1743
|
+
MetaTxParams memory mp = metaTx.params;
|
|
1744
|
+
bytes32 metaTxParamsStructHash = keccak256(abi.encode(
|
|
1745
|
+
META_TX_PARAMS_TYPE_HASH,
|
|
1746
|
+
mp.chainId,
|
|
1747
|
+
mp.nonce,
|
|
1748
|
+
mp.handlerContract,
|
|
1749
|
+
mp.handlerSelector,
|
|
1750
|
+
uint8(mp.action),
|
|
1751
|
+
mp.deadline,
|
|
1752
|
+
mp.maxGasPrice,
|
|
1753
|
+
mp.signer
|
|
1754
|
+
));
|
|
1755
|
+
|
|
1681
1756
|
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
|
|
1757
|
+
META_TX_TYPE_HASH,
|
|
1758
|
+
metaTxRecordStructHash,
|
|
1759
|
+
metaTxParamsStructHash,
|
|
1760
|
+
keccak256(metaTx.data)
|
|
1701
1761
|
));
|
|
1702
1762
|
|
|
1703
1763
|
return keccak256(abi.encodePacked(
|
|
@@ -1708,10 +1768,15 @@ library EngineBlox {
|
|
|
1708
1768
|
}
|
|
1709
1769
|
|
|
1710
1770
|
/**
|
|
1711
|
-
* @dev Recovers the signer
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
1771
|
+
* @dev Recovers the signer from the EIP-712 digest and signature. Uses standard EIP-712 recovery (no message prefix).
|
|
1772
|
+
* Integrators must sign the digest returned by generateMessageHash as a raw hash—e.g.
|
|
1773
|
+
* account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—with no
|
|
1774
|
+
* EIP-191/personal prefix, so signatures match this ecrecover(messageHash, v, r, s) verification.
|
|
1775
|
+
* Do NOT use personal_sign or generic eth_signTypedData_v4; the contract uses abi.encodePacked
|
|
1776
|
+
* for the domain version and a custom META_TX_TYPE_HASH, so those produce incompatible hashes.
|
|
1777
|
+
* @param messageHash The EIP-712 digest (keccak256("\x19\x01" || domainSeparator || structHash))
|
|
1778
|
+
* @param signature The signature (r, s, v)
|
|
1779
|
+
* @return The address of the signer
|
|
1715
1780
|
*/
|
|
1716
1781
|
function recoverSigner(bytes32 messageHash, bytes memory signature) public pure returns (address) {
|
|
1717
1782
|
SharedValidation.validateSignatureLength(signature);
|
|
@@ -1740,7 +1805,7 @@ library EngineBlox {
|
|
|
1740
1805
|
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}
|
|
1741
1806
|
SharedValidation.validateSignatureParams(s, v);
|
|
1742
1807
|
|
|
1743
|
-
address signer = ecrecover(messageHash
|
|
1808
|
+
address signer = ecrecover(messageHash, v, r, s);
|
|
1744
1809
|
SharedValidation.validateRecoveredSigner(signer);
|
|
1745
1810
|
|
|
1746
1811
|
return signer;
|
|
@@ -2109,14 +2174,72 @@ library EngineBlox {
|
|
|
2109
2174
|
}
|
|
2110
2175
|
|
|
2111
2176
|
/**
|
|
2112
|
-
* @dev Validates that
|
|
2113
|
-
*
|
|
2114
|
-
*
|
|
2115
|
-
* @param
|
|
2116
|
-
* @param
|
|
2117
|
-
* @param
|
|
2118
|
-
* @notice
|
|
2119
|
-
|
|
2177
|
+
* @dev Validates that the meta-transaction txRecord matches the stored record for the given txId.
|
|
2178
|
+
* Ensures the signer's intent (as reflected in the stored tx from the request phase) is what
|
|
2179
|
+
* is approved or cancelled; override by meta-tx payload is not allowed for approve/cancel flows.
|
|
2180
|
+
* @param self The SecureOperationState containing the stored tx record.
|
|
2181
|
+
* @param txId The transaction ID.
|
|
2182
|
+
* @param metaTxRecord The TxRecord from the meta-transaction calldata.
|
|
2183
|
+
* @notice Reverts with MetaTxRecordMismatchStoredTx if any execution-affecting or permission-affecting field differs.
|
|
2184
|
+
*/
|
|
2185
|
+
function _validateMetaTxMatchRecord(
|
|
2186
|
+
SecureOperationState storage self,
|
|
2187
|
+
uint256 txId,
|
|
2188
|
+
TxRecord memory metaTxRecord
|
|
2189
|
+
) internal view {
|
|
2190
|
+
TxRecord storage stored = self.txRecords[txId];
|
|
2191
|
+
TxParams storage sp = stored.params;
|
|
2192
|
+
TxParams memory mp = metaTxRecord.params;
|
|
2193
|
+
if (
|
|
2194
|
+
sp.executionSelector != mp.executionSelector ||
|
|
2195
|
+
sp.target != mp.target ||
|
|
2196
|
+
sp.value != mp.value ||
|
|
2197
|
+
sp.requester != mp.requester ||
|
|
2198
|
+
sp.gasLimit != mp.gasLimit ||
|
|
2199
|
+
sp.operationType != mp.operationType ||
|
|
2200
|
+
keccak256(sp.executionParams) != keccak256(mp.executionParams) ||
|
|
2201
|
+
stored.releaseTime != metaTxRecord.releaseTime
|
|
2202
|
+
) {
|
|
2203
|
+
revert SharedValidation.MetaTxRecordMismatchStoredTx(txId);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
/**
|
|
2208
|
+
* @dev Validates that the meta-transaction payment matches the stored record for the given txId.
|
|
2209
|
+
* Ensures the signed payment (recipient, amounts, token) equals what will be executed.
|
|
2210
|
+
* @param self The SecureOperationState containing the stored tx record.
|
|
2211
|
+
* @param txId The transaction ID.
|
|
2212
|
+
* @param metaTxRecord The TxRecord from the meta-transaction calldata.
|
|
2213
|
+
* @notice Reverts with MetaTxPaymentMismatchStoredTx if any payment field differs from stored.
|
|
2214
|
+
*/
|
|
2215
|
+
function _validateMetaTxPaymentMatchRecord(
|
|
2216
|
+
SecureOperationState storage self,
|
|
2217
|
+
uint256 txId,
|
|
2218
|
+
TxRecord memory metaTxRecord
|
|
2219
|
+
) internal view {
|
|
2220
|
+
PaymentDetails storage storedPayment = self.txRecords[txId].payment;
|
|
2221
|
+
PaymentDetails memory metaPayment = metaTxRecord.payment;
|
|
2222
|
+
if (
|
|
2223
|
+
storedPayment.recipient != metaPayment.recipient ||
|
|
2224
|
+
storedPayment.nativeTokenAmount != metaPayment.nativeTokenAmount ||
|
|
2225
|
+
storedPayment.erc20TokenAddress != metaPayment.erc20TokenAddress ||
|
|
2226
|
+
storedPayment.erc20TokenAmount != metaPayment.erc20TokenAmount
|
|
2227
|
+
) {
|
|
2228
|
+
revert SharedValidation.MetaTxPaymentMismatchStoredTx(txId);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
/**
|
|
2233
|
+
* @dev Validates that a wallet has permission for both execution selector and handler selector for a given action.
|
|
2234
|
+
* @param self The SecureOperationState to check.
|
|
2235
|
+
* @param wallet The wallet address to check permissions for.
|
|
2236
|
+
* @param executionSelector The execution function selector (underlying operation).
|
|
2237
|
+
* @param handlerSelector The handler/calling function selector.
|
|
2238
|
+
* @param action The action to validate permissions for.
|
|
2239
|
+
* @notice This function consolidates the repeated dual permission check pattern to reduce contract size.
|
|
2240
|
+
* @notice Reverts with NoPermission if either permission check fails.
|
|
2241
|
+
* @notice Strict mode enforces that the handler's *schema-level* handlerForSelectors flow allows the execution selector;
|
|
2242
|
+
* it does not bind this relation to a specific role's FunctionPermission, which may further narrow pairings.
|
|
2120
2243
|
*/
|
|
2121
2244
|
function _validateExecutionAndHandlerPermissions(
|
|
2122
2245
|
SecureOperationState storage self,
|
|
@@ -2137,27 +2260,37 @@ library EngineBlox {
|
|
|
2137
2260
|
if (!hasActionPermission(self, wallet, handlerSelector, action)) {
|
|
2138
2261
|
revert SharedValidation.NoPermission(wallet);
|
|
2139
2262
|
}
|
|
2263
|
+
|
|
2264
|
+
// In strict mode, enforce that the executionSelector is part of the handlerSelector's schema-level flow.
|
|
2265
|
+
// Handler schemas declare which execution selectors they are allowed to trigger globally; role permissions
|
|
2266
|
+
// can still narrow which selectors a given wallet may use via FunctionPermission.handlerForSelectors.
|
|
2267
|
+
FunctionSchema storage handlerSchema = self.functions[handlerSelector];
|
|
2268
|
+
if (handlerSchema.enforceHandlerRelations) {
|
|
2269
|
+
if (!_schemaHasHandlerSelector(handlerSchema, executionSelector)) {
|
|
2270
|
+
revert SharedValidation.HandlerForSelectorMismatch(
|
|
2271
|
+
executionSelector,
|
|
2272
|
+
handlerSelector
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2140
2276
|
}
|
|
2141
2277
|
|
|
2142
2278
|
/**
|
|
2143
|
-
* @dev Validates that all handlerForSelectors are present in the schema's handlerForSelectors array
|
|
2279
|
+
* @dev Validates that all handlerForSelectors are present in the schema's handlerForSelectors array.
|
|
2280
|
+
* When schema.enforceHandlerRelations is false (flexible mode), validation is skipped and this function returns immediately.
|
|
2281
|
+
* When true (strict mode), every permission handlerForSelector must appear in the schema's handlerForSelectors.
|
|
2144
2282
|
* @param self The SecureOperationState to validate against
|
|
2145
2283
|
* @param functionSelector The function selector for which the permission is defined
|
|
2146
2284
|
* @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)
|
|
2285
|
+
* @notice Reverts with HandlerForSelectorMismatch if any handlerForSelector is not found in the schema's array (strict mode only).
|
|
2286
|
+
* @notice Special case: Execution function permissions should include functionSelector in handlerForSelectors (self-reference).
|
|
2149
2287
|
*/
|
|
2150
2288
|
function _validateHandlerForSelectors(
|
|
2151
2289
|
SecureOperationState storage self,
|
|
2152
2290
|
bytes4 functionSelector,
|
|
2153
2291
|
bytes4[] memory handlerForSelectors
|
|
2154
2292
|
) internal view {
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
// Ensure the function schema exists
|
|
2158
|
-
if (!self.supportedFunctionsSet.contains(functionSelectorHash)) {
|
|
2159
|
-
revert SharedValidation.ResourceNotFound(functionSelectorHash);
|
|
2160
|
-
}
|
|
2293
|
+
_validateFunctionSchemaExists(self, functionSelector);
|
|
2161
2294
|
|
|
2162
2295
|
FunctionSchema storage schema = self.functions[functionSelector];
|
|
2163
2296
|
|
|
@@ -2170,14 +2303,7 @@ library EngineBlox {
|
|
|
2170
2303
|
for (uint256 j = 0; j < handlerForSelectors.length; j++) {
|
|
2171
2304
|
bytes4 handlerForSelector = handlerForSelectors[j];
|
|
2172
2305
|
|
|
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) {
|
|
2306
|
+
if (!_schemaHasHandlerSelector(schema, handlerForSelector)) {
|
|
2181
2307
|
revert SharedValidation.HandlerForSelectorMismatch(
|
|
2182
2308
|
bytes4(0), // Cannot return array, use 0 as placeholder
|
|
2183
2309
|
handlerForSelector
|
|
@@ -2186,6 +2312,24 @@ library EngineBlox {
|
|
|
2186
2312
|
}
|
|
2187
2313
|
}
|
|
2188
2314
|
|
|
2315
|
+
/**
|
|
2316
|
+
* @dev Checks whether a given handler selector is present in a function schema's handlerForSelectors array.
|
|
2317
|
+
* @param schema The function schema to inspect.
|
|
2318
|
+
* @param handlerSelector The handler selector to search for.
|
|
2319
|
+
* @return True if the handler selector is present, false otherwise.
|
|
2320
|
+
*/
|
|
2321
|
+
function _schemaHasHandlerSelector(
|
|
2322
|
+
FunctionSchema storage schema,
|
|
2323
|
+
bytes4 handlerSelector
|
|
2324
|
+
) internal view returns (bool) {
|
|
2325
|
+
for (uint256 i = 0; i < schema.handlerForSelectors.length; i++) {
|
|
2326
|
+
if (schema.handlerForSelectors[i] == handlerSelector) {
|
|
2327
|
+
return true;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
return false;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2189
2333
|
/**
|
|
2190
2334
|
* @dev Validates meta-transaction permissions for a function permission
|
|
2191
2335
|
* @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
|
/**
|