@bloxchain/contracts 1.0.0-alpha.20 → 1.0.0-alpha.22

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.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MPL-2.0
2
- pragma solidity 0.8.34;
2
+ pragma solidity 0.8.35;
3
3
 
4
4
  import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
5
  import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -56,11 +56,6 @@ library EngineBlox {
56
56
 
57
57
  /// @dev Maximum total number of functions allowed in the system (prevents gas exhaustion in function operations)
58
58
  uint256 public constant MAX_FUNCTIONS = 2000;
59
-
60
- /// @dev Maximum bytes copied from call returndata into memory (and later persisted in `TxRecord.result`).
61
- /// Full returndata from the callee may be larger; only the first `MAX_RESULT_PREVIEW_BYTES` are retained.
62
- /// Chosen as 32 KiB to maximize debug/audit surface while bounding memory expansion and storage growth.
63
- uint256 public constant MAX_RESULT_PREVIEW_BYTES = 32 * 1024;
64
59
 
65
60
  using SharedValidation for *;
66
61
  using EnumerableSet for EnumerableSet.UintSet;
@@ -75,8 +70,7 @@ library EngineBlox {
75
70
  PROCESSING_PAYMENT,
76
71
  CANCELLED,
77
72
  COMPLETED,
78
- FAILED,
79
- REJECTED
73
+ FAILED
80
74
  }
81
75
 
82
76
  enum TxAction {
@@ -118,7 +112,9 @@ library EngineBlox {
118
112
  TxStatus status;
119
113
  TxParams params;
120
114
  bytes32 message;
121
- bytes result;
115
+ /// @dev Commitment to execution returndata: `bytes32(0)` when empty, else `keccak256(returndata)`.
116
+ /// Full returndata is emitted in `TxExecutionResult` on terminal execution (COMPLETED/FAILED).
117
+ bytes32 resultHash;
122
118
  PaymentDetails payment;
123
119
  }
124
120
 
@@ -164,6 +160,9 @@ library EngineBlox {
164
160
  /// When false (flexible mode): no such check; forward references and unregistered selectors in handlerForSelectors are allowed at registration.
165
161
  bool enforceHandlerRelations;
166
162
  bool isProtected;
163
+ /// @dev When false, `removeFunctionFromRole` cannot remove this selector from any role (revoke + re-add updates blocked).
164
+ /// When true, grants may be removed from any role, including protected system roles; `isProtected` still blocks `unregisterFunction` for the schema.
165
+ bool isGrantRevocable;
167
166
  bytes4[] handlerForSelectors;
168
167
  }
169
168
 
@@ -254,9 +253,15 @@ library EngineBlox {
254
253
  TxStatus status,
255
254
  address indexed requester,
256
255
  address target,
257
- bytes32 operationType
256
+ bytes32 operationType,
257
+ bytes32 resultHash
258
258
  );
259
259
 
260
+ /// @dev Emitted only on terminal execution (COMPLETED/FAILED). Full execution returndata (`result` may be empty).
261
+ /// Verify against `TxRecord.resultHash` from the same tx: `result.length == 0` implies `resultHash == bytes32(0)`;
262
+ /// otherwise `keccak256(result) == resultHash` (see `_executionResultHash`).
263
+ event TxExecutionResult(uint256 indexed txId, bytes result);
264
+
260
265
  // ============ SYSTEM STATE FUNCTIONS ============
261
266
 
262
267
  /**
@@ -473,14 +478,15 @@ library EngineBlox {
473
478
  uint256 txId,
474
479
  bytes4 handlerSelector
475
480
  ) public returns (TxRecord memory) {
476
- // Validate both execution and handler selector permissions
481
+ // CHECK: Validate both execution and handler selector permissions
477
482
  _validateExecutionAndHandlerPermissions(self, msg.sender, self.txRecords[txId].params.executionSelector, handlerSelector, TxAction.EXECUTE_TIME_DELAY_APPROVE);
478
483
  _validateTxStatus(self, txId, TxStatus.PENDING);
484
+ _validateTargetWhitelist(self, self.txRecords[txId].params.executionSelector, self.txRecords[txId].params.target);
479
485
  SharedValidation.validateReleaseTime(self.txRecords[txId].releaseTime);
480
-
486
+
481
487
  // EFFECT: Update status to EXECUTING before external call to prevent reentrancy
482
488
  self.txRecords[txId].status = TxStatus.EXECUTING;
483
-
489
+
484
490
  // INTERACT: External call after state update
485
491
  (bool success, bytes memory result) = executeTransaction(self, self.txRecords[txId]);
486
492
 
@@ -560,17 +566,19 @@ library EngineBlox {
560
566
  * (direct path enforces releaseTime) and meta-tx workflows (delegated, time-flexible approval).
561
567
  */
562
568
  function _txApprovalWithMetaTx(SecureOperationState storage self, MetaTransaction memory metaTx) private returns (TxRecord memory) {
569
+ // CHECK: Validate transaction parameters
563
570
  uint256 txId = metaTx.txRecord.txId;
564
571
  _validateTxStatus(self, txId, TxStatus.PENDING);
572
+ _validateTargetWhitelist(self, self.txRecords[txId].params.executionSelector, self.txRecords[txId].params.target);
565
573
  _validateMetaTxMatchRecord(self, txId, metaTx.txRecord);
566
574
  _validateMetaTxPaymentMatchRecord(self, txId, metaTx.txRecord);
567
575
  if (!verifySignature(self, metaTx)) revert SharedValidation.InvalidSignature(metaTx.signature);
568
-
576
+
569
577
  incrementSignerNonce(self, metaTx.params.signer);
570
-
578
+
571
579
  // EFFECT: Update status to EXECUTING before external call to prevent reentrancy
572
580
  self.txRecords[txId].status = TxStatus.EXECUTING;
573
-
581
+
574
582
  // INTERACT: External call after state update
575
583
  (bool success, bytes memory result) = executeTransaction(self, self.txRecords[txId]);
576
584
 
@@ -612,46 +620,6 @@ library EngineBlox {
612
620
  return _txApprovalWithMetaTx(self, metaTx);
613
621
  }
614
622
 
615
- /**
616
- * @dev Performs `address.call` without copying unbounded returndata into memory.
617
- * @param target Callee address
618
- * @param value Native value to forward
619
- * @param callGas Gas to forward to the callee
620
- * @param data Full calldata for the call
621
- * @return success Whether the call returned success
622
- * @return result First `min(returndatasize(), MAX_RESULT_PREVIEW_BYTES)` bytes of returndata
623
- */
624
- function _callWithBoundedReturndata(
625
- address target,
626
- uint256 value,
627
- uint256 callGas,
628
- bytes memory data
629
- ) private returns (bool success, bytes memory result) {
630
- uint256 dataLength = data.length;
631
- assembly ("memory-safe") {
632
- success := call(
633
- callGas,
634
- target,
635
- value,
636
- add(data, 0x20),
637
- dataLength,
638
- 0,
639
- 0
640
- )
641
- }
642
- uint256 returnSize;
643
- assembly ("memory-safe") {
644
- returnSize := returndatasize()
645
- }
646
- uint256 copyLength = returnSize > MAX_RESULT_PREVIEW_BYTES ? MAX_RESULT_PREVIEW_BYTES : returnSize;
647
- result = new bytes(copyLength);
648
- assembly ("memory-safe") {
649
- if gt(copyLength, 0) {
650
- returndatacopy(add(result, 0x20), 0, copyLength)
651
- }
652
- }
653
- }
654
-
655
623
  /**
656
624
  * @dev Executes a transaction based on its execution type and attached payment.
657
625
  * @param self The SecureOperationState storage reference (for validation)
@@ -666,13 +634,12 @@ library EngineBlox {
666
634
  * causing _validateTxPending to revert in entry functions
667
635
  * 4. Status flow is one-way: PENDING → EXECUTING → (COMPLETED/FAILED)
668
636
  * This creates an effective reentrancy guard without additional storage overhead.
669
- * @notice Returndata from the target is captured up to `MAX_RESULT_PREVIEW_BYTES` only (low-level `call` with
670
- * zero output area, then bounded `returndatacopy`). This prevents unbounded memory expansion from
671
- * malicious or buggy callees while preserving a large preview for debugging and audits.
672
637
  * @notice **Atomicity:** If the main call succeeds but `executeAttachedPayment` reverts (e.g.
673
638
  * insufficient balance, whitelist mismatch), the **entire** approval/execute transaction reverts—main
674
639
  * effect included. This is **intentional all-or-nothing** semantics; splitting finalize vs payment
675
640
  * would require a separate design with reentrancy and state-machine implications.
641
+ * @notice `record` is a memory copy: final `status` and `resultHash` are written to storage by `_completeTransaction`;
642
+ * full returndata is emitted in `TxExecutionResult`.
676
643
  */
677
644
  function executeTransaction(SecureOperationState storage self, TxRecord memory record) private returns (bool, bytes memory) {
678
645
  // Validate that transaction is in EXECUTING status (set by caller before this function)
@@ -690,24 +657,15 @@ library EngineBlox {
690
657
  // Execute the main transaction
691
658
  // REENTRANCY SAFE: Status is EXECUTING, preventing reentry through entry functions
692
659
  // that require PENDING status. Any reentry attempt would fail at _validateTxStatus(..., PENDING).
693
- (bool success, bytes memory result) = _callWithBoundedReturndata(
694
- record.params.target,
695
- record.params.value,
696
- gas,
660
+ (bool success, bytes memory result) = record.params.target.call{value: record.params.value, gas: gas}(
697
661
  txData
698
662
  );
699
663
 
700
664
  if (success) {
701
- record.status = TxStatus.COMPLETED;
702
- record.result = result;
703
-
704
665
  // Execute attached payment if transaction was successful
705
666
  if (record.payment.recipient != address(0)) {
706
667
  executeAttachedPayment(self, record);
707
668
  }
708
- } else {
709
- record.status = TxStatus.FAILED;
710
- record.result = result;
711
669
  }
712
670
 
713
671
  return (success, result);
@@ -839,7 +797,7 @@ library EngineBlox {
839
797
  executionParams: executionParams
840
798
  }),
841
799
  message: 0,
842
- result: "",
800
+ resultHash: bytes32(0),
843
801
  payment: payment
844
802
  });
845
803
  }
@@ -850,8 +808,7 @@ library EngineBlox {
850
808
  * @param txId The transaction ID to add to the pending set.
851
809
  */
852
810
  function addPendingTx(SecureOperationState storage self, uint256 txId) private {
853
- SharedValidation.validateTransactionExists(txId);
854
- _validateTxStatus(self, txId, TxStatus.PENDING);
811
+ SharedValidation.validateTransactionExists(txId, self.txCounter);
855
812
 
856
813
  // Try to add transaction ID to the set - add() returns false if already exists
857
814
  if (!self.pendingTransactionsSet.add(txId)) {
@@ -865,7 +822,7 @@ library EngineBlox {
865
822
  * @param txId The transaction ID to remove from the pending set.
866
823
  */
867
824
  function removePendingTx(SecureOperationState storage self, uint256 txId) private {
868
- SharedValidation.validateTransactionExists(txId);
825
+ SharedValidation.validateTransactionExists(txId, self.txCounter);
869
826
 
870
827
  // Remove the transaction ID from the set (O(1) operation)
871
828
  if (!self.pendingTransactionsSet.remove(txId)) {
@@ -1108,8 +1065,8 @@ library EngineBlox {
1108
1065
  * @param functionPermission The function permission to add.
1109
1066
  * @notice Reverts **`ResourceAlreadyExists`** if the selector is already present on the role. To update
1110
1067
  * bitmap or `handlerForSelectors`, **`removeFunctionFromRole`** first then re-add.
1111
- * Protected schemas cannot be removed from roles (`CannotModifyProtected`), so grants of
1112
- * protected selectors to a role are effectively **permanent** unless the role itself is removed.
1068
+ * **`removeFunctionFromRole`** succeeds only when the schema's **`isGrantRevocable`** is true (see there);
1069
+ * **`isProtected`** on the schema does not, by itself, block removing a grant from a role.
1113
1070
  */
1114
1071
  function addFunctionToRole(
1115
1072
  SecureOperationState storage self,
@@ -1142,10 +1099,10 @@ library EngineBlox {
1142
1099
  * @param self The SecureOperationState to modify.
1143
1100
  * @param roleHash The role hash to remove the function permission from.
1144
1101
  * @param functionSelector The function selector to remove from the role.
1145
- * @notice **Protected schemas cannot be removed from roles** (`CannotModifyProtected`). Granting a protected
1146
- * selector to a role is therefore an **irreversible expansion** of that role's capability unless the
1147
- * role itself is removed. Operators should only add protected selectors to roles when the
1148
- * permanent authority is intended.
1102
+ * @notice When the selector is registered, reverts **`GrantNotRevocable`** if **`isGrantRevocable == false`**
1103
+ * (no role may drop this grant). When **`isGrantRevocable == true`**, the grant may be removed from
1104
+ * any role, including protected system roles; **`isProtected`** on the schema still blocks
1105
+ * **`unregisterFunction`** independently, and **`removeRole`** still blocks protected roles.
1149
1106
  */
1150
1107
  function removeFunctionFromRole(
1151
1108
  SecureOperationState storage self,
@@ -1155,12 +1112,10 @@ library EngineBlox {
1155
1112
  // Check if role exists (checks both roles mapping and supportedRolesSet)
1156
1113
  _validateRoleExists(self, roleHash);
1157
1114
 
1158
- // Security check: Prevent removing protected functions from roles
1159
- // Check if function exists and is protected
1160
1115
  if (self.supportedFunctionsSet.contains(bytes32(functionSelector))) {
1161
1116
  FunctionSchema memory functionSchema = self.functions[functionSelector];
1162
- if (functionSchema.isProtected) {
1163
- revert SharedValidation.CannotModifyProtected(bytes32(functionSelector));
1117
+ if (!functionSchema.isGrantRevocable) {
1118
+ revert SharedValidation.GrantNotRevocable(functionSelector);
1164
1119
  }
1165
1120
  }
1166
1121
 
@@ -1259,7 +1214,8 @@ library EngineBlox {
1259
1214
  * @param operationName The name of the operation type.
1260
1215
  * @param supportedActionsBitmap Bitmap of permissions required to execute this function.
1261
1216
  * @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.
1262
- * @param isProtected Whether the function schema is protected from removal.
1217
+ * @param isProtected Whether the function schema is protected from **unregister** (`unregisterFunction`).
1218
+ * @param isGrantRevocable When false, `removeFunctionFromRole` cannot remove this selector from any role; when true, grants may be removed from any role, including protected system roles.
1263
1219
  * @param handlerForSelectors Non-empty array required - execution selectors must contain self-reference, handler selectors must point to execution selectors.
1264
1220
  * @custom:security OPERATIONAL MODES: We do not require handlerForSelectors[i] to be in supportedFunctionsSet at registration.
1265
1221
  * - Strict mode (enforceHandlerRelations == true): at use time (_validateHandlerForSelectors) we require role permissions' handlerForSelectors to match this schema's handlerForSelectors; registration order is flexible.
@@ -1273,6 +1229,7 @@ library EngineBlox {
1273
1229
  uint16 supportedActionsBitmap,
1274
1230
  bool enforceHandlerRelations,
1275
1231
  bool isProtected,
1232
+ bool isGrantRevocable,
1276
1233
  bytes4[] memory handlerForSelectors
1277
1234
  ) public {
1278
1235
  // Validate that functionSignature matches functionSelector
@@ -1323,6 +1280,7 @@ library EngineBlox {
1323
1280
  schema.supportedActionsBitmap = supportedActionsBitmap;
1324
1281
  schema.enforceHandlerRelations = enforceHandlerRelations;
1325
1282
  schema.isProtected = isProtected;
1283
+ schema.isGrantRevocable = isGrantRevocable;
1326
1284
  schema.handlerForSelectors = handlerForSelectors;
1327
1285
 
1328
1286
  // Add to supportedFunctionsSet
@@ -2058,7 +2016,7 @@ library EngineBlox {
2058
2016
  SharedValidation.validateChainId(metaTxParams.chainId);
2059
2017
  SharedValidation.validateMetaTxHandlerContractBinding(metaTxParams.handlerContract);
2060
2018
  SharedValidation.validateHandlerSelector(metaTxParams.handlerSelector);
2061
- SharedValidation.validateDeadline(metaTxParams.deadline);
2019
+ SharedValidation.validateMetaTxDeadline(metaTxParams.deadline);
2062
2020
  SharedValidation.validateNotZeroAddress(metaTxParams.signer);
2063
2021
 
2064
2022
  // Populate the nonce directly from storage for security
@@ -2128,6 +2086,8 @@ library EngineBlox {
2128
2086
  * @notice **Gas tradeoff:** There is no explicit `{gas: ...}` stipend; the subcall receives the usual EIP-150
2129
2087
  * bounded share of remaining gas (not the entire tx). Primary state updates in callers run **before**
2130
2088
  * `logTxEvent` where applicable. Optional hardening: configurable stipend + explicit failure event.
2089
+ * @notice **Execution returndata:** full bytes are emitted only via `TxExecutionResult` from `_completeTransaction`;
2090
+ * this function emits lifecycle `TransactionEvent` with `resultHash` (zero on request/cancel).
2131
2091
  * @custom:security REENTRANCY PROTECTION: This function is safe from reentrancy because:
2132
2092
  * 1. It is called AFTER all state changes are complete (in _completeTransaction,
2133
2093
  * _cancelTransaction, and txRequest)
@@ -2145,21 +2105,17 @@ library EngineBlox {
2145
2105
  bytes4 functionSelector
2146
2106
  ) public {
2147
2107
  TxRecord memory txRecord = self.txRecords[txId];
2148
-
2149
- // Emit only non-sensitive public data
2108
+
2150
2109
  emit TransactionEvent(
2151
2110
  txId,
2152
2111
  functionSelector,
2153
2112
  txRecord.status,
2154
2113
  txRecord.params.requester,
2155
2114
  txRecord.params.target,
2156
- txRecord.params.operationType
2115
+ txRecord.params.operationType,
2116
+ txRecord.resultHash
2157
2117
  );
2158
-
2159
- // Forward event data to event forwarder
2160
- // REENTRANCY SAFE: External call is wrapped in try-catch and doesn't modify
2161
- // critical state. Even if eventForwarder is malicious, reentry attempts fail
2162
- // because transactions are no longer in PENDING status (they're COMPLETED/CANCELLED).
2118
+
2163
2119
  if (self.eventForwarder != address(0)) {
2164
2120
  try IEventForwarder(self.eventForwarder).forwardTxEvent(
2165
2121
  txId,
@@ -2167,12 +2123,9 @@ library EngineBlox {
2167
2123
  txRecord.status,
2168
2124
  txRecord.params.requester,
2169
2125
  txRecord.params.target,
2170
- txRecord.params.operationType
2171
- ) {
2172
- // Event forwarded successfully
2173
- } catch {
2174
- // Forwarding failed, continue execution (non-critical operation)
2175
- }
2126
+ txRecord.params.operationType,
2127
+ txRecord.resultHash
2128
+ ) {} catch {}
2176
2129
  }
2177
2130
  }
2178
2131
 
@@ -2258,33 +2211,26 @@ library EngineBlox {
2258
2211
  * @param self The SecureOperationState to modify
2259
2212
  * @param txId The transaction ID to complete
2260
2213
  * @param success Whether the transaction execution was successful
2261
- * @param result The result of the transaction execution
2214
+ * @param executionResult Returndata from the main target call (emitted in `TxExecutionResult`).
2262
2215
  */
2263
2216
  function _completeTransaction(
2264
2217
  SecureOperationState storage self,
2265
2218
  uint256 txId,
2266
2219
  bool success,
2267
- bytes memory result
2220
+ bytes memory executionResult
2268
2221
  ) private {
2269
- // enforce that the requested target is whitelisted for this selector.
2270
- _validateTargetWhitelist(self, self.txRecords[txId].params.executionSelector, self.txRecords[txId].params.target);
2271
-
2272
- // Update storage with new status and result
2222
+ bytes32 resultHash = _executionResultHash(executionResult);
2273
2223
  if (success) {
2274
2224
  self.txRecords[txId].status = TxStatus.COMPLETED;
2275
- self.txRecords[txId].result = result;
2276
2225
  } else {
2277
2226
  self.txRecords[txId].status = TxStatus.FAILED;
2278
- self.txRecords[txId].result = result; // Store failure reason for debugging
2279
- // Note: FAILED status is intentional - transactions can be valid when requested
2280
- // but fail when executed (e.g., conditions changed, insufficient balance, etc.)
2281
- // Users can query status via getTransaction() or listen to TransactionEvent
2282
2227
  }
2283
-
2284
- // Remove from pending transactions list
2228
+ self.txRecords[txId].resultHash = resultHash;
2229
+
2285
2230
  removePendingTx(self, txId);
2286
-
2231
+
2287
2232
  logTxEvent(self, txId, self.txRecords[txId].params.executionSelector);
2233
+ emit TxExecutionResult(txId, executionResult);
2288
2234
  }
2289
2235
 
2290
2236
  /**
@@ -2296,9 +2242,6 @@ library EngineBlox {
2296
2242
  SecureOperationState storage self,
2297
2243
  uint256 txId
2298
2244
  ) private {
2299
- // enforce that the requested target is whitelisted for this selector.
2300
- _validateTargetWhitelist(self, self.txRecords[txId].params.executionSelector, self.txRecords[txId].params.target);
2301
-
2302
2245
  self.txRecords[txId].status = TxStatus.CANCELLED;
2303
2246
 
2304
2247
  // Remove from pending transactions list
@@ -2307,7 +2250,16 @@ library EngineBlox {
2307
2250
  logTxEvent(self, txId, self.txRecords[txId].params.executionSelector);
2308
2251
  }
2309
2252
 
2310
- /**
2253
+ /**
2254
+ * @dev Commitment to execution returndata for storage and `TransactionEvent.resultHash`.
2255
+ * @param executionResult The execution returndata to hash
2256
+ * @return `bytes32(0)` when empty, else `keccak256(executionResult)`
2257
+ */
2258
+ function _executionResultHash(bytes memory executionResult) private pure returns (bytes32) {
2259
+ return executionResult.length == 0 ? bytes32(0) : keccak256(executionResult);
2260
+ }
2261
+
2262
+ /**
2311
2263
  * @dev Validates that the caller has any role permission
2312
2264
  * @param self The SecureOperationState to check
2313
2265
  * @notice This function consolidates the repeated permission check pattern to reduce contract size
@@ -2390,7 +2342,8 @@ library EngineBlox {
2390
2342
  sp.gasLimit != mp.gasLimit ||
2391
2343
  sp.operationType != mp.operationType ||
2392
2344
  keccak256(sp.executionParams) != keccak256(mp.executionParams) ||
2393
- stored.releaseTime != metaTxRecord.releaseTime
2345
+ stored.releaseTime != metaTxRecord.releaseTime ||
2346
+ metaTxRecord.resultHash != bytes32(0)
2394
2347
  ) {
2395
2348
  revert SharedValidation.MetaTxRecordMismatchStoredTx(txId);
2396
2349
  }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MPL-2.0
2
- pragma solidity 0.8.34;
2
+ pragma solidity 0.8.35;
3
3
 
4
4
  import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
5
5
  import "../EngineBlox.sol";
@@ -1,33 +1,35 @@
1
- // SPDX-License-Identifier: MPL-2.0
2
- pragma solidity 0.8.34;
3
-
4
- // Import TxRecord struct from EngineBlox
5
- import "../EngineBlox.sol";
6
-
7
- /**
8
- * @title IEventForwarder
9
- * @dev Interface for the event forwarder contract
10
- *
11
- * This interface defines the contract for forwarding events from deployed instances
12
- * to a centralized event monitoring system. It uses function selectors for efficient
13
- * event identification and categorization.
14
- */
15
- interface IEventForwarder {
16
- /**
17
- * @dev Forward a transaction event from a deployed instance
18
- * @param txId The transaction ID
19
- * @param functionSelector The function selector for the event (bytes4)
20
- * @param status The transaction status
21
- * @param requester The address of the requester
22
- * @param target The target contract address
23
- * @param operationType The type of operation
24
- */
25
- function forwardTxEvent(
26
- uint256 txId,
27
- bytes4 functionSelector,
28
- EngineBlox.TxStatus status,
29
- address requester,
30
- address target,
31
- bytes32 operationType
32
- ) external;
33
- }
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ pragma solidity 0.8.35;
3
+
4
+ // Import TxRecord struct from EngineBlox
5
+ import "../EngineBlox.sol";
6
+
7
+ /**
8
+ * @title IEventForwarder
9
+ * @dev Interface for the event forwarder contract
10
+ *
11
+ * This interface defines the contract for forwarding events from deployed instances
12
+ * to a centralized event monitoring system. It uses function selectors for efficient
13
+ * event identification and categorization.
14
+ */
15
+ interface IEventForwarder {
16
+ /**
17
+ * @dev Forward a transaction event from a deployed instance
18
+ * @param txId The transaction ID
19
+ * @param functionSelector The function selector for the event (bytes4)
20
+ * @param status The transaction status
21
+ * @param requester The address of the requester
22
+ * @param target The target contract address
23
+ * @param operationType The type of operation
24
+ * @param resultHash Commitment to execution returndata (`bytes32(0)` when none). Full bytes: `TxExecutionResult` log.
25
+ */
26
+ function forwardTxEvent(
27
+ uint256 txId,
28
+ bytes4 functionSelector,
29
+ EngineBlox.TxStatus status,
30
+ address requester,
31
+ address target,
32
+ bytes32 operationType,
33
+ bytes32 resultHash
34
+ ) external;
35
+ }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MPL-2.0
2
- pragma solidity 0.8.34;
2
+ pragma solidity 0.8.35;
3
3
 
4
4
  /**
5
5
  * @title SharedValidation
@@ -38,7 +38,6 @@ library SharedValidation {
38
38
  // Time and deadline errors with context
39
39
  error InvalidTimeLockPeriod(uint256 provided);
40
40
  error TimeLockPeriodZero(uint256 provided);
41
- error DeadlineInPast(uint256 deadline, uint256 currentTime);
42
41
  error MetaTxExpired(uint256 deadline, uint256 currentTime);
43
42
  error BeforeReleaseTime(uint256 releaseTime, uint256 currentTime);
44
43
  error NewTimelockSame(uint256 newPeriod, uint256 currentPeriod);
@@ -82,6 +81,7 @@ library SharedValidation {
82
81
  error ResourceNotFound(bytes32 resourceId);
83
82
  error ResourceAlreadyExists(bytes32 resourceId);
84
83
  error CannotModifyProtected(bytes32 resourceId);
84
+ error GrantNotRevocable(bytes4 functionSelector);
85
85
 
86
86
  // Consolidated item errors (for addresses: wallets, policies, etc.)
87
87
  error ItemAlreadyExists(address item);
@@ -222,14 +222,6 @@ library SharedValidation {
222
222
  if (timeLockPeriod == 0) revert TimeLockPeriodZero(timeLockPeriod);
223
223
  }
224
224
 
225
- /**
226
- * @dev Validates that a deadline is in the future
227
- * @param deadline The deadline timestamp to validate
228
- */
229
- function validateDeadline(uint256 deadline) internal view {
230
- if (deadline <= block.timestamp) revert DeadlineInPast(deadline, block.timestamp);
231
- }
232
-
233
225
  /**
234
226
  * @dev Validates that a new time lock period is different from the current one
235
227
  * @param newPeriod The new time lock period
@@ -249,8 +241,9 @@ library SharedValidation {
249
241
  }
250
242
 
251
243
  /**
252
- * @dev Validates that a meta-transaction has not expired
253
- * @param deadline The deadline of the meta-transaction
244
+ * @dev Validates that a meta-transaction deadline has not passed (inclusive boundary: `block.timestamp == deadline` is valid).
245
+ * Used both when building unsigned meta-tx payloads (`EngineBlox.generateMetaTransaction`) and when verifying submitted meta-txs.
246
+ * @param deadline The deadline timestamp from `MetaTxParams`
254
247
  */
255
248
  function validateMetaTxDeadline(uint256 deadline) internal view {
256
249
  if (block.timestamp > deadline) revert MetaTxExpired(deadline, block.timestamp);
@@ -360,11 +353,12 @@ library SharedValidation {
360
353
  }
361
354
 
362
355
  /**
363
- * @dev Validates that a transaction exists (has non-zero ID)
356
+ * @dev Validates that `txId` refers to a minted record: non-zero and at most `txCounter` (inclusive).
364
357
  * @param txId The transaction ID to validate
358
+ * @param txCounter The engine's `txCounter` after the record was allocated (valid ids are `1..txCounter`)
365
359
  */
366
- function validateTransactionExists(uint256 txId) internal pure {
367
- if (txId == 0) revert ResourceNotFound(bytes32(uint256(txId)));
360
+ function validateTransactionExists(uint256 txId, uint256 txCounter) internal pure {
361
+ if (txId == 0 || txId > txCounter) revert ResourceNotFound(bytes32(uint256(txId)));
368
362
  }
369
363
 
370
364
  /**
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MPL-2.0
2
- pragma solidity 0.8.34;
2
+ pragma solidity 0.8.35;
3
3
 
4
4
  import "../execution/GuardController.sol";
5
5
  import "../execution/interface/IGuardController.sol";