@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.
@@ -0,0 +1,64 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity >=0.8.19 <0.9.0;
3
+
4
+ import { ICoFHEAddressBook } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
5
+
6
+ /**
7
+ * @title MockCoFHEAddressBook
8
+ * @notice Mock of the CoFHEAddressBook: maps a TaskManager id to the TaskManager that FHE.sol
9
+ * releases pinned to that id talk to. Deployed to `COFHE_ADDRESS_BOOK` (from FHE.sol)
10
+ * with `hardhat_setCode` / `deployCodeTo`, so no constructor or initializer runs.
11
+ * @dev The production book is an Ownable2Step UUPS proxy; setters here are unrestricted.
12
+ */
13
+ contract MockCoFHEAddressBook is ICoFHEAddressBook {
14
+ mapping(uint256 id => address tm) private taskManagers;
15
+
16
+ /// @notice Emitted when an id is set or unset (`current == address(0)`).
17
+ /// @param id TaskManager id.
18
+ /// @param previous Address the id resolved to before, zero if it was unset.
19
+ /// @param current Address the id resolves to now, zero if it was unset.
20
+ event TaskManagerSet(uint256 indexed id, address indexed previous, address indexed current);
21
+
22
+ /// @notice Returned when an id resolves to nothing.
23
+ /// @param id TaskManager id.
24
+ error TaskManagerNotSet(uint256 id);
25
+
26
+ /// @notice Returned when setting an id to the zero address.
27
+ error InvalidAddress();
28
+
29
+ function exists() public pure returns (bool) {
30
+ return true;
31
+ }
32
+
33
+ /// @notice Resolves an id to its TaskManager.
34
+ /// @param id TaskManager id.
35
+ /// @return tm The TaskManager address; reverts with {TaskManagerNotSet} when unset.
36
+ function getTm(uint256 id) external view returns (address tm) {
37
+ tm = taskManagers[id];
38
+ if (tm == address(0)) {
39
+ revert TaskManagerNotSet(id);
40
+ }
41
+ }
42
+
43
+ /// @notice Points an id at a TaskManager, replacing any previous target.
44
+ /// @param id TaskManager id.
45
+ /// @param tm TaskManager address.
46
+ function setTm(uint256 id, address tm) external {
47
+ if (tm == address(0)) {
48
+ revert InvalidAddress();
49
+ }
50
+ emit TaskManagerSet(id, taskManagers[id], tm);
51
+ taskManagers[id] = tm;
52
+ }
53
+
54
+ /// @notice Retires an id: every FHE.sol build pinned to it reverts from then on.
55
+ /// @param id TaskManager id.
56
+ function unsetTm(uint256 id) external {
57
+ address previous = taskManagers[id];
58
+ if (previous == address(0)) {
59
+ revert TaskManagerNotSet(id);
60
+ }
61
+ emit TaskManagerSet(id, previous, address(0));
62
+ delete taskManagers[id];
63
+ }
64
+ }
@@ -0,0 +1,13 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity >=0.8.25 <0.9.0;
3
+
4
+ import { ERC1967Proxy } from '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol';
5
+
6
+ /**
7
+ * @notice OpenZeppelin's ERC-1967 proxy under a name of its own (no clash with a project's own
8
+ * `ERC1967Proxy` artifact). The ACP share registry is the production contract, which refuses to
9
+ * be initialized directly, so the mocks deploy it behind this proxy as production does.
10
+ */
11
+ contract MockERC1967Proxy is ERC1967Proxy {
12
+ constructor(address implementation, bytes memory data) payable ERC1967Proxy(implementation, data) {}
13
+ }
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity >=0.8.25 <0.9.0;
3
+
4
+ import { PermissionedUpgradeable } from './Permissioned.sol';
5
+
6
+ /**
7
+ * @notice The ACL's ACP checks, taken unchanged from cofhe-contracts (`PermissionedUpgradeable`
8
+ * in Permissioned.sol), on a contract deployed directly rather than behind a proxy. The EIP-712
9
+ * domain ("ACL", "2") is defined by code there, so it needs no initialization.
10
+ */
11
+ contract MockPermissioned is PermissionedUpgradeable {
12
+ /// @dev Exposed for tests that build typed-data digests.
13
+ function hashTypedDataV4(bytes32 structHash) public view virtual returns (bytes32) {
14
+ return _hashTypedDataV4(structHash);
15
+ }
16
+ }
@@ -31,7 +31,6 @@ error OnlyAggregatorAllowed(address caller);
31
31
 
32
32
  // Operation-specific errors
33
33
  error RandomFunctionNotSupported();
34
- error NotImplemented();
35
34
 
36
35
  library TMCommon {
37
36
  uint256 private constant HASH_MASK_FOR_METADATA = type(uint256).max - type(uint16).max; // 2 bytes reserved for metadata
@@ -249,6 +248,9 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
249
248
  // Signer address for decrypt result verification (threshold network's signing key)
250
249
  address public decryptResultSigner;
251
250
 
251
+ // Counter mixed into generated random seeds, as in the real task manager
252
+ uint256 private randomCounter;
253
+
252
254
  // Storage contract for plaintext results of decrypt operations
253
255
  // PlaintextsStorage public plaintextsStorage;
254
256
 
@@ -263,22 +265,63 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
263
265
  return true;
264
266
  }
265
267
 
266
- function sendEventCreated(uint256 ctHash, string memory operation, uint256[] memory inputs) private {
267
- if (inputs.length == 1 || opIs(operation, FunctionId.cast)) {
268
- emit TaskCreated(ctHash, operation, inputs[0], 0, 0);
268
+ error OnlySelf();
269
269
 
270
- // NOTE: MOCK
271
- MOCK_unaryOperation(ctHash, operation, inputs[0]);
272
- } else if (inputs.length == 2) {
273
- emit TaskCreated(ctHash, operation, inputs[0], inputs[1], 0);
270
+ function sendEventCreated(
271
+ uint256 ctHash,
272
+ FunctionId funcId,
273
+ string memory operation,
274
+ uint256[] memory inputs
275
+ ) private {
276
+ uint8 arity = (inputs.length == 1 || funcId == FunctionId.cast) ? 1 : (inputs.length == 2 ? 2 : 3);
274
277
 
275
- // NOTE: MOCK
276
- MOCK_twoInputOperation(ctHash, operation, inputs[0], inputs[1]);
278
+ if (arity == 1) {
279
+ emit TaskCreated(ctHash, operation, inputs[0], 0, 0);
280
+ } else if (arity == 2) {
281
+ emit TaskCreated(ctHash, operation, inputs[0], inputs[1], 0);
277
282
  } else {
278
283
  emit TaskCreated(ctHash, operation, inputs[0], inputs[1], inputs[2]);
284
+ }
279
285
 
280
- // NOTE: MOCK
281
- MOCK_threeInputOperation(ctHash, operation, inputs[0], inputs[1], inputs[2]);
286
+ // NOTE: MOCK - plaintext replication is mock-only work that doesn't exist in the real
287
+ // task manager, so (under forge, when enabled) it runs with gas metering paused; when
288
+ // metered (hardhat) its cost is reported via MockGasConsumed instead.
289
+ (MockGasMode mode, uint256 startGas) = _mockGasTrackStart();
290
+ if (mode != MockGasMode.PAUSED_BY_MOCK) {
291
+ // MEASURE, or the caller's test already paused metering (nothing of ours to leak
292
+ // on revert in either case).
293
+ _mockDispatch(ctHash, funcId, inputs, arity);
294
+ _mockGasTrackEnd(mode, startGas);
295
+ return;
296
+ }
297
+
298
+ // External self-call so a revert inside the mock op can be caught and gas metering
299
+ // resumed before bubbling the original error - otherwise the paused state leaks into
300
+ // the rest of the test and every later call reports ~0 gas. The extra call itself
301
+ // runs unmetered.
302
+ try this.MOCK_dispatchOperation(ctHash, funcId, inputs, arity) {
303
+ _mockGasTrackEnd(mode, startGas);
304
+ } catch (bytes memory err) {
305
+ _mockGasTrackEnd(mode, startGas);
306
+ assembly ('memory-safe') {
307
+ revert(add(err, 0x20), mload(err))
308
+ }
309
+ }
310
+ }
311
+
312
+ /// @dev Trampoline for `sendEventCreated`'s try/catch; not part of the mocked interface.
313
+ function MOCK_dispatchOperation(uint256 ctHash, FunctionId funcId, uint256[] calldata inputs, uint8 arity) external {
314
+ if (msg.sender != address(this)) revert OnlySelf();
315
+ _mockDispatch(ctHash, funcId, inputs, arity);
316
+ }
317
+
318
+ function _mockDispatch(uint256 ctHash, FunctionId funcId, uint256[] memory inputs, uint8 arity) private {
319
+ if (arity == 1) {
320
+ MOCK_unaryOperation(ctHash, funcId, inputs[0]);
321
+ } else if (arity == 2) {
322
+ MOCK_twoInputOperation(ctHash, funcId, inputs[0], inputs[1]);
323
+ } else {
324
+ MOCK_threeInputOperation(ctHash, funcId, inputs[0], inputs[1], inputs[2]);
282
325
  }
283
326
  }
284
327
 
@@ -299,12 +342,18 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
299
342
  // );
300
343
  // }
301
344
 
302
- // NOTE: MOCK
345
+ // NOTE: MOCK - the real task manager only emits a task event here and the result lands
346
+ // in a later transaction; storing it synchronously is mock-only work. Only the cheap
347
+ // existence check runs before the tracked block (so an InputNotInMockStorage revert
348
+ // can't leak paused gas metering); the read itself is mock-only and stays inside.
349
+ MOCK_verifyKeyInStorage(ctHash);
350
+ (MockGasMode mode, uint256 startGas) = _mockGasTrackStart();
303
351
  _decryptResultReady[ctHash] = true;
304
352
  _decryptResult[ctHash] = _get(ctHash);
305
353
 
306
354
  uint64 asyncOffset = uint64((block.timestamp % 10) + 1);
307
355
  _decryptResultReadyTimestamp[ctHash] = uint64(block.timestamp) + asyncOffset;
356
+ _mockGasTrackEnd(mode, startGas);
308
357
  }
309
358
 
310
359
  function getDecryptResult(uint256 ctHash) public view returns (uint256) {
@@ -399,9 +448,11 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
399
448
  }
400
449
  }
401
450
 
402
- function validateInputs(uint256[] memory encryptedHashes, FunctionId funcId) internal view {
403
- string memory functionName = Utils.functionIdToString(funcId);
404
-
451
+ function validateInputs(
452
+ uint256[] memory encryptedHashes,
453
+ FunctionId funcId,
454
+ string memory functionName
455
+ ) internal view {
405
456
  if (encryptedHashes.length == 0) {
406
457
  if (!isPlaintextOperation(funcId)) {
407
458
  revert InvalidOperationInputs(functionName);
@@ -432,6 +483,32 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
432
483
  validateFunctionInputTypes(funcId, functionName, encryptedHashes);
433
484
  }
434
485
 
486
+ /// @dev Same checks as the real task manager: the plaintext must fit the target type and the
487
+ /// security zone must be in range. Without them `FHE.asEuint8(256)` would be stored masked
488
+ /// to 0 here while the real task manager reverts.
489
+ function validateTrivialEncryptInputs(uint256[] memory extraInputs) internal view {
490
+ uint256 securityZone = extraInputs[2];
491
+ if (securityZone > uint256(int256(type(int32).max)) || !isValidSecurityZone(int32(int256(securityZone)))) {
492
+ revert InvalidSecurityZone(int32(int256(securityZone)), securityZoneMin, securityZoneMax);
493
+ }
494
+
495
+ uint256 value = extraInputs[0];
496
+ uint256 toType = extraInputs[1];
497
+ uint256 max;
498
+ if (toType == Utils.EUINT8_TFHE) max = type(uint8).max;
499
+ else if (toType == Utils.EUINT16_TFHE) max = type(uint16).max;
500
+ else if (toType == Utils.EUINT32_TFHE) max = type(uint32).max;
501
+ else if (toType == Utils.EUINT64_TFHE) max = type(uint64).max;
502
+ else if (toType == Utils.EUINT128_TFHE) max = type(uint128).max;
503
+ else if (toType == Utils.EADDRESS_TFHE) max = type(uint160).max;
504
+ else if (toType == Utils.EBOOL_TFHE) max = 1;
505
+ else return;
506
+
507
+ if (value > max) {
508
+ revert InvalidInputForFunction('trivialEncrypt', uint8(toType));
509
+ }
510
+ }
511
+
435
512
  function validateSelectInputs(uint256[] memory encryptedHashes) internal pure {
436
513
  if (encryptedHashes.length != 3) {
437
514
  revert InvalidInputsAmount('select', encryptedHashes.length, 3);
@@ -455,19 +532,23 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
455
532
  if (funcId == FunctionId.random) {
456
533
  revert RandomFunctionNotSupported();
457
534
  }
535
+ string memory operation = Utils.functionIdToString(funcId);
458
536
  uint256 inputsLength = encryptedHashes.length + extraInputs.length;
459
537
  if (inputsLength > 3) {
460
- revert TooManyInputs(Utils.functionIdToString(funcId), inputsLength, 3);
538
+ revert TooManyInputs(operation, inputsLength, 3);
461
539
  }
462
540
 
463
- validateInputs(encryptedHashes, funcId);
541
+ validateInputs(encryptedHashes, funcId, operation);
542
+ if (funcId == FunctionId.trivialEncrypt) {
543
+ validateTrivialEncryptInputs(extraInputs);
544
+ }
464
545
  uint256[] memory inputs = TMCommon.combineInputs(encryptedHashes, extraInputs);
465
546
 
466
547
  int32 securityZone = getSecurityZone(funcId, encryptedHashes, extraInputs);
467
548
  uint256 ctHash = TMCommon.calcPlaceholderKey(returnType, securityZone, inputs, funcId);
468
549
 
469
550
  acl.allowTransient(ctHash, msg.sender, address(this));
470
- sendEventCreated(ctHash, Utils.functionIdToString(funcId), inputs);
551
+ sendEventCreated(ctHash, funcId, operation, inputs);
471
552
 
472
553
  return ctHash;
473
554
  }
@@ -611,7 +692,7 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
611
692
  acl.allow(ctHash, account, msg.sender);
612
693
 
613
694
  // NOTE: MOCK
614
- MOCK_logAllow(account == msg.sender ? 'FHE.allowThis' : 'FHE.allow', ctHash, account);
695
+ MOCK_trackedLogAllow(account == msg.sender ? 'FHE.allowThis' : 'FHE.allow', ctHash, account);
615
696
  }
616
697
  }
617
698
 
@@ -620,7 +701,7 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
620
701
  acl.allowGlobal(ctHash, msg.sender);
621
702
 
622
703
  // NOTE: MOCK
623
- MOCK_logAllow('FHE.allowGlobal', ctHash, msg.sender);
704
+ MOCK_trackedLogAllow('FHE.allowGlobal', ctHash, msg.sender);
624
705
  }
625
706
  }
626
707
 
@@ -629,7 +710,7 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
629
710
  acl.allowTransient(ctHash, account, msg.sender);
630
711
 
631
712
  // NOTE: MOCK
632
- MOCK_logAllow('FHE.allowTransient', ctHash, account);
713
+ MOCK_trackedLogAllow('FHE.allowTransient', ctHash, account);
633
714
  }
634
715
  }
635
716
 
@@ -640,7 +721,7 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
640
721
  acl.allowForDecryption(hashes, msg.sender);
641
722
 
642
723
  // NOTE: MOCK
643
- MOCK_logAllow('FHE.allowForDecryption', ctHash, msg.sender);
724
+ MOCK_trackedLogAllow('FHE.allowForDecryption', ctHash, msg.sender);
644
725
  }
645
726
  }
646
727
 
@@ -655,14 +736,14 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
655
736
  acl.shareCtHash(ctHash, msg.sender, receiver);
656
737
 
657
738
  // NOTE: MOCK
658
- MOCK_logAllow('FHE.share', ctHash, receiver);
739
+ MOCK_trackedLogAllow('FHE.share', ctHash, receiver);
659
740
  }
660
741
 
661
742
  function receiveCtHash(uint256 ctHash, address expectedSharer) external {
662
743
  acl.receiveCtHash(ctHash, expectedSharer, msg.sender);
663
744
 
664
745
  // NOTE: MOCK
665
- MOCK_logAllow('FHE.receive', ctHash, expectedSharer);
746
+ MOCK_trackedLogAllow('FHE.receive', ctHash, expectedSharer);
666
747
  }
667
748
 
668
749
  /// @dev Per-input message hash used by batch verification:
@@ -763,12 +844,46 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
763
844
  // Stub implementations for new ITaskManager interface methods (inc PR #48)
764
845
 
765
846
  function createRandomTask(uint8 returnType, uint256 seed, int32 securityZone) external returns (uint256) {
766
- // Mock implementation: just return a pseudo-random hash based on seed
767
- return uint256(keccak256(abi.encode(returnType, seed, securityZone, block.timestamp)));
847
+ if (!isValidSecurityZone(securityZone)) {
848
+ revert InvalidSecurityZone(securityZone, securityZoneMin, securityZoneMax);
849
+ }
850
+
851
+ if (seed == 0) {
852
+ seed = uint256(
853
+ keccak256(
854
+ abi.encodePacked(blockhash(block.number - 1), block.timestamp, randomCounter, block.chainid, securityZone)
855
+ )
856
+ );
857
+ unchecked {
858
+ randomCounter++;
859
+ }
860
+ }
861
+
862
+ // Same handle derivation as the real task manager: msg.sender is part of the preimage.
863
+ uint256[] memory inputs = new uint256[](2);
864
+ inputs[0] = seed;
865
+ inputs[1] = uint256(uint160(msg.sender));
866
+
867
+ uint256 ctHash = TMCommon.calcPlaceholderKey(returnType, securityZone, inputs, FunctionId.random);
868
+ acl.allowTransient(ctHash, msg.sender, address(this));
869
+ emit TaskCreated(
870
+ ctHash,
871
+ Utils.functionIdToString(FunctionId.random),
872
+ seed,
873
+ uint256(uint32(securityZone)),
874
+ inputs[1]
875
+ );
876
+
877
+ // NOTE: MOCK - the real task manager leaves the value to the coprocessor; store one here.
878
+ (MockGasMode mode, uint256 startGas) = _mockGasTrackStart();
879
+ _set(ctHash, uint256(keccak256(abi.encode(ctHash))));
880
+ _mockGasTrackEnd(mode, startGas);
881
+
882
+ return ctHash;
768
883
  }
769
884
 
770
885
  function isPubliclyAllowed(uint256 ctHash) external view returns (bool) {
771
- revert NotImplemented();
886
+ return acl.globalAllowed(ctHash);
772
887
  }
773
888
 
774
889
  function verifyDecryptResultBatch(
@@ -9,7 +9,7 @@ pragma solidity >=0.8.19 <0.9.0;
9
9
 
10
10
  import { MockACL } from './MockACL.sol';
11
11
  import { MockTaskManager } from './MockTaskManager.sol';
12
- import { MockPermissioned, ACP } from './Permissioned.sol';
12
+ import { ACP, PermissionedUpgradeable } from './Permissioned.sol';
13
13
 
14
14
  contract MockThresholdNetwork {
15
15
  MockTaskManager public mockTaskManager;
@@ -181,16 +181,16 @@ contract MockThresholdNetwork {
181
181
 
182
182
  function decodeLowLevelReversion(bytes memory data) public pure returns (string memory error) {
183
183
  bytes4 selector = bytes4(data);
184
- if (selector == MockPermissioned.PermissionInvalid_Expired.selector) {
184
+ if (selector == PermissionedUpgradeable.PermissionInvalid_Expired.selector) {
185
185
  return 'PermissionInvalid_Expired';
186
186
  }
187
- if (selector == MockPermissioned.PermissionInvalid_IssuerSignature.selector) {
187
+ if (selector == PermissionedUpgradeable.PermissionInvalid_IssuerSignature.selector) {
188
188
  return 'PermissionInvalid_IssuerSignature';
189
189
  }
190
- if (selector == MockPermissioned.PermissionInvalid_RecipientSignature.selector) {
190
+ if (selector == PermissionedUpgradeable.PermissionInvalid_RecipientSignature.selector) {
191
191
  return 'PermissionInvalid_RecipientSignature';
192
192
  }
193
- if (selector == MockPermissioned.PermissionInvalid_Disabled.selector) {
193
+ if (selector == PermissionedUpgradeable.PermissionInvalid_Disabled.selector) {
194
194
  return 'PermissionInvalid_Disabled';
195
195
  }
196
196
  // Handle other errors
@@ -3,7 +3,7 @@
3
3
 
4
4
  pragma solidity >=0.8.19 <0.9.0;
5
5
 
6
- import { TASK_MANAGER_ADDRESS } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
6
+ import { Common } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
7
7
  import { EncryptedInput } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
8
8
  import { MockTaskManager } from './MockTaskManager.sol';
9
9
 
@@ -104,7 +104,7 @@ contract MockZkVerifier {
104
104
  }
105
105
 
106
106
  function insertCtHash(uint256 ctHash, uint256 value) public {
107
- MockTaskManager(TASK_MANAGER_ADDRESS).MOCK_setInEuintKey(ctHash, value);
107
+ MockTaskManager(address(Common.tm())).MOCK_setInEuintKey(ctHash, value);
108
108
  salt += 1;
109
109
  }
110
110