@cofhe/mock-contracts 0.7.1 → 1.0.0-alpha.0

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.
@@ -2,8 +2,8 @@
2
2
  pragma solidity >=0.8.19 <0.9.0;
3
3
 
4
4
  import { Strings } from '@openzeppelin/contracts/utils/Strings.sol';
5
- import { MockPermissioned, ACP, SCOPE_GLOBAL, SCOPE_CONTRACT, SCOPE_HANDLES } from './Permissioned.sol';
6
- import { TASK_MANAGER_ADDRESS } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
5
+ import { ACP, SCOPE_GLOBAL, SCOPE_CONTRACT, SCOPE_HANDLES } from './Permissioned.sol';
6
+ import { MockPermissioned } from './MockPermissioned.sol';
7
7
 
8
8
  /**
9
9
  * @title ACL
@@ -32,6 +32,9 @@ contract MockACL is MockPermissioned {
32
32
  /// @param receiver Address the share would have been directed at.
33
33
  error NotShared(uint256 handle, address receiver);
34
34
 
35
+ /// @notice Returned when the TaskManager address being set is zero.
36
+ error InvalidTaskManagerAddress();
37
+
35
38
  /// @notice Returned when a share is pending but was written by a different party.
36
39
  /// @param expected Sharer the receiver named.
37
40
  /// @param actual Sharer recorded in the slot.
@@ -53,6 +56,9 @@ contract MockACL is MockPermissioned {
53
56
  /// @notice Emitted when the share registry address is updated (zero = unset).
54
57
  event ShareRegistryUpdated(address oldAddress, address newAddress);
55
58
 
59
+ /// @notice Emitted when the TaskManager address is updated.
60
+ event TaskManagerUpdated(address oldAddress, address newAddress);
61
+
56
62
  /// @custom:storage-location erc7201:cofhe.storage.ACL
57
63
  struct ACLStorage {
58
64
  mapping(uint256 handle => bool isGlobal) globalHandles;
@@ -62,6 +68,7 @@ contract MockACL is MockPermissioned {
62
68
  /// @dev ACP infrastructure addresses served to SDKs (appended fields — do not reorder)
63
69
  address defaultRevokerContract;
64
70
  address shareRegistry;
71
+ address taskManager;
65
72
  }
66
73
 
67
74
  /// @notice Name of the contract.
@@ -76,9 +83,6 @@ contract MockACL is MockPermissioned {
76
83
  /// @notice Patch version of the contract.
77
84
  uint256 private constant PATCH_VERSION = 0;
78
85
 
79
- /// @notice TaskManagerAddress address.
80
- address public constant TASK_MANAGER_ADDRESS_ = TASK_MANAGER_ADDRESS;
81
-
82
86
  /// @dev keccak256(abi.encode(uint256(keccak256("cofhe.storage.ACL")) - 1)) & ~bytes32(uint256(0xff))
83
87
  bytes32 private constant ACL_SLOT =
84
88
  keccak256(abi.encode(uint256(keccak256('cofhe.storage.ACL')) - 1)) & ~bytes32(uint256(0xff));
@@ -103,7 +107,7 @@ contract MockACL is MockPermissioned {
103
107
  * @param requester Address of the account giving the permissions.
104
108
  */
105
109
  function allow(uint256 handle, address account, address requester) public virtual {
106
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
110
+ if (msg.sender != _taskManager()) {
107
111
  revert DirectAllowForbidden(msg.sender);
108
112
  }
109
113
 
@@ -122,7 +126,7 @@ contract MockACL is MockPermissioned {
122
126
  * @param requester Address of the account giving the permissions.
123
127
  */
124
128
  function allowGlobal(uint256 handle, address requester) public virtual {
125
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
129
+ if (msg.sender != _taskManager()) {
126
130
  revert DirectAllowForbidden(msg.sender);
127
131
  }
128
132
 
@@ -140,7 +144,7 @@ contract MockACL is MockPermissioned {
140
144
  * @param handlesList List of handles.
141
145
  */
142
146
  function allowForDecryption(uint256[] memory handlesList, address requester) public virtual {
143
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
147
+ if (msg.sender != _taskManager()) {
144
148
  revert DirectAllowForbidden(msg.sender);
145
149
  }
146
150
 
@@ -167,11 +171,11 @@ contract MockACL is MockPermissioned {
167
171
  * @param requester Address of the requester.
168
172
  */
169
173
  function allowTransient(uint256 handle, address account, address requester) public virtual {
170
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
174
+ if (msg.sender != _taskManager()) {
171
175
  revert DirectAllowForbidden(msg.sender);
172
176
  }
173
177
 
174
- if (!isAllowed(handle, requester) && requester != TASK_MANAGER_ADDRESS_) {
178
+ if (!isAllowed(handle, requester) && requester != _taskManager()) {
175
179
  revert SenderNotAllowed(requester);
176
180
  }
177
181
 
@@ -217,14 +221,14 @@ contract MockACL is MockPermissioned {
217
221
  * @notice Grants `receiver` transient access to `handle` and records `sharer` as the
218
222
  * party that handed it over, for the duration of this transaction.
219
223
  * @dev The caller must be the Task Manager contract.
220
- * @dev Stricter than allowTransient: there is no TASK_MANAGER_ADDRESS bypass, since
224
+ * @dev Stricter than allowTransient: there is no TaskManager bypass, since
221
225
  * nothing shares on the Task Manager's own behalf.
222
226
  * @param handle Handle.
223
227
  * @param sharer Address handing the handle over.
224
228
  * @param receiver Address the handle is being handed to.
225
229
  */
226
230
  function shareCtHash(uint256 handle, address sharer, address receiver) public virtual {
227
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
231
+ if (msg.sender != _taskManager()) {
228
232
  revert DirectAllowForbidden(msg.sender);
229
233
  }
230
234
 
@@ -247,7 +251,7 @@ contract MockACL is MockPermissioned {
247
251
  * @param receiver Address consuming the share.
248
252
  */
249
253
  function receiveCtHash(uint256 handle, address expectedSharer, address receiver) public virtual {
250
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
254
+ if (msg.sender != _taskManager()) {
251
255
  revert DirectAllowForbidden(msg.sender);
252
256
  }
253
257
 
@@ -270,7 +274,7 @@ contract MockACL is MockPermissioned {
270
274
  * @param delegateeContract Delegatee contract.
271
275
  */
272
276
  function delegateAccount(address delegatee, address delegateeContract) public virtual {
273
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
277
+ if (msg.sender != _taskManager()) {
274
278
  revert DirectAllowForbidden(msg.sender);
275
279
  }
276
280
  if (delegateeContract == msg.sender) {
@@ -328,7 +332,7 @@ contract MockACL is MockPermissioned {
328
332
  * @return taskManagerAddress Address of the TaskManager.
329
333
  */
330
334
  function getTaskManagerAddress() public view virtual returns (address) {
331
- return TASK_MANAGER_ADDRESS_;
335
+ return _taskManager();
332
336
  }
333
337
 
334
338
  /**
@@ -378,7 +382,7 @@ contract MockACL is MockPermissioned {
378
382
  * Account Abstraction when bundling several UserOps calling the TaskManagerCoprocessor.
379
383
  */
380
384
  function cleanTransientStorage() external virtual {
381
- if (msg.sender != TASK_MANAGER_ADDRESS_) {
385
+ if (msg.sender != _taskManager()) {
382
386
  revert DirectAllowForbidden(msg.sender);
383
387
  }
384
388
 
@@ -458,6 +462,21 @@ contract MockACL is MockPermissioned {
458
462
  $.shareRegistry = newAddress;
459
463
  }
460
464
 
465
+ /// @notice Sets the TaskManager allowed to drive this ACL (unrestricted in the mock;
466
+ /// the production setter is onlyRole(DEFAULT_ADMIN_ROLE)).
467
+ function setTaskManager(address newAddress) external {
468
+ if (newAddress == address(0)) {
469
+ revert InvalidTaskManagerAddress();
470
+ }
471
+ ACLStorage storage $ = _getACLStorage();
472
+ emit TaskManagerUpdated($.taskManager, newAddress);
473
+ $.taskManager = newAddress;
474
+ }
475
+
476
+ function _taskManager() private view returns (address) {
477
+ return _getACLStorage().taskManager;
478
+ }
479
+
461
480
  /// @notice V3 (ACP) access check — the scope table.
462
481
  ///
463
482
  /// | condition | result |
@@ -6,6 +6,11 @@ import { FHE } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
6
6
  import { FunctionId, Utils } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
7
7
  import { console } from 'hardhat/console.sol';
8
8
 
9
+ /// @dev Foundry/hevm cheatcode address: address(uint160(uint256(keccak256('hevm cheat code')))).
10
+ /// Only reachable when running under forge with cheatcode access granted (vm.allowCheatcodes);
11
+ /// on Hardhat this address has no code and the shim is never enabled.
12
+ address constant CHEATCODE_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
13
+
9
14
  address constant ZK_VERIFIER_SIGNER_ADDRESS = 0x6E12D8C87503D4287c294f2Fdef96ACd9DFf6bd2;
10
15
  uint256 constant ZK_VERIFIER_SIGNER_PRIVATE_KEY = 49099792800763675079532137679706322989817545357788440619111868498148356080914;
11
16
 
@@ -33,9 +38,27 @@ abstract contract MockCoFHE {
33
38
 
34
39
  bool public logOps = true;
35
40
 
41
+ /// @dev When true, mock-only work (plaintext replication + logging) runs between
42
+ /// pauseGasMetering/resumeGasMetering cheatcodes so it is excluded from forge's
43
+ /// gas accounting, giving gas numbers closer to the real CoFHE task manager.
44
+ /// Only enable under forge AFTER granting this contract cheatcode access
45
+ /// (vm.allowCheatcodes(address(taskManager))); must stay false on Hardhat.
46
+ bool public mockGasExcluded = false;
47
+
36
48
  mapping(uint256 => uint256) public mockStorage;
37
49
  mapping(uint256 => bool) public inMockStorage;
38
50
 
51
+ /// @dev Emitted for each block of mock-only work (op replication, decrypt-task storage,
52
+ /// log building) with the gas it consumed, whenever that work runs metered (i.e. not
53
+ /// under forge's paused gas metering). Tooling sums these per transaction to compute
54
+ /// gas usage excluding mock overhead - see the hardhat plugin's getAdjustedGasUsed.
55
+ event MockGasConsumed(uint256 gas);
56
+
57
+ /// @dev Gas spent emitting MockGasConsumed itself (LOG1 + 32 data bytes + bookkeeping).
58
+ /// It lands after the closing gasleft() read, so it is added back as a constant to
59
+ /// keep the reported figure covering the full mock-only cost.
60
+ uint256 internal constant MOCK_GAS_EVENT_COST = 1030;
61
+
39
62
  error InputNotInMockStorage(uint256 ctHash);
40
63
 
41
64
  // Used internally to check if we missed any operations in the mocks
@@ -49,6 +72,59 @@ abstract contract MockCoFHE {
49
72
  logOps = _logOps;
50
73
  }
51
74
 
75
+ function setMockGasExcluded(bool _mockGasExcluded) public {
76
+ mockGasExcluded = _mockGasExcluded;
77
+ }
78
+
79
+ /// @dev How a mock-only block is being accounted for.
80
+ /// MEASURE - metering is live: measure with gasleft() and emit MockGasConsumed.
81
+ /// PAUSED_BY_MOCK - we paused forge's metering and must resume it afterwards.
82
+ /// ALREADY_UNMETERED - metering was already paused by the caller's own test; leave it
83
+ /// untouched (the mock work is unmetered either way) and emit nothing.
84
+ enum MockGasMode {
85
+ MEASURE,
86
+ PAUSED_BY_MOCK,
87
+ ALREADY_UNMETERED
88
+ }
89
+
90
+ /// @dev True while forge's gas metering is paused: gasleft() is frozen, so two consecutive
91
+ /// reads are equal. While metered they differ by the cost of the second GAS opcode.
92
+ /// (Foundry has no query cheatcode for this state.)
93
+ function _isGasMeteringPaused() internal view returns (bool) {
94
+ uint256 a = gasleft();
95
+ uint256 b = gasleft();
96
+ return a == b;
97
+ }
98
+
99
+ /// @dev Opens a mock-only block. Under forge (detected by code at the cheatcode address -
100
+ /// forge plants a marker byte there, other environments have none) with exclusion
101
+ /// enabled, pauses gas metering unless the caller's test already paused it - never
102
+ /// steal a pause we don't own, or `_mockGasTrackEnd` would resume metering out from
103
+ /// under the user's own measurement region. Everywhere else, records gasleft() so the
104
+ /// block's cost can be reported via MockGasConsumed.
105
+ /// Only wrap code that cannot revert - a revert inside the block would skip
106
+ /// `_mockGasTrackEnd` and leak paused metering (see sendEventCreated's trampoline
107
+ /// for the revert-safe variant).
108
+ function _mockGasTrackStart() internal returns (MockGasMode mode, uint256 startGas) {
109
+ if (mockGasExcluded && CHEATCODE_ADDRESS.code.length > 0) {
110
+ if (_isGasMeteringPaused()) return (MockGasMode.ALREADY_UNMETERED, 0);
111
+ (bool ok, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature('pauseGasMetering()'));
112
+ if (ok) return (MockGasMode.PAUSED_BY_MOCK, 0);
113
+ }
114
+ return (MockGasMode.MEASURE, gasleft());
115
+ }
116
+
117
+ /// @dev Closes a mock-only block: resumes metering (if we paused it) or emits the
118
+ /// measured cost. A caller-owned pause is left exactly as we found it.
119
+ function _mockGasTrackEnd(MockGasMode mode, uint256 startGas) internal {
120
+ if (mode == MockGasMode.PAUSED_BY_MOCK) {
121
+ (bool ok, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature('resumeGasMetering()'));
122
+ ok;
123
+ } else if (mode == MockGasMode.MEASURE) {
124
+ emit MockGasConsumed(startGas - gasleft() + MOCK_GAS_EVENT_COST);
125
+ }
126
+ }
127
+
52
128
  // Utils
53
129
 
54
130
  function getUintTypeFromHash(uint256 hash) internal pure returns (uint8) {
@@ -224,76 +300,92 @@ abstract contract MockCoFHE {
224
300
  logAllow(operation, ctHash, account);
225
301
  }
226
302
 
303
+ /// @dev Same as MOCK_logAllow, but the log-string building runs excluded from forge gas
304
+ /// metering / reported via MockGasConsumed, like the rest of the mock-only work.
305
+ /// With logging disabled the whole thing is skipped - the residual cost is negligible.
306
+ /// Safe without the revert trampoline: logAllow only formats bounded strings.
307
+ function MOCK_trackedLogAllow(string memory operation, uint256 ctHash, address account) internal {
308
+ if (!logOps) return;
309
+ (MockGasMode mode, uint256 startGas) = _mockGasTrackStart();
310
+ logAllow(operation, ctHash, account);
311
+ _mockGasTrackEnd(mode, startGas);
312
+ }
313
+
227
314
  // Mock functions
228
315
 
229
316
  function MOCK_verifyKeyInStorage(uint256 ctHash) internal view {
230
317
  if (!inMockStorage[ctHash]) revert InputNotInMockStorage(ctHash);
231
318
  }
232
319
 
233
- function MOCK_unaryOperation(uint256 ctHash, string memory operation, uint256 input) internal {
234
- if (opIs(operation, FunctionId.random)) {
320
+ function MOCK_unaryOperation(uint256 ctHash, FunctionId funcId, uint256 input) internal {
321
+ if (funcId == FunctionId.random) {
235
322
  _set(ctHash, uint256(blockhash(block.number - 1)));
236
- logOperation('FHE.random', '', logCtHash(ctHash));
323
+ if (logOps) logOperation('FHE.random', '', logCtHash(ctHash));
237
324
  return;
238
325
  }
239
- if (opIs(operation, FunctionId.cast)) {
326
+ if (funcId == FunctionId.cast) {
240
327
  _set(ctHash, _get(input));
241
- logOperation('FHE.cast', logCtHash(input), logCtHash(ctHash));
328
+ if (logOps) logOperation('FHE.cast', logCtHash(input), logCtHash(ctHash));
242
329
  return;
243
330
  }
244
- if (opIs(operation, FunctionId.not)) {
245
- bool inputIsTruthy = _get(input) == 1;
246
- _set(ctHash, !inputIsTruthy);
247
- logOperation('FHE.not', logCtHash(input), logCtHash(ctHash));
331
+ if (funcId == FunctionId.not) {
332
+ if (getIsBoolTypeFromHash(ctHash)) {
333
+ bool inputIsTruthy = _get(input) == 1;
334
+ _set(ctHash, !inputIsTruthy);
335
+ } else {
336
+ // bitwise NOT, masked to the type width by _set
337
+ _set(ctHash, ~_get(input));
338
+ }
339
+ if (logOps) logOperation('FHE.not', logCtHash(input), logCtHash(ctHash));
248
340
  return;
249
341
  }
250
- if (opIs(operation, FunctionId.square)) {
342
+ if (funcId == FunctionId.square) {
251
343
  unchecked {
252
344
  _set(ctHash, _get(input) * _get(input));
253
345
  }
254
- logOperation('FHE.square', string.concat(logCtHash(input), ' * ', logCtHash(input)), logCtHash(ctHash));
346
+ if (logOps) logOperation('FHE.square', string.concat(logCtHash(input), ' * ', logCtHash(input)), logCtHash(ctHash));
255
347
  return;
256
348
  }
257
- revert InvalidUnaryOperation(operation);
349
+ revert InvalidUnaryOperation(Utils.functionIdToString(funcId));
258
350
  }
259
351
 
260
- function MOCK_twoInputOperation(uint256 ctHash, string memory operation, uint256 input1, uint256 input2) internal {
261
- if (opIs(operation, FunctionId.sub)) {
352
+ function MOCK_twoInputOperation(uint256 ctHash, FunctionId funcId, uint256 input1, uint256 input2) internal {
353
+ if (funcId == FunctionId.sub) {
262
354
  unchecked {
263
355
  _set(ctHash, _get(input1) - _get(input2));
264
356
  }
265
- logOperation('FHE.sub', string.concat(logCtHash(input1), ' - ', logCtHash(input2)), logCtHash(ctHash));
357
+ if (logOps) logOperation('FHE.sub', string.concat(logCtHash(input1), ' - ', logCtHash(input2)), logCtHash(ctHash));
266
358
  return;
267
359
  }
268
- if (opIs(operation, FunctionId.add)) {
360
+ if (funcId == FunctionId.add) {
269
361
  unchecked {
270
362
  _set(ctHash, _get(input1) + _get(input2));
271
363
  }
272
- logOperation('FHE.add', string.concat(logCtHash(input1), ' + ', logCtHash(input2)), logCtHash(ctHash));
364
+ if (logOps) logOperation('FHE.add', string.concat(logCtHash(input1), ' + ', logCtHash(input2)), logCtHash(ctHash));
273
365
  return;
274
366
  }
275
- if (opIs(operation, FunctionId.xor)) {
367
+ if (funcId == FunctionId.xor) {
276
368
  unchecked {
277
369
  _set(ctHash, _get(input1) ^ _get(input2));
278
370
  }
279
- logOperation('FHE.xor', string.concat(logCtHash(input1), ' ^ ', logCtHash(input2)), logCtHash(ctHash));
371
+ if (logOps) logOperation('FHE.xor', string.concat(logCtHash(input1), ' ^ ', logCtHash(input2)), logCtHash(ctHash));
280
372
  return;
281
373
  }
282
- if (opIs(operation, FunctionId.and)) {
374
+ if (funcId == FunctionId.and) {
283
375
  unchecked {
284
376
  _set(ctHash, _get(input1) & _get(input2));
285
377
  }
286
- logOperation('FHE.and', string.concat(logCtHash(input1), ' & ', logCtHash(input2)), logCtHash(ctHash));
378
+ if (logOps) logOperation('FHE.and', string.concat(logCtHash(input1), ' & ', logCtHash(input2)), logCtHash(ctHash));
287
379
  return;
288
380
  }
289
- if (opIs(operation, FunctionId.or)) {
381
+ if (funcId == FunctionId.or) {
290
382
  unchecked {
291
383
  _set(ctHash, _get(input1) | _get(input2));
292
384
  }
293
- logOperation('FHE.or', string.concat(logCtHash(input1), ' | ', logCtHash(input2)), logCtHash(ctHash));
385
+ if (logOps) logOperation('FHE.or', string.concat(logCtHash(input1), ' | ', logCtHash(input2)), logCtHash(ctHash));
294
386
  return;
295
387
  }
296
- if (opIs(operation, FunctionId.div)) {
388
+ if (funcId == FunctionId.div) {
297
389
  uint256 cleartext2 = _get(input2);
298
390
  if (cleartext2 == 0) {
299
391
  _set(ctHash, type(uint256).max);
@@ -302,143 +394,166 @@ abstract contract MockCoFHE {
302
394
  _set(ctHash, _get(input1) / cleartext2);
303
395
  }
304
396
  }
305
- logOperation('FHE.div', string.concat(logCtHash(input1), ' / ', logCtHash(input2)), logCtHash(ctHash));
397
+ if (logOps) logOperation('FHE.div', string.concat(logCtHash(input1), ' / ', logCtHash(input2)), logCtHash(ctHash));
306
398
  return;
307
399
  }
308
- if (opIs(operation, FunctionId.rem)) {
309
- unchecked {
310
- _set(ctHash, _get(input1) % _get(input2));
400
+ if (funcId == FunctionId.rem) {
401
+ // Like TFHE-rs, the remainder by zero returns the first input unchanged
402
+ uint256 cleartext2 = _get(input2);
403
+ if (cleartext2 == 0) {
404
+ _set(ctHash, _get(input1));
405
+ } else {
406
+ unchecked {
407
+ _set(ctHash, _get(input1) % cleartext2);
408
+ }
311
409
  }
312
- logOperation('FHE.rem', string.concat(logCtHash(input1), ' % ', logCtHash(input2)), logCtHash(ctHash));
410
+ if (logOps) logOperation('FHE.rem', string.concat(logCtHash(input1), ' % ', logCtHash(input2)), logCtHash(ctHash));
313
411
  return;
314
412
  }
315
- if (opIs(operation, FunctionId.mul)) {
413
+ if (funcId == FunctionId.mul) {
316
414
  unchecked {
317
415
  _set(ctHash, _get(input1) * _get(input2));
318
416
  }
319
- logOperation('FHE.mul', string.concat(logCtHash(input1), ' * ', logCtHash(input2)), logCtHash(ctHash));
417
+ if (logOps) logOperation('FHE.mul', string.concat(logCtHash(input1), ' * ', logCtHash(input2)), logCtHash(ctHash));
320
418
  return;
321
419
  }
322
- if (opIs(operation, FunctionId.shl)) {
420
+ if (funcId == FunctionId.shl) {
323
421
  unchecked {
324
422
  _set(ctHash, _get(input1) << _get(input2));
325
423
  }
326
- logOperation('FHE.shl', string.concat(logCtHash(input1), ' << ', logCtHash(input2)), logCtHash(ctHash));
424
+ if (logOps) logOperation('FHE.shl', string.concat(logCtHash(input1), ' << ', logCtHash(input2)), logCtHash(ctHash));
327
425
  return;
328
426
  }
329
- if (opIs(operation, FunctionId.shr)) {
427
+ if (funcId == FunctionId.shr) {
330
428
  unchecked {
331
429
  _set(ctHash, _get(input1) >> _get(input2));
332
430
  }
333
- logOperation('FHE.shr', string.concat(logCtHash(input1), ' >> ', logCtHash(input2)), logCtHash(ctHash));
431
+ if (logOps) logOperation('FHE.shr', string.concat(logCtHash(input1), ' >> ', logCtHash(input2)), logCtHash(ctHash));
334
432
  return;
335
433
  }
336
- if (opIs(operation, FunctionId.gte)) {
434
+ if (funcId == FunctionId.gte) {
337
435
  _set(ctHash, _get(input1) >= _get(input2));
338
- logOperation('FHE.gte', string.concat(logCtHash(input1), ' >= ', logCtHash(input2)), logCtHash(ctHash));
436
+ if (logOps) logOperation('FHE.gte', string.concat(logCtHash(input1), ' >= ', logCtHash(input2)), logCtHash(ctHash));
339
437
  return;
340
438
  }
341
- if (opIs(operation, FunctionId.lte)) {
439
+ if (funcId == FunctionId.lte) {
342
440
  _set(ctHash, _get(input1) <= _get(input2));
343
- logOperation('FHE.lte', string.concat(logCtHash(input1), ' <= ', logCtHash(input2)), logCtHash(ctHash));
441
+ if (logOps) logOperation('FHE.lte', string.concat(logCtHash(input1), ' <= ', logCtHash(input2)), logCtHash(ctHash));
344
442
  return;
345
443
  }
346
- if (opIs(operation, FunctionId.lt)) {
444
+ if (funcId == FunctionId.lt) {
347
445
  _set(ctHash, _get(input1) < _get(input2));
348
- logOperation('FHE.lt', string.concat(logCtHash(input1), ' < ', logCtHash(input2)), logCtHash(ctHash));
446
+ if (logOps) logOperation('FHE.lt', string.concat(logCtHash(input1), ' < ', logCtHash(input2)), logCtHash(ctHash));
349
447
  return;
350
448
  }
351
- if (opIs(operation, FunctionId.gt)) {
449
+ if (funcId == FunctionId.gt) {
352
450
  _set(ctHash, _get(input1) > _get(input2));
353
- logOperation('FHE.gt', string.concat(logCtHash(input1), ' > ', logCtHash(input2)), logCtHash(ctHash));
451
+ if (logOps) logOperation('FHE.gt', string.concat(logCtHash(input1), ' > ', logCtHash(input2)), logCtHash(ctHash));
354
452
  return;
355
453
  }
356
- if (opIs(operation, FunctionId.min)) {
454
+ if (funcId == FunctionId.min) {
357
455
  uint256 min;
358
456
  unchecked {
359
457
  min = _get(input1) < _get(input2) ? _get(input1) : _get(input2);
360
458
  }
361
459
  _set(ctHash, min);
362
460
 
363
- logOperation(
364
- 'FHE.min',
365
- string.concat('min(', logCtHash(input1), ', ', logCtHash(input2), ')'),
366
- logCtHash(ctHash)
367
- );
461
+ if (logOps)
462
+ logOperation(
463
+ 'FHE.min',
464
+ string.concat('min(', logCtHash(input1), ', ', logCtHash(input2), ')'),
465
+ logCtHash(ctHash)
466
+ );
368
467
  return;
369
468
  }
370
- if (opIs(operation, FunctionId.max)) {
469
+ if (funcId == FunctionId.max) {
371
470
  uint256 max;
372
471
  unchecked {
373
472
  max = _get(input1) > _get(input2) ? _get(input1) : _get(input2);
374
473
  }
375
474
  _set(ctHash, max);
376
475
 
377
- logOperation(
378
- 'FHE.max',
379
- string.concat('max(', logCtHash(input1), ', ', logCtHash(input2), ')'),
380
- logCtHash(ctHash)
381
- );
476
+ if (logOps)
477
+ logOperation(
478
+ 'FHE.max',
479
+ string.concat('max(', logCtHash(input1), ', ', logCtHash(input2), ')'),
480
+ logCtHash(ctHash)
481
+ );
382
482
  return;
383
483
  }
384
- if (opIs(operation, FunctionId.eq)) {
484
+ if (funcId == FunctionId.eq) {
385
485
  _set(ctHash, _get(input1) == _get(input2));
386
486
 
387
- logOperation('FHE.eq', string.concat(logCtHash(input1), ' == ', logCtHash(input2)), logCtHash(ctHash));
487
+ if (logOps) logOperation('FHE.eq', string.concat(logCtHash(input1), ' == ', logCtHash(input2)), logCtHash(ctHash));
388
488
  return;
389
489
  }
390
- if (opIs(operation, FunctionId.ne)) {
490
+ if (funcId == FunctionId.ne) {
391
491
  _set(ctHash, _get(input1) != _get(input2));
392
492
 
393
- logOperation('FHE.ne', string.concat(logCtHash(input1), ' != ', logCtHash(input2)), logCtHash(ctHash));
493
+ if (logOps) logOperation('FHE.ne', string.concat(logCtHash(input1), ' != ', logCtHash(input2)), logCtHash(ctHash));
394
494
  return;
395
495
  }
396
- if (opIs(operation, FunctionId.rol)) {
397
- unchecked {
398
- _set(ctHash, _get(input1) << _get(input2));
399
- }
400
-
401
- logOperation('FHE.rol', string.concat(logCtHash(input1), ' << ', logCtHash(input2)), logCtHash(ctHash));
496
+ if (funcId == FunctionId.rol) {
497
+ // rotation within the type width, amount modulo the width (TFHE-rs semantics); _set masks the result
498
+ uint256 bits = getUtypeBits(ctHash);
499
+ uint256 shift = _get(input2) % bits;
500
+ uint256 value = _get(input1);
501
+ _set(ctHash, shift == 0 ? value : (value << shift) | (value >> (bits - shift)));
502
+
503
+ if (logOps)
504
+ logOperation(
505
+ 'FHE.rol',
506
+ string.concat('rol(', logCtHash(input1), ', ', logCtHash(input2), ')'),
507
+ logCtHash(ctHash)
508
+ );
402
509
  return;
403
510
  }
404
- if (opIs(operation, FunctionId.ror)) {
405
- unchecked {
406
- _set(ctHash, _get(input1) >> _get(input2));
407
- }
408
-
409
- logOperation('FHE.ror', string.concat(logCtHash(input1), ' >> ', logCtHash(input2)), logCtHash(ctHash));
511
+ if (funcId == FunctionId.ror) {
512
+ uint256 bits = getUtypeBits(ctHash);
513
+ uint256 shift = _get(input2) % bits;
514
+ uint256 value = _get(input1);
515
+ _set(ctHash, shift == 0 ? value : (value >> shift) | (value << (bits - shift)));
516
+
517
+ if (logOps)
518
+ logOperation(
519
+ 'FHE.ror',
520
+ string.concat('ror(', logCtHash(input1), ', ', logCtHash(input2), ')'),
521
+ logCtHash(ctHash)
522
+ );
410
523
  return;
411
524
  }
412
- revert InvalidTwoInputOperation(operation);
525
+ revert InvalidTwoInputOperation(Utils.functionIdToString(funcId));
413
526
  }
414
527
 
415
528
  function MOCK_threeInputOperation(
416
529
  uint256 ctHash,
417
- string memory operation,
530
+ FunctionId funcId,
418
531
  uint256 input1,
419
532
  uint256 input2,
420
533
  uint256 input3
421
534
  ) internal {
422
- if (opIs(operation, FunctionId.trivialEncrypt)) {
535
+ if (funcId == FunctionId.trivialEncrypt) {
423
536
  _set(ctHash, input1);
424
537
 
425
- logOperation(
426
- string.concat('FHE.asE', removeFirstLetter(getUtypeStringFromHash(ctHash))),
427
- string.concat(removeFirstLetter(getUtypeStringFromHash(ctHash)), '(', Strings.toString(input1), ')'),
428
- logCtHash(ctHash)
429
- );
538
+ if (logOps)
539
+ logOperation(
540
+ string.concat('FHE.asE', removeFirstLetter(getUtypeStringFromHash(ctHash))),
541
+ string.concat(removeFirstLetter(getUtypeStringFromHash(ctHash)), '(', Strings.toString(input1), ')'),
542
+ logCtHash(ctHash)
543
+ );
430
544
  return;
431
545
  }
432
- if (opIs(operation, FunctionId.select)) {
546
+ if (funcId == FunctionId.select) {
433
547
  _set(ctHash, _get(input1) == 1 ? _get(input2) : _get(input3));
434
548
 
435
- logOperation(
436
- 'FHE.select',
437
- string.concat(logCtHash(input1), ' ? ', logCtHash(input2), ' : ', logCtHash(input3)),
438
- logCtHash(ctHash)
439
- );
549
+ if (logOps)
550
+ logOperation(
551
+ 'FHE.select',
552
+ string.concat(logCtHash(input1), ' ? ', logCtHash(input2), ' : ', logCtHash(input3)),
553
+ logCtHash(ctHash)
554
+ );
440
555
  return;
441
556
  }
442
- revert InvalidThreeInputOperation(operation);
557
+ revert InvalidThreeInputOperation(Utils.functionIdToString(funcId));
443
558
  }
444
559
  }