@bloxchain/contracts 1.0.0-alpha
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/README.md +49 -0
- package/abi/BareBlox.abi.json +1341 -0
- package/abi/BaseStateMachine.abi.json +1308 -0
- package/abi/ControlBlox.abi.json +6210 -0
- package/abi/EngineBlox.abi.json +872 -0
- package/abi/GuardController.abi.json +3045 -0
- package/abi/IDefinition.abi.json +94 -0
- package/abi/RoleBlox.abi.json +4569 -0
- package/abi/RuntimeRBAC.abi.json +1857 -0
- package/abi/RuntimeRBACDefinitions.abi.json +133 -0
- package/abi/SecureBlox.abi.json +4085 -0
- package/abi/SecureOwnable.abi.json +4085 -0
- package/abi/SecureOwnableDefinitions.abi.json +354 -0
- package/abi/SimpleRWA20.abi.json +5545 -0
- package/abi/SimpleRWA20Definitions.abi.json +172 -0
- package/abi/SimpleVault.abi.json +5208 -0
- package/abi/SimpleVaultDefinitions.abi.json +250 -0
- package/contracts/core/access/RuntimeRBAC.sol +344 -0
- package/contracts/core/access/interface/IRuntimeRBAC.sol +108 -0
- package/contracts/core/access/lib/definitions/RuntimeRBACDefinitions.sol +168 -0
- package/contracts/core/base/BaseStateMachine.sol +834 -0
- package/contracts/core/base/interface/IBaseStateMachine.sol +153 -0
- package/contracts/core/execution/GuardController.sol +507 -0
- package/contracts/core/execution/interface/IGuardController.sol +120 -0
- package/contracts/core/execution/lib/definitions/GuardControllerDefinitions.sol +401 -0
- package/contracts/core/lib/EngineBlox.sol +2283 -0
- package/contracts/core/security/SecureOwnable.sol +419 -0
- package/contracts/core/security/interface/ISecureOwnable.sol +118 -0
- package/contracts/core/security/lib/definitions/SecureOwnableDefinitions.sol +757 -0
- package/contracts/interfaces/IDefinition.sol +40 -0
- package/contracts/interfaces/IEventForwarder.sol +33 -0
- package/contracts/interfaces/IOnActionHook.sol +79 -0
- package/contracts/utils/SharedValidation.sol +486 -0
- package/package.json +47 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
pragma solidity 0.8.33;
|
|
3
|
+
|
|
4
|
+
// OpenZeppelin imports
|
|
5
|
+
import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
|
|
6
|
+
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
7
|
+
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
|
|
8
|
+
|
|
9
|
+
// Contracts imports
|
|
10
|
+
import "../lib/EngineBlox.sol";
|
|
11
|
+
import "../../utils/SharedValidation.sol";
|
|
12
|
+
import "./interface/IBaseStateMachine.sol";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @title BaseStateMachine
|
|
16
|
+
* @dev Core state machine functionality for secure multi-phase operations
|
|
17
|
+
*
|
|
18
|
+
* This contract provides the foundational state machine capabilities that can be extended
|
|
19
|
+
* by security-specific contracts. It handles:
|
|
20
|
+
* - State initialization and management
|
|
21
|
+
* - Meta-transaction utilities and parameter creation
|
|
22
|
+
* - State queries and transaction history
|
|
23
|
+
* - Role-based access control queries
|
|
24
|
+
* - System state information
|
|
25
|
+
*
|
|
26
|
+
* The contract is designed to be inherited by security-specific contracts that implement
|
|
27
|
+
* their own operation types and business logic while leveraging the core state machine.
|
|
28
|
+
* All access to EngineBlox library functions is centralized through BaseStateMachine
|
|
29
|
+
* wrapper functions to ensure consistency and maintainability.
|
|
30
|
+
*
|
|
31
|
+
* Key Features:
|
|
32
|
+
* - State initialization with role and permission setup
|
|
33
|
+
* - Meta-transaction parameter creation and generation
|
|
34
|
+
* - Comprehensive state queries and transaction history
|
|
35
|
+
* - Role and permission validation utilities
|
|
36
|
+
* - System configuration queries
|
|
37
|
+
* - Event forwarding for external monitoring
|
|
38
|
+
*/
|
|
39
|
+
abstract contract BaseStateMachine is Initializable, ERC165Upgradeable, ReentrancyGuardUpgradeable {
|
|
40
|
+
using EngineBlox for EngineBlox.SecureOperationState;
|
|
41
|
+
using SharedValidation for *;
|
|
42
|
+
|
|
43
|
+
EngineBlox.SecureOperationState internal _secureState;
|
|
44
|
+
|
|
45
|
+
// Events for core state machine operations
|
|
46
|
+
event TransactionRequested(
|
|
47
|
+
uint256 indexed txId,
|
|
48
|
+
address indexed requester,
|
|
49
|
+
bytes32 indexed operationType,
|
|
50
|
+
uint256 releaseTime
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
event TransactionApproved(
|
|
54
|
+
uint256 indexed txId,
|
|
55
|
+
bytes32 indexed operationType,
|
|
56
|
+
address indexed approver
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
event TransactionCancelled(
|
|
60
|
+
uint256 indexed txId,
|
|
61
|
+
bytes32 indexed operationType,
|
|
62
|
+
address indexed canceller
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
event TransactionExecuted(
|
|
66
|
+
uint256 indexed txId,
|
|
67
|
+
bytes32 indexed operationType,
|
|
68
|
+
bool success
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @notice Initializes the base state machine core
|
|
73
|
+
* @param initialOwner The initial owner address
|
|
74
|
+
* @param broadcaster The broadcaster address
|
|
75
|
+
* @param recovery The recovery address
|
|
76
|
+
* @param timeLockPeriodSec The timelock period in seconds
|
|
77
|
+
* @param eventForwarder The event forwarder address
|
|
78
|
+
*/
|
|
79
|
+
function _initializeBaseStateMachine(
|
|
80
|
+
address initialOwner,
|
|
81
|
+
address broadcaster,
|
|
82
|
+
address recovery,
|
|
83
|
+
uint256 timeLockPeriodSec,
|
|
84
|
+
address eventForwarder
|
|
85
|
+
) internal onlyInitializing {
|
|
86
|
+
__ERC165_init();
|
|
87
|
+
__ReentrancyGuard_init();
|
|
88
|
+
|
|
89
|
+
_secureState.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec);
|
|
90
|
+
|
|
91
|
+
_secureState.setEventForwarder(eventForwarder);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============ SYSTEM ROLE QUERY FUNCTIONS ============
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @dev Returns the owner of the contract
|
|
98
|
+
* @return The owner of the contract
|
|
99
|
+
*/
|
|
100
|
+
function owner() public view returns (address) {
|
|
101
|
+
return _getAuthorizedWalletAt(EngineBlox.OWNER_ROLE, 0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @dev Returns all broadcaster addresses for the BROADCASTER_ROLE
|
|
106
|
+
* @return Array of broadcaster addresses
|
|
107
|
+
*/
|
|
108
|
+
function getBroadcasters() public view returns (address[] memory) {
|
|
109
|
+
return _getAuthorizedWallets(EngineBlox.BROADCASTER_ROLE);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @dev Returns the recovery address
|
|
114
|
+
* @return The recovery address
|
|
115
|
+
*/
|
|
116
|
+
function getRecovery() public view returns (address) {
|
|
117
|
+
return _getAuthorizedWalletAt(EngineBlox.RECOVERY_ROLE, 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============ INTERFACE SUPPORT ============
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @dev See {IERC165-supportsInterface}.
|
|
124
|
+
* @notice Base implementation for ERC165 interface detection
|
|
125
|
+
* @notice Registers IBaseStateMachine interface ID for proper interface detection
|
|
126
|
+
* @notice Component contracts (SecureOwnable, RuntimeRBAC, GuardController) should override
|
|
127
|
+
* to add their respective interface IDs for component detection
|
|
128
|
+
*/
|
|
129
|
+
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
|
|
130
|
+
return interfaceId == type(IBaseStateMachine).interfaceId || super.supportsInterface(interfaceId);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============ TRANSACTION MANAGEMENT ============
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @dev Centralized function to request a transaction with common validation
|
|
137
|
+
* @param requester The address requesting the transaction
|
|
138
|
+
* @param target The target contract address
|
|
139
|
+
* @param value The ETH value to send (0 for standard function calls)
|
|
140
|
+
* @param gasLimit The gas limit for execution
|
|
141
|
+
* @param operationType The type of operation
|
|
142
|
+
* @param functionSelector The function selector for execution (NATIVE_TRANSFER_SELECTOR for simple native token transfers)
|
|
143
|
+
* @param params The encoded parameters for the function (empty for simple native token transfers)
|
|
144
|
+
* @return The created transaction record
|
|
145
|
+
* @notice Validates permissions for the calling function (request function), not the execution selector
|
|
146
|
+
* @notice Execution functions are internal-only and don't need permission definitions
|
|
147
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
148
|
+
* @notice For standard function calls: value=0, functionSelector=non-zero, params=encoded data
|
|
149
|
+
* @notice For simple native token transfers: value>0, functionSelector=NATIVE_TRANSFER_SELECTOR, params=""
|
|
150
|
+
*/
|
|
151
|
+
function _requestTransaction(
|
|
152
|
+
address requester,
|
|
153
|
+
address target,
|
|
154
|
+
uint256 value,
|
|
155
|
+
uint256 gasLimit,
|
|
156
|
+
bytes32 operationType,
|
|
157
|
+
bytes4 functionSelector,
|
|
158
|
+
bytes memory params
|
|
159
|
+
) internal virtual returns (EngineBlox.TxRecord memory) {
|
|
160
|
+
return EngineBlox.txRequest(
|
|
161
|
+
_getSecureState(),
|
|
162
|
+
requester,
|
|
163
|
+
target,
|
|
164
|
+
value,
|
|
165
|
+
gasLimit,
|
|
166
|
+
operationType,
|
|
167
|
+
bytes4(msg.sig),
|
|
168
|
+
functionSelector,
|
|
169
|
+
params
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @dev Centralized function to approve a pending transaction after release time
|
|
175
|
+
* @param txId The transaction ID
|
|
176
|
+
* @return The updated transaction record
|
|
177
|
+
* @notice Validates permissions for the calling function (approval function selector), not the execution selector
|
|
178
|
+
* @notice Execution functions are internal-only and don't need permission definitions
|
|
179
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
180
|
+
* @notice Protected by ReentrancyGuard to prevent reentrancy attacks
|
|
181
|
+
*/
|
|
182
|
+
function _approveTransaction(
|
|
183
|
+
uint256 txId
|
|
184
|
+
) internal virtual nonReentrant returns (EngineBlox.TxRecord memory) {
|
|
185
|
+
return EngineBlox.txDelayedApproval(_getSecureState(), txId, bytes4(msg.sig));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @dev Centralized function to approve a transaction using meta-transaction
|
|
190
|
+
* @param metaTx The meta-transaction
|
|
191
|
+
* @return The updated transaction record
|
|
192
|
+
* @notice Validates permissions for the calling function (msg.sig) and handler selector from metaTx
|
|
193
|
+
* @notice Uses EXECUTE_META_APPROVE action for permission checking
|
|
194
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
195
|
+
* @notice Protected by ReentrancyGuard to prevent reentrancy attacks
|
|
196
|
+
*/
|
|
197
|
+
function _approveTransactionWithMetaTx(
|
|
198
|
+
EngineBlox.MetaTransaction memory metaTx
|
|
199
|
+
) internal virtual nonReentrant returns (EngineBlox.TxRecord memory) {
|
|
200
|
+
return EngineBlox.txApprovalWithMetaTx(_getSecureState(), metaTx);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @dev Centralized function to cancel a pending transaction
|
|
205
|
+
* @param txId The transaction ID
|
|
206
|
+
* @return The updated transaction record
|
|
207
|
+
* @notice Validates permissions for the calling function (cancellation function selector), not the execution selector
|
|
208
|
+
* @notice Execution functions are internal-only and don't need permission definitions
|
|
209
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
210
|
+
*/
|
|
211
|
+
function _cancelTransaction(
|
|
212
|
+
uint256 txId
|
|
213
|
+
) internal virtual returns (EngineBlox.TxRecord memory) {
|
|
214
|
+
return EngineBlox.txCancellation(_getSecureState(), txId, bytes4(msg.sig));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @dev Centralized function to cancel a transaction using meta-transaction
|
|
219
|
+
* @param metaTx The meta-transaction
|
|
220
|
+
* @return The updated transaction record
|
|
221
|
+
* @notice Validates permissions for the calling function (msg.sig) and handler selector from metaTx
|
|
222
|
+
* @notice Uses EXECUTE_META_CANCEL action for permission checking
|
|
223
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
224
|
+
*/
|
|
225
|
+
function _cancelTransactionWithMetaTx(
|
|
226
|
+
EngineBlox.MetaTransaction memory metaTx
|
|
227
|
+
) internal virtual returns (EngineBlox.TxRecord memory) {
|
|
228
|
+
return EngineBlox.txCancellationWithMetaTx(_getSecureState(), metaTx);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @dev Centralized function to request and approve a transaction using meta-transaction
|
|
233
|
+
* @param metaTx The meta-transaction
|
|
234
|
+
* @return The transaction record
|
|
235
|
+
* @notice Validates permissions for the calling function (msg.sig) and handler selector from metaTx
|
|
236
|
+
* @notice Uses EXECUTE_META_REQUEST_AND_APPROVE action for permission checking
|
|
237
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
238
|
+
* @notice Protected by ReentrancyGuard to prevent reentrancy attacks
|
|
239
|
+
*/
|
|
240
|
+
function _requestAndApproveTransaction(
|
|
241
|
+
EngineBlox.MetaTransaction memory metaTx
|
|
242
|
+
) internal virtual nonReentrant returns (EngineBlox.TxRecord memory) {
|
|
243
|
+
return EngineBlox.requestAndApprove(_getSecureState(), metaTx);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @dev Centralized function to update payment details for a pending transaction
|
|
248
|
+
* @param txId The transaction ID to update payment for
|
|
249
|
+
* @param paymentDetails The new payment details
|
|
250
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
251
|
+
*/
|
|
252
|
+
function _updatePaymentForTransaction(
|
|
253
|
+
uint256 txId,
|
|
254
|
+
EngineBlox.PaymentDetails memory paymentDetails
|
|
255
|
+
) internal virtual returns (EngineBlox.TxRecord memory) {
|
|
256
|
+
EngineBlox.updatePaymentForTransaction(_getSecureState(), txId, paymentDetails);
|
|
257
|
+
return _secureState.getTxRecord(txId);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ============ META-TRANSACTION UTILITIES ============
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @dev Creates meta-transaction parameters with specified values
|
|
264
|
+
* @param handlerContract The contract that will handle the meta-transaction
|
|
265
|
+
* @param handlerSelector The function selector for the handler
|
|
266
|
+
* @param action The transaction action type
|
|
267
|
+
* @param deadline The timestamp after which the meta-transaction expires
|
|
268
|
+
* @param maxGasPrice The maximum gas price allowed for execution
|
|
269
|
+
* @param signer The address that will sign the meta-transaction
|
|
270
|
+
* @return The formatted meta-transaction parameters
|
|
271
|
+
*/
|
|
272
|
+
function createMetaTxParams(
|
|
273
|
+
address handlerContract,
|
|
274
|
+
bytes4 handlerSelector,
|
|
275
|
+
EngineBlox.TxAction action,
|
|
276
|
+
uint256 deadline,
|
|
277
|
+
uint256 maxGasPrice,
|
|
278
|
+
address signer
|
|
279
|
+
) public view returns (EngineBlox.MetaTxParams memory) {
|
|
280
|
+
return EngineBlox.createMetaTxParams(
|
|
281
|
+
handlerContract,
|
|
282
|
+
handlerSelector,
|
|
283
|
+
action,
|
|
284
|
+
deadline,
|
|
285
|
+
maxGasPrice,
|
|
286
|
+
signer
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @dev Generates an unsigned meta-transaction for a new operation
|
|
292
|
+
* @param requester The address requesting the operation
|
|
293
|
+
* @param target The target contract address
|
|
294
|
+
* @param value The ETH value to send
|
|
295
|
+
* @param gasLimit The gas limit for execution
|
|
296
|
+
* @param operationType The type of operation
|
|
297
|
+
* @param executionSelector The function selector to execute (NATIVE_TRANSFER_SELECTOR for simple native token transfers)
|
|
298
|
+
* @param executionParams The encoded parameters for the function (empty for simple native token transfers)
|
|
299
|
+
* @param metaTxParams The meta-transaction parameters
|
|
300
|
+
* @return The unsigned meta-transaction
|
|
301
|
+
*/
|
|
302
|
+
function generateUnsignedMetaTransactionForNew(
|
|
303
|
+
address requester,
|
|
304
|
+
address target,
|
|
305
|
+
uint256 value,
|
|
306
|
+
uint256 gasLimit,
|
|
307
|
+
bytes32 operationType,
|
|
308
|
+
bytes4 executionSelector,
|
|
309
|
+
bytes memory executionParams,
|
|
310
|
+
EngineBlox.MetaTxParams memory metaTxParams
|
|
311
|
+
) public view returns (EngineBlox.MetaTransaction memory) {
|
|
312
|
+
EngineBlox.TxParams memory txParams = EngineBlox.TxParams({
|
|
313
|
+
requester: requester,
|
|
314
|
+
target: target,
|
|
315
|
+
value: value,
|
|
316
|
+
gasLimit: gasLimit,
|
|
317
|
+
operationType: operationType,
|
|
318
|
+
executionSelector: executionSelector,
|
|
319
|
+
executionParams: executionParams
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return _secureState.generateUnsignedForNewMetaTx(txParams, metaTxParams);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @dev Generates an unsigned meta-transaction for an existing transaction
|
|
327
|
+
* @param txId The ID of the existing transaction
|
|
328
|
+
* @param metaTxParams The meta-transaction parameters
|
|
329
|
+
* @return The unsigned meta-transaction
|
|
330
|
+
*/
|
|
331
|
+
function generateUnsignedMetaTransactionForExisting(
|
|
332
|
+
uint256 txId,
|
|
333
|
+
EngineBlox.MetaTxParams memory metaTxParams
|
|
334
|
+
) public view returns (EngineBlox.MetaTransaction memory) {
|
|
335
|
+
return _secureState.generateUnsignedForExistingMetaTx(txId, metaTxParams);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ============ STATE QUERIES ============
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @dev Gets transaction history within a specified range
|
|
342
|
+
* @param fromTxId The starting transaction ID (inclusive)
|
|
343
|
+
* @param toTxId The ending transaction ID (inclusive)
|
|
344
|
+
* @return The transaction history within the specified range
|
|
345
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
346
|
+
*/
|
|
347
|
+
function getTransactionHistory(uint256 fromTxId, uint256 toTxId) public view returns (EngineBlox.TxRecord[] memory) {
|
|
348
|
+
_validateAnyRole();
|
|
349
|
+
|
|
350
|
+
// Validate the range
|
|
351
|
+
fromTxId = fromTxId > 0 ? fromTxId : 1;
|
|
352
|
+
toTxId = toTxId > _secureState.txCounter ? _secureState.txCounter : toTxId;
|
|
353
|
+
|
|
354
|
+
// Validate that fromTxId is less than toTxId
|
|
355
|
+
SharedValidation.validateLessThan(fromTxId, toTxId);
|
|
356
|
+
|
|
357
|
+
uint256 rangeSize = toTxId - fromTxId + 1;
|
|
358
|
+
|
|
359
|
+
// For larger ranges, use paginated version
|
|
360
|
+
SharedValidation.validateRangeSize(rangeSize, 1000);
|
|
361
|
+
|
|
362
|
+
EngineBlox.TxRecord[] memory history = new EngineBlox.TxRecord[](rangeSize);
|
|
363
|
+
|
|
364
|
+
for (uint256 i = 0; i < rangeSize; i++) {
|
|
365
|
+
history[i] = _secureState.getTxRecord(fromTxId + i);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return history;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @dev Gets a transaction by ID
|
|
373
|
+
* @param txId The transaction ID
|
|
374
|
+
* @return The transaction record
|
|
375
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
376
|
+
*/
|
|
377
|
+
function getTransaction(uint256 txId) public view returns (EngineBlox.TxRecord memory) {
|
|
378
|
+
_validateAnyRole();
|
|
379
|
+
return _secureState.getTxRecord(txId);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @dev Gets all pending transaction IDs
|
|
384
|
+
* @return Array of pending transaction IDs
|
|
385
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
386
|
+
*/
|
|
387
|
+
function getPendingTransactions() public view returns (uint256[] memory) {
|
|
388
|
+
_validateAnyRole();
|
|
389
|
+
return _secureState.getPendingTransactionsList();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ============ ROLE AND PERMISSION QUERIES ============
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @dev Gets the basic role information by its hash
|
|
396
|
+
* @param roleHash The hash of the role to get
|
|
397
|
+
* @return roleName The name of the role
|
|
398
|
+
* @return roleHashReturn The hash of the role
|
|
399
|
+
* @return maxWallets The maximum number of wallets allowed for this role
|
|
400
|
+
* @return walletCount The current number of wallets assigned to this role
|
|
401
|
+
* @return isProtected Whether the role is protected from removal
|
|
402
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
403
|
+
*/
|
|
404
|
+
function getRole(bytes32 roleHash) public view returns (
|
|
405
|
+
string memory roleName,
|
|
406
|
+
bytes32 roleHashReturn,
|
|
407
|
+
uint256 maxWallets,
|
|
408
|
+
uint256 walletCount,
|
|
409
|
+
bool isProtected
|
|
410
|
+
) {
|
|
411
|
+
_validateAnyRole();
|
|
412
|
+
EngineBlox.Role storage role = _secureState.getRole(roleHash);
|
|
413
|
+
return (
|
|
414
|
+
role.roleName,
|
|
415
|
+
role.roleHash,
|
|
416
|
+
role.maxWallets,
|
|
417
|
+
role.walletCount,
|
|
418
|
+
role.isProtected
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @dev Returns if a wallet is authorized for a role
|
|
424
|
+
* @param roleHash The hash of the role to check
|
|
425
|
+
* @param wallet The wallet address to check
|
|
426
|
+
* @return True if the wallet is authorized for the role, false otherwise
|
|
427
|
+
*/
|
|
428
|
+
function hasRole(bytes32 roleHash, address wallet) public view returns (bool) {
|
|
429
|
+
return _secureState.hasRole(roleHash, wallet);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @dev Gets all roles assigned to a wallet
|
|
434
|
+
* @param wallet The wallet address to get roles for
|
|
435
|
+
* @return Array of role hashes assigned to the wallet
|
|
436
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
437
|
+
* @notice This function uses the reverse index for efficient lookup
|
|
438
|
+
*/
|
|
439
|
+
function getWalletRoles(address wallet) public view returns (bytes32[] memory) {
|
|
440
|
+
_validateAnyRole();
|
|
441
|
+
return EngineBlox.getWalletRoles(_getSecureState(), wallet);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @dev Checks if a function schema exists
|
|
446
|
+
* @param functionSelector The function selector to check
|
|
447
|
+
* @return True if the function schema exists, false otherwise
|
|
448
|
+
*/
|
|
449
|
+
function functionSchemaExists(bytes4 functionSelector) public view returns (bool) {
|
|
450
|
+
return _secureState.functions[functionSelector].functionSelector == functionSelector;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @dev Returns if an action is supported by a function
|
|
455
|
+
* @param functionSelector The function selector to check
|
|
456
|
+
* @param action The action to check
|
|
457
|
+
* @return True if the action is supported by the function, false otherwise
|
|
458
|
+
*/
|
|
459
|
+
function isActionSupportedByFunction(bytes4 functionSelector, EngineBlox.TxAction action) public view returns (bool) {
|
|
460
|
+
return _secureState.isActionSupportedByFunction(functionSelector, action);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* @dev Gets the function permissions for a specific role
|
|
465
|
+
* @param roleHash The hash of the role to get permissions for
|
|
466
|
+
* @return The function permissions array for the role
|
|
467
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
468
|
+
*/
|
|
469
|
+
function getActiveRolePermissions(bytes32 roleHash) public view returns (EngineBlox.FunctionPermission[] memory) {
|
|
470
|
+
_validateAnyRole();
|
|
471
|
+
return _secureState.getRoleFunctionPermissions(roleHash);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* @dev Gets the current nonce for a specific signer
|
|
476
|
+
* @param signer The address of the signer
|
|
477
|
+
* @return The current nonce for the signer
|
|
478
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
479
|
+
*/
|
|
480
|
+
function getSignerNonce(address signer) public view returns (uint256) {
|
|
481
|
+
_validateAnyRole();
|
|
482
|
+
return _secureState.getSignerNonce(signer);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ============ SYSTEM STATE QUERIES ============
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* @dev Returns the supported operation types
|
|
489
|
+
* @return The supported operation types
|
|
490
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
491
|
+
*/
|
|
492
|
+
function getSupportedOperationTypes() public view returns (bytes32[] memory) {
|
|
493
|
+
_validateAnyRole();
|
|
494
|
+
return _secureState.getSupportedOperationTypesList();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @dev Returns the supported roles list
|
|
499
|
+
* @return The supported roles list
|
|
500
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
501
|
+
*/
|
|
502
|
+
function getSupportedRoles() public view returns (bytes32[] memory) {
|
|
503
|
+
_validateAnyRole();
|
|
504
|
+
return _secureState.getSupportedRolesList();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @dev Returns the supported functions list
|
|
509
|
+
* @return The supported functions list
|
|
510
|
+
* @notice Requires caller to have any role (via _validateAnyRole) to limit information visibility
|
|
511
|
+
*/
|
|
512
|
+
function getSupportedFunctions() public view returns (bytes4[] memory) {
|
|
513
|
+
_validateAnyRole();
|
|
514
|
+
return _secureState.getSupportedFunctionsList();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* @dev Returns the time lock period
|
|
519
|
+
* @return The time lock period in seconds
|
|
520
|
+
*/
|
|
521
|
+
function getTimeLockPeriodSec() public view returns (uint256) {
|
|
522
|
+
return _secureState.timeLockPeriodSec;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* @dev Returns whether the contract is initialized
|
|
527
|
+
* @return bool True if the contract is initialized, false otherwise
|
|
528
|
+
*/
|
|
529
|
+
function initialized() public view returns (bool) {
|
|
530
|
+
return _getInitializedVersion() != type(uint8).max && _secureState.initialized;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ============ ROLE MANAGEMENT ============
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* @dev Centralized function to get authorized wallet at specific index
|
|
537
|
+
* @param roleHash The role hash
|
|
538
|
+
* @param index The wallet index
|
|
539
|
+
* @return The authorized wallet address
|
|
540
|
+
*/
|
|
541
|
+
function _getAuthorizedWalletAt(bytes32 roleHash, uint256 index) internal view returns (address) {
|
|
542
|
+
return EngineBlox.getAuthorizedWalletAt(_getSecureState(), roleHash, index);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* @dev Centralized function to get all authorized wallets for a role
|
|
547
|
+
* @param roleHash The role hash
|
|
548
|
+
* @return Array of authorized wallet addresses
|
|
549
|
+
*/
|
|
550
|
+
function _getAuthorizedWallets(bytes32 roleHash) internal view returns (address[] memory) {
|
|
551
|
+
EngineBlox.Role storage role = _secureState.roles[roleHash];
|
|
552
|
+
uint256 walletCount = role.walletCount;
|
|
553
|
+
|
|
554
|
+
address[] memory wallets = new address[](walletCount);
|
|
555
|
+
for (uint256 i = 0; i < walletCount; i++) {
|
|
556
|
+
wallets[i] = _getAuthorizedWalletAt(roleHash, i);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return wallets;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @dev Centralized function to create a new role
|
|
564
|
+
* @param roleName The name of the role
|
|
565
|
+
* @param maxWallets The maximum number of wallets allowed for this role
|
|
566
|
+
* @param isProtected Whether the role is protected from removal
|
|
567
|
+
* @return roleHash The hash of the created role
|
|
568
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
569
|
+
*/
|
|
570
|
+
function _createRole(
|
|
571
|
+
string memory roleName,
|
|
572
|
+
uint256 maxWallets,
|
|
573
|
+
bool isProtected
|
|
574
|
+
) internal virtual returns (bytes32) {
|
|
575
|
+
bytes32 roleHash = keccak256(bytes(roleName));
|
|
576
|
+
EngineBlox.createRole(_getSecureState(), roleName, maxWallets, isProtected);
|
|
577
|
+
return roleHash;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* @dev Centralized function to remove a role
|
|
582
|
+
* @param roleHash The hash of the role to remove
|
|
583
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
584
|
+
*/
|
|
585
|
+
function _removeRole(bytes32 roleHash) internal virtual {
|
|
586
|
+
EngineBlox.removeRole(_getSecureState(), roleHash);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* @dev Centralized function to assign a wallet to a role
|
|
591
|
+
* @param roleHash The role hash
|
|
592
|
+
* @param wallet The wallet address to assign
|
|
593
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
594
|
+
*/
|
|
595
|
+
function _assignWallet(bytes32 roleHash, address wallet) internal virtual {
|
|
596
|
+
EngineBlox.assignWallet(_getSecureState(), roleHash, wallet);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* @dev Centralized function to revoke a wallet from a role
|
|
601
|
+
* @param roleHash The role hash
|
|
602
|
+
* @param wallet The wallet address to revoke
|
|
603
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
604
|
+
*/
|
|
605
|
+
function _revokeWallet(bytes32 roleHash, address wallet) internal virtual {
|
|
606
|
+
EngineBlox.revokeWallet(_getSecureState(), roleHash, wallet);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* @dev Centralized function to update assigned wallet for a role
|
|
611
|
+
* @param roleHash The role hash
|
|
612
|
+
* @param newWallet The new wallet address
|
|
613
|
+
* @param oldWallet The old wallet address
|
|
614
|
+
* @notice This function is virtual to allow extensions to add hook functionality or additional validation
|
|
615
|
+
*/
|
|
616
|
+
function _updateAssignedWallet(bytes32 roleHash, address newWallet, address oldWallet) internal virtual {
|
|
617
|
+
EngineBlox.updateAssignedWallet(_getSecureState(), roleHash, newWallet, oldWallet);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* @dev Centralized function to update the time lock period
|
|
622
|
+
* @param newTimeLockPeriodSec The new time lock period in seconds
|
|
623
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
624
|
+
*/
|
|
625
|
+
function _updateTimeLockPeriod(uint256 newTimeLockPeriodSec) internal virtual {
|
|
626
|
+
EngineBlox.updateTimeLockPeriod(_getSecureState(), newTimeLockPeriodSec);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ============ FUNCTION SCHEMA MANAGEMENT ============
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @dev Centralized function to create a function schema
|
|
633
|
+
* @param functionSignature The function signature
|
|
634
|
+
* @param functionSelector The function selector
|
|
635
|
+
* @param operationName The operation name
|
|
636
|
+
* @param supportedActionsBitmap The bitmap of supported actions
|
|
637
|
+
* @param isProtected Whether the function schema is protected
|
|
638
|
+
* @param handlerForSelectors Array of handler selectors
|
|
639
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
640
|
+
*/
|
|
641
|
+
function _createFunctionSchema(
|
|
642
|
+
string memory functionSignature,
|
|
643
|
+
bytes4 functionSelector,
|
|
644
|
+
string memory operationName,
|
|
645
|
+
uint16 supportedActionsBitmap,
|
|
646
|
+
bool isProtected,
|
|
647
|
+
bytes4[] memory handlerForSelectors
|
|
648
|
+
) internal virtual {
|
|
649
|
+
EngineBlox.createFunctionSchema(
|
|
650
|
+
_getSecureState(),
|
|
651
|
+
functionSignature,
|
|
652
|
+
functionSelector,
|
|
653
|
+
operationName,
|
|
654
|
+
supportedActionsBitmap,
|
|
655
|
+
isProtected,
|
|
656
|
+
handlerForSelectors
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* @dev Centralized function to remove a function schema
|
|
662
|
+
* @param functionSelector The function selector to remove
|
|
663
|
+
* @param safeRemoval Whether to perform safe removal (check for role references)
|
|
664
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
665
|
+
*/
|
|
666
|
+
function _removeFunctionSchema(bytes4 functionSelector, bool safeRemoval) internal virtual {
|
|
667
|
+
EngineBlox.removeFunctionSchema(_getSecureState(), functionSelector, safeRemoval);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* @dev Centralized function to add a function permission to a role
|
|
672
|
+
* @param roleHash The role hash
|
|
673
|
+
* @param functionPermission The function permission to add
|
|
674
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
675
|
+
*/
|
|
676
|
+
function _addFunctionToRole(
|
|
677
|
+
bytes32 roleHash,
|
|
678
|
+
EngineBlox.FunctionPermission memory functionPermission
|
|
679
|
+
) internal virtual {
|
|
680
|
+
EngineBlox.addFunctionToRole(_getSecureState(), roleHash, functionPermission);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* @dev Centralized function to remove a function permission from a role
|
|
685
|
+
* @param roleHash The role hash
|
|
686
|
+
* @param functionSelector The function selector to remove
|
|
687
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
688
|
+
*/
|
|
689
|
+
function _removeFunctionFromRole(bytes32 roleHash, bytes4 functionSelector) internal virtual {
|
|
690
|
+
EngineBlox.removeFunctionFromRole(_getSecureState(), roleHash, functionSelector);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// ============ PERMISSION VALIDATION ============
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* @dev Centralized function to validate that the caller has any role
|
|
697
|
+
*/
|
|
698
|
+
function _validateAnyRole() internal view {
|
|
699
|
+
EngineBlox._validateAnyRole(_getSecureState());
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* @dev Centralized function to validate that a role exists
|
|
704
|
+
* @param roleHash The role hash to validate
|
|
705
|
+
*/
|
|
706
|
+
function _validateRoleExists(bytes32 roleHash) internal view {
|
|
707
|
+
EngineBlox._validateRoleExists(_getSecureState(), roleHash);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ============ UTILITY FUNCTIONS ============
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* @dev Centralized function to convert a bitmap to an array of actions
|
|
714
|
+
* @param bitmap The bitmap to convert
|
|
715
|
+
* @return Array of TxAction values
|
|
716
|
+
*/
|
|
717
|
+
function _convertBitmapToActions(uint16 bitmap) internal pure returns (EngineBlox.TxAction[] memory) {
|
|
718
|
+
return EngineBlox.convertBitmapToActions(bitmap);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* @dev Centralized function to create a bitmap from an array of actions
|
|
723
|
+
* @param actions Array of TxAction values
|
|
724
|
+
* @return The bitmap representation
|
|
725
|
+
*/
|
|
726
|
+
function _createBitmapFromActions(EngineBlox.TxAction[] memory actions) internal pure returns (uint16) {
|
|
727
|
+
return EngineBlox.createBitmapFromActions(actions);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// ============ TARGET WHITELIST MANAGEMENT ============
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* @dev Centralized function to add a target address to the whitelist for a function selector
|
|
734
|
+
* @param functionSelector The function selector
|
|
735
|
+
* @param target The target address to whitelist
|
|
736
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
737
|
+
*/
|
|
738
|
+
function _addTargetToFunctionWhitelist(bytes4 functionSelector, address target) internal virtual {
|
|
739
|
+
_getSecureState().addTargetToFunctionWhitelist(functionSelector, target);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* @dev Centralized function to remove a target address from the whitelist for a function selector
|
|
744
|
+
* @param functionSelector The function selector
|
|
745
|
+
* @param target The target address to remove
|
|
746
|
+
* @notice This function is virtual to allow extensions to add hook functionality
|
|
747
|
+
*/
|
|
748
|
+
function _removeTargetFromFunctionWhitelist(bytes4 functionSelector, address target) internal virtual {
|
|
749
|
+
_getSecureState().removeTargetFromFunctionWhitelist(functionSelector, target);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* @dev Centralized function to get all whitelisted targets for a function selector
|
|
754
|
+
* @param functionSelector The function selector
|
|
755
|
+
* @return Array of whitelisted target addresses
|
|
756
|
+
*/
|
|
757
|
+
function _getFunctionWhitelistTargets(bytes4 functionSelector) internal view returns (address[] memory) {
|
|
758
|
+
return _getSecureState().getFunctionWhitelistTargets(functionSelector);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ============ DEFINITION LOADING ============
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* @dev Loads definitions directly into the secure state
|
|
765
|
+
* This function initializes the secure state with all predefined definitions
|
|
766
|
+
* @param functionSchemas Array of function schema definitions
|
|
767
|
+
* @param roleHashes Array of role hashes
|
|
768
|
+
* @param functionPermissions Array of function permissions (parallel to roleHashes)
|
|
769
|
+
*/
|
|
770
|
+
function _loadDefinitions(
|
|
771
|
+
EngineBlox.FunctionSchema[] memory functionSchemas,
|
|
772
|
+
bytes32[] memory roleHashes,
|
|
773
|
+
EngineBlox.FunctionPermission[] memory functionPermissions
|
|
774
|
+
) internal {
|
|
775
|
+
// Load function schemas
|
|
776
|
+
for (uint256 i = 0; i < functionSchemas.length; i++) {
|
|
777
|
+
EngineBlox.createFunctionSchema(
|
|
778
|
+
_getSecureState(),
|
|
779
|
+
functionSchemas[i].functionSignature,
|
|
780
|
+
functionSchemas[i].functionSelector,
|
|
781
|
+
functionSchemas[i].operationName,
|
|
782
|
+
functionSchemas[i].supportedActionsBitmap,
|
|
783
|
+
functionSchemas[i].isProtected,
|
|
784
|
+
functionSchemas[i].handlerForSelectors
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Load role permissions using parallel arrays
|
|
789
|
+
SharedValidation.validateArrayLengthMatch(roleHashes.length, functionPermissions.length);
|
|
790
|
+
for (uint256 i = 0; i < roleHashes.length; i++) {
|
|
791
|
+
EngineBlox.addFunctionToRole(
|
|
792
|
+
_getSecureState(),
|
|
793
|
+
roleHashes[i],
|
|
794
|
+
functionPermissions[i]
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ============ INTERNAL UTILITIES ============
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* @dev Internal function to get the secure state
|
|
803
|
+
* @return secureState The secure state
|
|
804
|
+
*/
|
|
805
|
+
function _getSecureState() internal view returns (EngineBlox.SecureOperationState storage) {
|
|
806
|
+
return _secureState;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* @dev Internal function to check if an address has action permission
|
|
811
|
+
* @param caller The address to check
|
|
812
|
+
* @param functionSelector The function selector
|
|
813
|
+
* @param action The action to check
|
|
814
|
+
* @return True if the caller has permission, false otherwise
|
|
815
|
+
*/
|
|
816
|
+
function _hasActionPermission(
|
|
817
|
+
address caller,
|
|
818
|
+
bytes4 functionSelector,
|
|
819
|
+
EngineBlox.TxAction action
|
|
820
|
+
) internal view returns (bool) {
|
|
821
|
+
return _secureState.hasActionPermission(caller, functionSelector, action);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* @dev Internal helper to validate that a caller has the BROADCASTER_ROLE
|
|
826
|
+
* @param caller The address to validate
|
|
827
|
+
*/
|
|
828
|
+
function _validateBroadcaster(address caller) internal view {
|
|
829
|
+
if (!hasRole(EngineBlox.BROADCASTER_ROLE, caller)) {
|
|
830
|
+
revert SharedValidation.NoPermission(caller);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
}
|