@cofhe/hardhat-plugin 0.1.0 → 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # @cofhe/hardhat-plugin Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a1d1323: Add repository info to package.json of public packages to fix npm publish provenance issue.
8
+ - d232d11: Ensure publish includes correct src and dist files
9
+ - b6521fb: Update publish workflow to create versioning PR upon merge with changeset.
10
+ - Updated dependencies [a1d1323]
11
+ - Updated dependencies [d232d11]
12
+ - Updated dependencies [b6521fb]
13
+ - @cofhe/mock-contracts@0.1.1
14
+ - @cofhe/sdk@0.1.1
15
+
16
+ ## 0.1.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 8d41cf2: Combine existing packages into more reasonable defaults. New package layout is @cofhe/sdk (includes all the core logic for configuring and creating a @cofhe/sdk client, encrypting values, and decrypting handles), mock-contracts, hardhat-plugin, and react.
21
+ - a83facb: Prepare for initial release. Rename scope from `@cofhesdk` to `@cofhe` and rename `cofhesdk` package to `@cofhe/sdk`. Create `publish.yml` to publish `beta` packages on merged PR, and `latest` on changeset PR.
22
+ - 58e93a8: Migrate cofhe-mock-contracts and cofhe-hardhat-plugin into @cofhe/sdk.
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies [cba73fd]
27
+ - Updated dependencies [87fc8a0]
28
+ - Updated dependencies [8d41cf2]
29
+ - Updated dependencies [738b9f5]
30
+ - Updated dependencies [a83facb]
31
+ - Updated dependencies [9a7c98e]
32
+ - Updated dependencies [58e93a8]
33
+ - Updated dependencies [fdf26d4]
34
+ - Updated dependencies [f5b8e25]
35
+ - Updated dependencies [3b135a8]
36
+ - Updated dependencies [5b7c43b]
37
+ - Updated dependencies [4bc8182]
38
+ - @cofhe/sdk@0.1.0
39
+ - @cofhe/mock-contracts@0.1.0
40
+
41
+ This changelog is maintained by Changesets and will be populated on each release.
42
+
43
+ - Do not edit this file by hand.
44
+ - Upcoming changes can be previewed with `pnpm changeset status --verbose`.
45
+ - Entries are generated when the Changesets "Version Packages" PR is created/merged.
package/package.json CHANGED
@@ -1,13 +1,20 @@
1
1
  {
2
2
  "name": "@cofhe/hardhat-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
7
7
  "sideEffects": false,
8
8
  "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/FhenixProtocol/cofhesdk.git",
12
+ "directory": "packages/hardhat-plugin"
13
+ },
9
14
  "files": [
10
- "dist/**"
15
+ "dist/**",
16
+ "src/**",
17
+ "CHANGELOG.md"
11
18
  ],
12
19
  "dependencies": {
13
20
  "@fhenixprotocol/cofhe-contracts": "0.0.13",
@@ -17,8 +24,8 @@
17
24
  "fast-glob": "^3.3.2",
18
25
  "viem": "^2.0.0",
19
26
  "chai": "^4.2.0",
20
- "@cofhe/sdk": "0.1.0",
21
- "@cofhe/mock-contracts": "0.1.0"
27
+ "@cofhe/sdk": "0.1.1",
28
+ "@cofhe/mock-contracts": "0.1.1"
22
29
  },
23
30
  "devDependencies": {
24
31
  "@nomicfoundation/hardhat-ethers": "^3.0.5",
@@ -36,7 +43,7 @@
36
43
  "tsup": "^8.0.2",
37
44
  "typescript": "5.5.4",
38
45
  "@cofhe/tsconfig": "0.1.0",
39
- "@cofhe/eslint-config": "0.1.0"
46
+ "@cofhe/eslint-config": "0.1.1"
40
47
  },
41
48
  "peerDependencies": {
42
49
  "hardhat": "^2.0.0",
package/src/consts.ts ADDED
@@ -0,0 +1,10 @@
1
+ export const TASK_COFHE_USE_FAUCET = 'task:cofhe:usefaucet';
2
+ export const TASK_COFHE_MOCKS_SET_LOG_OPS = 'task:cofhe-mocks:setlogops';
3
+ export const TASK_COFHE_MOCKS_DEPLOY = 'task:cofhe-mocks:deploy';
4
+
5
+ export const TASK_MANAGER_ADDRESS = '0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9';
6
+ export const MOCKS_ACL_ADDRESS = '0x0000000000000000000000000000000000000400';
7
+ export const MOCKS_ZK_VERIFIER_ADDRESS = '0x0000000000000000000000000000000000000100';
8
+ export const MOCKS_QUERY_DECRYPTER_ADDRESS = '0x0000000000000000000000000000000000000200';
9
+ export const TEST_BED_ADDRESS = '0x0000000000000000000000000000000000000300';
10
+ export const MOCKS_ZK_VERIFIER_SIGNER_ADDRESS = '0x6E12D8C87503D4287c294f2Fdef96ACd9DFf6bd2';
package/src/deploy.ts ADDED
@@ -0,0 +1,242 @@
1
+ import { type HardhatRuntimeEnvironment } from 'hardhat/types';
2
+ import chalk from 'chalk';
3
+ import { Contract } from 'ethers';
4
+
5
+ import {
6
+ TASK_MANAGER_ADDRESS,
7
+ MOCKS_ZK_VERIFIER_ADDRESS,
8
+ MOCKS_QUERY_DECRYPTER_ADDRESS,
9
+ TEST_BED_ADDRESS,
10
+ MOCKS_ZK_VERIFIER_SIGNER_ADDRESS,
11
+ MOCKS_ACL_ADDRESS,
12
+ } from './consts.js';
13
+
14
+ import {
15
+ MockTaskManagerArtifact,
16
+ MockACLArtifact,
17
+ MockZkVerifierArtifact,
18
+ MockQueryDecrypterArtifact,
19
+ TestBedArtifact,
20
+ } from '@cofhe/mock-contracts';
21
+
22
+ // Deploy
23
+
24
+ const deployMockTaskManager = async (hre: HardhatRuntimeEnvironment) => {
25
+ const [signer] = await hre.ethers.getSigners();
26
+
27
+ // Deploy MockTaskManager
28
+ await hardhatSetCode(hre, TASK_MANAGER_ADDRESS, MockTaskManagerArtifact.deployedBytecode);
29
+ const taskManager = await hre.ethers.getContractAt(MockTaskManagerArtifact.abi, TASK_MANAGER_ADDRESS);
30
+
31
+ // Initialize MockTaskManager
32
+ const initTx = await taskManager.initialize(signer.address);
33
+ await initTx.wait();
34
+
35
+ // Check if MockTaskManager exists
36
+ const tmExists = await taskManager.exists();
37
+ if (!tmExists) {
38
+ throw new Error('MockTaskManager does not exist');
39
+ }
40
+
41
+ return taskManager;
42
+ };
43
+
44
+ const deployMockACL = async (hre: HardhatRuntimeEnvironment): Promise<Contract> => {
45
+ // Deploy MockACL
46
+ await hardhatSetCode(hre, MOCKS_ACL_ADDRESS, MockACLArtifact.deployedBytecode);
47
+ const acl = await hre.ethers.getContractAt(MockACLArtifact.abi, MOCKS_ACL_ADDRESS);
48
+
49
+ // Check if ACL exists
50
+ const exists = await acl.exists();
51
+ if (!exists) {
52
+ logError('MockACL does not exist', 2);
53
+ throw new Error('MockACL does not exist');
54
+ }
55
+
56
+ return acl;
57
+ };
58
+
59
+ const deployMockZkVerifier = async (hre: HardhatRuntimeEnvironment) => {
60
+ await hardhatSetCode(hre, MOCKS_ZK_VERIFIER_ADDRESS, MockZkVerifierArtifact.deployedBytecode);
61
+ const zkVerifier = await hre.ethers.getContractAt(MockZkVerifierArtifact.abi, MOCKS_ZK_VERIFIER_ADDRESS);
62
+
63
+ const zkVerifierExists = await zkVerifier.exists();
64
+ if (!zkVerifierExists) {
65
+ logError('MockZkVerifier does not exist', 2);
66
+ throw new Error('MockZkVerifier does not exist');
67
+ }
68
+
69
+ return zkVerifier;
70
+ };
71
+
72
+ const deployMockQueryDecrypter = async (hre: HardhatRuntimeEnvironment, acl: Contract) => {
73
+ await hardhatSetCode(hre, MOCKS_QUERY_DECRYPTER_ADDRESS, MockQueryDecrypterArtifact.deployedBytecode);
74
+ const queryDecrypter = await hre.ethers.getContractAt(MockQueryDecrypterArtifact.abi, MOCKS_QUERY_DECRYPTER_ADDRESS);
75
+
76
+ // Initialize MockQueryDecrypter
77
+ const initTx = await queryDecrypter.initialize(TASK_MANAGER_ADDRESS, await acl.getAddress());
78
+ await initTx.wait();
79
+
80
+ // Check if MockQueryDecrypter exists
81
+ const queryDecrypterExists = await queryDecrypter.exists();
82
+ if (!queryDecrypterExists) {
83
+ logError('MockQueryDecrypter does not exist', 2);
84
+ throw new Error('MockQueryDecrypter does not exist');
85
+ }
86
+
87
+ return queryDecrypter;
88
+ };
89
+
90
+ const deployTestBedContract = async (hre: HardhatRuntimeEnvironment) => {
91
+ await hardhatSetCode(hre, TEST_BED_ADDRESS, TestBedArtifact.deployedBytecode);
92
+ const testBed = await hre.ethers.getContractAt(TestBedArtifact.abi, TEST_BED_ADDRESS);
93
+ await testBed.waitForDeployment();
94
+ return testBed;
95
+ };
96
+
97
+ // Funding
98
+
99
+ const fundZkVerifierSigner = async (hre: HardhatRuntimeEnvironment) => {
100
+ const zkVerifierSigner = await hre.ethers.getSigner(MOCKS_ZK_VERIFIER_SIGNER_ADDRESS);
101
+ await hre.network.provider.send('hardhat_setBalance', [
102
+ zkVerifierSigner.address,
103
+ '0x' + hre.ethers.parseEther('10').toString(16),
104
+ ]);
105
+ };
106
+
107
+ // Initializations
108
+
109
+ const setTaskManagerACL = async (taskManager: Contract, acl: Contract) => {
110
+ const setAclTx = await taskManager.setACLContract(await acl.getAddress());
111
+ await setAclTx.wait();
112
+ };
113
+
114
+ export type DeployMocksArgs = {
115
+ deployTestBed?: boolean;
116
+ gasWarning?: boolean;
117
+ silent?: boolean;
118
+ };
119
+
120
+ export const deployMocks = async (
121
+ hre: HardhatRuntimeEnvironment,
122
+ options: DeployMocksArgs = {
123
+ deployTestBed: true,
124
+ gasWarning: true,
125
+ silent: false,
126
+ }
127
+ ) => {
128
+ // Check if network is Hardhat, if not log skip message and return
129
+ const isHardhat = await checkNetworkAndSkip(hre);
130
+ if (!isHardhat) return;
131
+
132
+ const logEmptyIfNoisy = () => {
133
+ if (!options.silent) {
134
+ logEmpty();
135
+ }
136
+ };
137
+ const logSuccessIfNoisy = (message: string, indent = 0) => {
138
+ if (!options.silent) {
139
+ logSuccess(message, indent);
140
+ }
141
+ };
142
+ const logDeploymentIfNoisy = (contractName: string, address: string) => {
143
+ if (!options.silent) {
144
+ logDeployment(contractName, address);
145
+ }
146
+ };
147
+ const logWarningIfNoisy = (message: string, indent = 0) => {
148
+ if (!options.silent) {
149
+ logWarning(message, indent);
150
+ }
151
+ };
152
+
153
+ // Log start message
154
+ logEmptyIfNoisy();
155
+ logSuccessIfNoisy(chalk.bold('cofhe-hardhat-plugin :: deploy mocks'), 0);
156
+ logEmptyIfNoisy();
157
+
158
+ // Compile mock contracts
159
+ logEmptyIfNoisy();
160
+ logSuccessIfNoisy('Mock contracts compiled', 1);
161
+
162
+ // Deploy mock contracts
163
+ const taskManager = await deployMockTaskManager(hre);
164
+ logDeploymentIfNoisy('MockTaskManager', await taskManager.getAddress());
165
+
166
+ const acl = await deployMockACL(hre);
167
+ logDeploymentIfNoisy('MockACL', await acl.getAddress());
168
+
169
+ await setTaskManagerACL(taskManager, acl);
170
+ logSuccessIfNoisy('ACL address set in TaskManager', 2);
171
+
172
+ await fundZkVerifierSigner(hre);
173
+ logSuccessIfNoisy(`ZkVerifier signer (${MOCKS_ZK_VERIFIER_SIGNER_ADDRESS}) funded`, 1);
174
+
175
+ const zkVerifierSignerBalance = await hre.ethers.provider.getBalance(MOCKS_ZK_VERIFIER_SIGNER_ADDRESS);
176
+ logSuccessIfNoisy(`ETH balance: ${zkVerifierSignerBalance.toString()}`, 2);
177
+
178
+ const zkVerifier = await deployMockZkVerifier(hre);
179
+ logDeploymentIfNoisy('MockZkVerifier', await zkVerifier.getAddress());
180
+
181
+ const queryDecrypter = await deployMockQueryDecrypter(hre, acl);
182
+ logDeploymentIfNoisy('MockQueryDecrypter', await queryDecrypter.getAddress());
183
+
184
+ if (options.deployTestBed) {
185
+ logSuccessIfNoisy('TestBed deployment enabled', 2);
186
+ const testBed = await deployTestBedContract(hre);
187
+ logDeploymentIfNoisy('TestBed', await testBed.getAddress());
188
+ }
189
+
190
+ // Log success message
191
+ logEmptyIfNoisy();
192
+ logSuccessIfNoisy(chalk.bold('cofhe-hardhat-plugin :: mocks deployed successfully'), 0);
193
+
194
+ // Log warning about mocks increased gas costs
195
+ if (options.gasWarning) {
196
+ logEmptyIfNoisy();
197
+ logWarningIfNoisy(
198
+ "When using mocks, FHE operations (eg FHE.add / FHE.mul) report a higher gas price due to additional on-chain mocking logic. Deploy your contracts on a testnet chain to check the true gas costs.\n(Disable this warning by setting '@cofhe/sdk.gasWarning' to false in your hardhat config",
199
+ 0
200
+ );
201
+ }
202
+
203
+ logEmptyIfNoisy();
204
+ };
205
+
206
+ // Utils
207
+
208
+ const hardhatSetCode = async (hre: HardhatRuntimeEnvironment, address: string, bytecode: string) => {
209
+ await hre.network.provider.send('hardhat_setCode', [address, bytecode]);
210
+ };
211
+
212
+ // Network
213
+
214
+ const checkNetworkAndSkip = async (hre: HardhatRuntimeEnvironment) => {
215
+ const network = hre.network.name;
216
+ const isHardhat = network === 'hardhat';
217
+ if (!isHardhat) logSuccess(`cofhe-hardhat-plugin - deploy mocks - skipped on non-hardhat network ${network}`, 0);
218
+ return isHardhat;
219
+ };
220
+
221
+ // Logging
222
+
223
+ const logEmpty = () => {
224
+ console.log('');
225
+ };
226
+
227
+ const logSuccess = (message: string, indent = 1) => {
228
+ console.log(chalk.green(`${' '.repeat(indent)}✓ ${message}`));
229
+ };
230
+
231
+ const logWarning = (message: string, indent = 1) => {
232
+ console.log(chalk.bold(chalk.yellow(`${' '.repeat(indent)}⚠ NOTE:`)), message);
233
+ };
234
+
235
+ const logError = (message: string, indent = 1) => {
236
+ console.log(chalk.red(`${' '.repeat(indent)}✗ ${message}`));
237
+ };
238
+
239
+ const logDeployment = (contractName: string, address: string) => {
240
+ const paddedName = `${contractName} deployed`.padEnd(36);
241
+ logSuccess(`${paddedName} ${chalk.bold(address)}`);
242
+ };
@@ -0,0 +1,24 @@
1
+ import { type Result } from '@cofhe/sdk';
2
+ import { expect } from 'chai';
3
+
4
+ export const expectResultError = <T>(result: Result<T>, errorPartial: string) => {
5
+ expect(result.success).to.eq(false, 'Result should be an error');
6
+ expect(result.error).to.include(errorPartial, `Error should contain error partial: ${errorPartial}`);
7
+ };
8
+
9
+ export const expectResultSuccess = <T>(result: Result<T>): T => {
10
+ expect(result.success).to.eq(true, 'Result should be a success');
11
+ return result.data!;
12
+ };
13
+
14
+ export const expectResultValue = <T>(result: Result<T>, value: T): T => {
15
+ expect(result.success).to.eq(true, 'Result should be a success');
16
+ expect(result.data).to.eq(value, `Result should have the expected value ${value}`);
17
+ return result.data!;
18
+ };
19
+
20
+ export const expectResultPartialValue = <T>(result: Result<T>, partial: Partial<T>): T => {
21
+ expect(result.success).to.eq(true, 'Result should be a success');
22
+ expect(result.data).to.include(partial, `Result should have the expected partial ${partial}`);
23
+ return result.data!;
24
+ };
package/src/fund.ts ADDED
@@ -0,0 +1,82 @@
1
+ /* eslint-disable turbo/no-undeclared-env-vars */
2
+ import { type HardhatRuntimeEnvironment } from 'hardhat/types';
3
+ import '@nomicfoundation/hardhat-ethers';
4
+
5
+ /**
6
+ * Sends funds to the specified address
7
+ * @param hre Hardhat Runtime Environment
8
+ * @param toAddress Address to send funds to
9
+ * @param amount Amount to send in ETH (default: 10)
10
+ * @returns Transaction receipt or null if failed
11
+ */
12
+ export async function localcofheFundAccount(hre: HardhatRuntimeEnvironment, toAddress: string, amount: string = '10') {
13
+ // Load private key from environment
14
+ const privateKey =
15
+ process.env.FUNDER_PRIVATE_KEY ?? '0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659';
16
+ if (!privateKey) {
17
+ console.error('Error: FUNDER_PRIVATE_KEY environment variable not set');
18
+ return null;
19
+ }
20
+
21
+ try {
22
+ // Create wallet from private key
23
+ const wallet = new hre.ethers.Wallet(privateKey, hre.ethers.provider);
24
+
25
+ // Get wallet balance
26
+ const balance = await hre.ethers.provider.getBalance(wallet.address);
27
+ console.log(`Funder wallet address: ${wallet.address}`);
28
+ console.log(`Funder wallet balance: ${hre.ethers.formatEther(balance)} ETH`);
29
+
30
+ // Check if wallet has enough funds
31
+ const amountToSend = hre.ethers.parseEther(amount);
32
+ if (balance < amountToSend) {
33
+ console.error(
34
+ `Error: Funder wallet doesn't have enough funds. Current balance: ${hre.ethers.formatEther(balance)} ETH`
35
+ );
36
+ return null;
37
+ }
38
+
39
+ // Send transaction
40
+ console.log(`Sending ${amount} ETH to ${toAddress}...`);
41
+ const tx = await wallet.sendTransaction({
42
+ to: toAddress,
43
+ value: amountToSend,
44
+ });
45
+
46
+ console.log(`Transaction sent! Hash: ${tx.hash}`);
47
+ console.log('Waiting for confirmation...');
48
+
49
+ // Wait for transaction to be mined
50
+ const receipt = await tx.wait();
51
+ console.log(`Transaction confirmed in block ${receipt?.blockNumber}`);
52
+ console.log(`Successfully sent ${amount} ETH to ${toAddress}`);
53
+
54
+ return receipt;
55
+ } catch (error) {
56
+ console.error('Error sending funds:', error);
57
+ return null;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Checks a wallet's balance and funds it if below 1 ETH
63
+ * @param hre Hardhat Runtime Environment
64
+ * @param walletAddress Address of the wallet to check and potentially fund
65
+ * @returns Promise that resolves when the funding operation completes (if needed)
66
+ */
67
+ export async function localcofheFundWalletIfNeeded(hre: HardhatRuntimeEnvironment, walletAddress: string) {
68
+ // Check wallet balance and fund if needed
69
+ const walletBalance = await hre.ethers.provider.getBalance(walletAddress);
70
+ console.log(`Wallet balance: ${hre.ethers.formatEther(walletBalance)} ETH`);
71
+
72
+ if (walletBalance < hre.ethers.parseEther('1')) {
73
+ console.log(`Wallet balance is less than 1 ETH. Funding ${walletAddress}...`);
74
+ const receipt = await localcofheFundAccount(hre, walletAddress);
75
+ if (receipt) {
76
+ const newBalance = await hre.ethers.provider.getBalance(walletAddress);
77
+ console.log(`Wallet new balance: ${hre.ethers.formatEther(newBalance)} ETH`);
78
+ } else {
79
+ console.error(`Failed to fund ${walletAddress}`);
80
+ }
81
+ }
82
+ }
package/src/index.ts ADDED
@@ -0,0 +1,390 @@
1
+ /* eslint-disable no-empty-pattern */
2
+ /* eslint-disable turbo/no-undeclared-env-vars */
3
+ /* eslint-disable no-unused-vars */
4
+ import chalk from 'chalk';
5
+ import { type PublicClient, type WalletClient } from 'viem';
6
+ import { extendConfig, extendEnvironment, task, types } from 'hardhat/config';
7
+ import { TASK_TEST, TASK_NODE } from 'hardhat/builtin-tasks/task-names';
8
+ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
9
+ import { type CofhesdkClient, type CofhesdkConfig, type CofhesdkInputConfig, type Result } from '@cofhe/sdk';
10
+ import { createCofhesdkClient, createCofhesdkConfig } from '@cofhe/sdk/node';
11
+ import { HardhatSignerAdapter } from '@cofhe/sdk/adapters';
12
+
13
+ import { localcofheFundAccount } from './fund.js';
14
+ import {
15
+ MOCKS_ZK_VERIFIER_SIGNER_ADDRESS,
16
+ TASK_COFHE_MOCKS_DEPLOY,
17
+ TASK_COFHE_MOCKS_SET_LOG_OPS,
18
+ TASK_COFHE_USE_FAUCET,
19
+ } from './consts.js';
20
+ import { deployMocks, type DeployMocksArgs } from './deploy.js';
21
+ import { mock_setLoggingEnabled, mock_withLogs } from './logging.js';
22
+ import { mock_expectPlaintext } from './utils.js';
23
+ import { mock_getPlaintext } from './utils.js';
24
+ import {
25
+ expectResultError,
26
+ expectResultPartialValue,
27
+ expectResultSuccess,
28
+ expectResultValue,
29
+ } from './expectResultUtils.js';
30
+ export {
31
+ MockACLArtifact,
32
+ MockQueryDecrypterArtifact,
33
+ MockTaskManagerArtifact,
34
+ MockZkVerifierArtifact,
35
+ TestBedArtifact,
36
+ } from '@cofhe/mock-contracts';
37
+
38
+ /**
39
+ * Configuration interface for the CoFHE Hardhat plugin.
40
+ * Allows users to configure mock logging and gas warning settings.
41
+ */
42
+ declare module 'hardhat/types/config' {
43
+ interface HardhatUserConfig {
44
+ cofhesdk?: {
45
+ /** Whether to log mock operations (default: true) */
46
+ logMocks?: boolean;
47
+ /** Whether to show gas usage warnings for mock operations (default: true) */
48
+ gasWarning?: boolean;
49
+ };
50
+ }
51
+
52
+ interface HardhatConfig {
53
+ cofhesdk: {
54
+ /** Whether to log mock operations (default: true) */
55
+ logMocks: boolean;
56
+ /** Whether to show gas usage warnings for mock operations (default: true) */
57
+ gasWarning: boolean;
58
+ };
59
+ }
60
+ }
61
+
62
+ extendConfig((config, userConfig) => {
63
+ // Allow users to override the localcofhe network config
64
+ if (userConfig.networks && userConfig.networks.localcofhe) {
65
+ return;
66
+ }
67
+
68
+ // Default config
69
+ config.networks.localcofhe = {
70
+ gas: 'auto',
71
+ gasMultiplier: 1.2,
72
+ gasPrice: 'auto',
73
+ timeout: 10_000,
74
+ httpHeaders: {},
75
+ url: 'http://127.0.0.1:42069',
76
+ accounts: [
77
+ '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
78
+ '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',
79
+ '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a',
80
+ '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6',
81
+ ],
82
+ };
83
+
84
+ // Only add Sepolia config if user hasn't defined it
85
+ if (!userConfig.networks?.['eth-sepolia']) {
86
+ config.networks['eth-sepolia'] = {
87
+ url: process.env.SEPOLIA_RPC_URL ?? 'https://ethereum-sepolia.publicnode.com',
88
+ accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
89
+ chainId: 11155111,
90
+ gas: 'auto',
91
+ gasMultiplier: 1.2,
92
+ gasPrice: 'auto',
93
+ timeout: 60_000,
94
+ httpHeaders: {},
95
+ };
96
+ }
97
+
98
+ // Only add Arbitrum Sepolia config if user hasn't defined it
99
+ if (!userConfig.networks?.['arb-sepolia']) {
100
+ config.networks['arb-sepolia'] = {
101
+ url: process.env.ARBITRUM_SEPOLIA_RPC_URL ?? 'https://sepolia-rollup.arbitrum.io/rpc',
102
+ accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
103
+ chainId: 421614,
104
+ gas: 'auto',
105
+ gasMultiplier: 1.2,
106
+ gasPrice: 'auto',
107
+ timeout: 60_000,
108
+ httpHeaders: {},
109
+ };
110
+ }
111
+
112
+ // Add cofhe config
113
+ config.cofhesdk = {
114
+ logMocks: userConfig.cofhesdk?.logMocks ?? true,
115
+ gasWarning: userConfig.cofhesdk?.gasWarning ?? true,
116
+ };
117
+ });
118
+
119
+ type UseFaucetArgs = {
120
+ address?: string;
121
+ };
122
+
123
+ task(TASK_COFHE_USE_FAUCET, 'Fund an account from the funder')
124
+ .addOptionalParam('address', 'Address to fund', undefined, types.string)
125
+ .setAction(async ({ address }: UseFaucetArgs, hre) => {
126
+ const { network } = hre;
127
+ const { name: networkName } = network;
128
+
129
+ if (networkName !== 'localcofhe') {
130
+ console.info(chalk.yellow(`Programmatic faucet only supported for localcofhe`));
131
+ return;
132
+ }
133
+
134
+ if (!address) {
135
+ console.info(chalk.red(`Failed to get address to fund`));
136
+ return;
137
+ }
138
+
139
+ console.info(chalk.green(`Getting funds from faucet for ${address}`));
140
+
141
+ try {
142
+ await localcofheFundAccount(hre, address);
143
+ } catch (e) {
144
+ console.info(chalk.red(`failed to get funds from localcofhe for ${address}: ${e}`));
145
+ }
146
+ });
147
+
148
+ // DEPLOY TASKS
149
+
150
+ task(TASK_COFHE_MOCKS_DEPLOY, 'Deploys the mock contracts on the Hardhat network')
151
+ .addOptionalParam('deployTestBed', 'Whether to deploy the test bed', true, types.boolean)
152
+ .addOptionalParam('silent', 'Whether to suppress output', false, types.boolean)
153
+ .setAction(async ({ deployTestBed, silent }: DeployMocksArgs, hre) => {
154
+ await deployMocks(hre, {
155
+ deployTestBed: deployTestBed ?? true,
156
+ gasWarning: hre.config.cofhesdk.gasWarning ?? true,
157
+ silent: silent ?? false,
158
+ });
159
+ });
160
+
161
+ task(TASK_TEST, 'Deploy mock contracts on hardhat').setAction(async ({}, hre, runSuper) => {
162
+ await deployMocks(hre, {
163
+ deployTestBed: true,
164
+ gasWarning: hre.config.cofhesdk.gasWarning ?? true,
165
+ });
166
+ return runSuper();
167
+ });
168
+
169
+ task(TASK_NODE, 'Deploy mock contracts on hardhat').setAction(async ({}, hre, runSuper) => {
170
+ await deployMocks(hre, {
171
+ deployTestBed: true,
172
+ gasWarning: hre.config.cofhesdk.gasWarning ?? true,
173
+ });
174
+ return runSuper();
175
+ });
176
+
177
+ // SET LOG OPS
178
+
179
+ task(TASK_COFHE_MOCKS_SET_LOG_OPS, 'Set logging for the Mock CoFHE contracts')
180
+ .addParam('enable', 'Whether to enable logging', false, types.boolean)
181
+ .setAction(async ({ enable }, hre) => {
182
+ await mock_setLoggingEnabled(hre, enable);
183
+ });
184
+
185
+ // MOCK UTILS
186
+
187
+ export * from './utils.js';
188
+ export * from './expectResultUtils.js';
189
+ export * from './fund.js';
190
+ export * from './logging.js';
191
+ export * from './deploy.js';
192
+
193
+ /**
194
+ * Runtime environment extensions for the CoFHE Hardhat plugin.
195
+ * Provides access to CoFHE initialization, environment checks, and mock utilities.
196
+ */
197
+ declare module 'hardhat/types/runtime' {
198
+ export interface HardhatRuntimeEnvironment {
199
+ cofhesdk: {
200
+ /**
201
+ * Create a CoFHE SDK configuration for use with cofhesdk.createCofhesdkClient(...)
202
+ * @param {CofhesdkInputConfig} config - The CoFHE SDK input configuration
203
+ * @returns {CofhesdkConfig} The CoFHE SDK configuration
204
+ */
205
+ createCofhesdkConfig: (config: CofhesdkInputConfig) => Promise<CofhesdkConfig>;
206
+ /**
207
+ * Create a CoFHE SDK client instance
208
+ * @param {CofhesdkConfig} config - The CoFHE SDK configuration (use createCofhesdkConfig to create with Node.js defaults)
209
+ * @returns {Promise<CofhesdkClient>} The CoFHE SDK client instance
210
+ */
211
+ createCofhesdkClient: (config: CofhesdkConfig) => CofhesdkClient;
212
+ /**
213
+ * Create viem clients from a Hardhat ethers signer, to be used with `cofhesdkClient.connect(...)`
214
+ * @param {HardhatEthersSigner} signer - The Hardhat ethers signer to use
215
+ * @returns {Promise<{ publicClient: PublicClient; walletClient: WalletClient }>} The viem clients
216
+ */
217
+ hardhatSignerAdapter: (
218
+ signer: HardhatEthersSigner
219
+ ) => Promise<{ publicClient: PublicClient; walletClient: WalletClient }>;
220
+
221
+ /**
222
+ * Assert that a Result type returned from a function is successful and return its value (result.success === true)
223
+ * @param {Result<T>} result - The Result to check
224
+ * @returns {T} The inner data of the Result (non null)
225
+ */
226
+ expectResultSuccess: <T>(result: Result<T> | Promise<Result<T>>) => Promise<T>;
227
+
228
+ /**
229
+ * Assert that a Result type contains an error matching the partial string (result.success === false && result.error.includes(errorPartial))
230
+ * @param {Result<T>} result - The Result to check
231
+ * @param {string} errorPartial - The partial error string to match
232
+ */
233
+ expectResultError: <T>(result: Result<T> | Promise<Result<T>>, errorPartial: string) => Promise<void>;
234
+
235
+ /**
236
+ * Assert that a Result type contains a specific value (result.success === true && result.data === value)
237
+ * @param {Result<T>} result - The Result to check
238
+ * @param {T} value - The inner data of the Result (non null)
239
+ */
240
+ expectResultValue: <T>(result: Result<T> | Promise<Result<T>>, value: T) => Promise<T>;
241
+
242
+ /**
243
+ * Assert that a Result type contains a value matching the partial object (result.success === true && result.data.includes(partial))
244
+ * @param {Result<T>} result - The Result to check
245
+ * @param {Partial<T>} partial - The partial object to match against
246
+ * @returns {T} The inner data of the Result (non null)
247
+ */
248
+ expectResultPartialValue: <T>(result: Result<T> | Promise<Result<T>>, partial: Partial<T>) => Promise<T>;
249
+
250
+ mocks: {
251
+ /**
252
+ * **[MOCKS ONLY]**
253
+ *
254
+ * Execute a block of code with cofhe mock contracts logging enabled.
255
+ *
256
+ * _(If logging only a function, we recommend passing the function name as the closureName (ex "counter.increment()"))_
257
+ *
258
+ * Example usage:
259
+ *
260
+ * ```ts
261
+ * await hre.cofhesdk.mocks.withLogs("counter.increment()", async () => {
262
+ * await counter.increment();
263
+ * });
264
+ * ```
265
+ *
266
+ * Expected output:
267
+ * ```
268
+ * ┌──────────────────┬──────────────────────────────────────────────────
269
+ * │ [COFHE-MOCKS] │ "counter.increment()" logs:
270
+ * ├──────────────────┴──────────────────────────────────────────────────
271
+ * ├ FHE.add | euint32(4473..3424)[0] + euint32(1157..3648)[1] => euint32(1106..1872)[1]
272
+ * ├ FHE.allowThis | euint32(1106..1872)[1] -> 0x663f..6602
273
+ * ├ FHE.allow | euint32(1106..1872)[1] -> 0x3c44..93bc
274
+ * └─────────────────────────────────────────────────────────────────────
275
+ * ```
276
+ * @param {string} closureName - Name of the code block to log within
277
+ * @param {() => Promise<void>} closure - The async function to execute
278
+ */
279
+ withLogs: (closureName: string, closure: () => Promise<void>) => Promise<void>;
280
+
281
+ /**
282
+ * **[MOCKS ONLY]**
283
+ *
284
+ * Enable logging from cofhe mock contracts
285
+ * @param {string} closureName - Optional name of the code block to enable logging for
286
+ */
287
+ enableLogs: (closureName?: string) => Promise<void>;
288
+
289
+ /**
290
+ * **[MOCKS ONLY]**
291
+ *
292
+ * Disable logging from cofhe mock contracts
293
+ */
294
+ disableLogs: () => Promise<void>;
295
+
296
+ /**
297
+ * **[MOCKS ONLY]**
298
+ *
299
+ * Deploy the cofhe mock contracts (normally this is done automatically)
300
+ * @param {DeployMocksArgs} options - Deployment options
301
+ */
302
+ deployMocks: (options: DeployMocksArgs) => Promise<void>;
303
+
304
+ /**
305
+ * **[MOCKS ONLY]**
306
+ *
307
+ * Get the plaintext value for a ciphertext hash
308
+ * @param {bigint} ctHash - The ciphertext hash to look up
309
+ * @returns {Promise<bigint>} The plaintext value
310
+ */
311
+ getPlaintext: (ctHash: bigint) => Promise<bigint>;
312
+
313
+ /**
314
+ * **[MOCKS ONLY]**
315
+ *
316
+ * Assert that a ciphertext hash represents an expected plaintext value
317
+ * @param {bigint} ctHash - The ciphertext hash to check
318
+ * @param {bigint} expectedValue - The expected plaintext value
319
+ */
320
+ expectPlaintext: (ctHash: bigint, expectedValue: bigint) => Promise<void>;
321
+ };
322
+ };
323
+ }
324
+ }
325
+
326
+ extendEnvironment((hre) => {
327
+ hre.cofhesdk = {
328
+ createCofhesdkConfig: async (config: CofhesdkInputConfig) => {
329
+ // Create zkv wallet client
330
+ // This wallet interacts with the MockZkVerifier contract so that the user's connected wallet doesn't have to
331
+ const zkvHhSigner = await hre.ethers.getImpersonatedSigner(MOCKS_ZK_VERIFIER_SIGNER_ADDRESS);
332
+ const { walletClient: zkvWalletClient } = await HardhatSignerAdapter(zkvHhSigner);
333
+
334
+ // Inject zkv wallet client into config
335
+ const configWithZkvWalletClient = {
336
+ ...config,
337
+ _internal: {
338
+ ...config._internal,
339
+ zkvWalletClient,
340
+ },
341
+ };
342
+
343
+ return createCofhesdkConfig(configWithZkvWalletClient);
344
+ },
345
+ createCofhesdkClient: (config: CofhesdkConfig) => {
346
+ return createCofhesdkClient(config);
347
+ },
348
+ hardhatSignerAdapter: async (signer: HardhatEthersSigner) => {
349
+ return HardhatSignerAdapter(signer);
350
+ },
351
+ expectResultSuccess: async <T>(result: Result<T> | Promise<Result<T>>) => {
352
+ const awaitedResult = await result;
353
+ return expectResultSuccess(awaitedResult);
354
+ },
355
+ expectResultError: async <T>(result: Result<T> | Promise<Result<T>>, errorPartial: string) => {
356
+ const awaitedResult = await result;
357
+ return expectResultError(awaitedResult, errorPartial);
358
+ },
359
+ expectResultValue: async <T>(result: Result<T> | Promise<Result<T>>, value: T) => {
360
+ const awaitedResult = await result;
361
+ return expectResultValue(awaitedResult, value);
362
+ },
363
+ expectResultPartialValue: async <T>(result: Result<T> | Promise<Result<T>>, partial: Partial<T>) => {
364
+ const awaitedResult = await result;
365
+ return expectResultPartialValue(awaitedResult, partial);
366
+ },
367
+ mocks: {
368
+ withLogs: async (closureName: string, closure: () => Promise<void>) => {
369
+ return mock_withLogs(hre, closureName, closure);
370
+ },
371
+ enableLogs: async (closureName?: string) => {
372
+ return mock_setLoggingEnabled(hre, true, closureName);
373
+ },
374
+ disableLogs: async () => {
375
+ return mock_setLoggingEnabled(hre, false);
376
+ },
377
+ deployMocks: async (options: DeployMocksArgs) => {
378
+ return deployMocks(hre, options);
379
+ },
380
+ getPlaintext: async (ctHash: bigint) => {
381
+ const [signer] = await hre.ethers.getSigners();
382
+ return mock_getPlaintext(signer.provider, ctHash);
383
+ },
384
+ expectPlaintext: async (ctHash: bigint, expectedValue: bigint) => {
385
+ const [signer] = await hre.ethers.getSigners();
386
+ return mock_expectPlaintext(signer.provider, ctHash, expectedValue);
387
+ },
388
+ },
389
+ };
390
+ });
package/src/logging.ts ADDED
@@ -0,0 +1,75 @@
1
+ import chalk from 'chalk';
2
+ import { type HardhatRuntimeEnvironment } from 'hardhat/types';
3
+ import { TASK_MANAGER_ADDRESS } from './consts';
4
+ import { MockTaskManagerArtifact } from '@cofhe/mock-contracts';
5
+
6
+ const getDeployedMockTaskManager = async (hre: HardhatRuntimeEnvironment) => {
7
+ // Fetch the deployed MockTaskManager
8
+ const taskManager = await hre.ethers.getContractAt(MockTaskManagerArtifact.abi, TASK_MANAGER_ADDRESS);
9
+
10
+ return taskManager;
11
+ };
12
+
13
+ const getLoggingEnabled = async (hre: HardhatRuntimeEnvironment) => {
14
+ const taskManager = await getDeployedMockTaskManager(hre);
15
+ return await taskManager.logOps();
16
+ };
17
+
18
+ const setLoggingEnabled = async (hre: HardhatRuntimeEnvironment, enabled: boolean) => {
19
+ const taskManager = await getDeployedMockTaskManager(hre);
20
+ await taskManager.setLogOps(enabled);
21
+ };
22
+
23
+ // prettier-ignore
24
+ const printLogsEnabledMessage = (closureMessage: string) => {
25
+ console.log("┌──────────────────┬──────────────────────────────────────────────────");
26
+ console.log(`│ [COFHE-MOCKS] │ ${closureMessage}`);
27
+ console.log("├──────────────────┴──────────────────────────────────────────────────");
28
+ };
29
+
30
+ // prettier-ignore
31
+ const printLogsBlockEnd = () => {
32
+ console.log("└─────────────────────────────────────────────────────────────────────");
33
+ };
34
+
35
+ export const mock_setLoggingEnabled = async (
36
+ hre: HardhatRuntimeEnvironment,
37
+ enabled: boolean,
38
+ closureName?: string
39
+ ) => {
40
+ try {
41
+ const initiallyEnabled = await getLoggingEnabled(hre);
42
+
43
+ await setLoggingEnabled(hre, enabled);
44
+
45
+ // Only print if enabling logs
46
+ if (enabled) {
47
+ printLogsEnabledMessage(`${closureName ? `"${chalk.bold(closureName)}" logs:` : 'Logs:'}`);
48
+ }
49
+
50
+ // Only print if disabling logs AND logs currently enabled
51
+ if (!enabled && initiallyEnabled) {
52
+ printLogsBlockEnd();
53
+ }
54
+ } catch (error) {
55
+ console.log(chalk.red('mock_setLoggingEnabled error'), error);
56
+ }
57
+ };
58
+
59
+ export const mock_withLogs = async (
60
+ hre: HardhatRuntimeEnvironment,
61
+ closureName: string,
62
+ closure: () => Promise<void>
63
+ ) => {
64
+ const initiallyEnabled = await getLoggingEnabled(hre);
65
+
66
+ await setLoggingEnabled(hre, true);
67
+ printLogsEnabledMessage(`"${chalk.bold(closureName)}" logs:`);
68
+ await closure();
69
+ printLogsBlockEnd();
70
+
71
+ // If logs were disabled, disable them again
72
+ if (!initiallyEnabled) {
73
+ await setLoggingEnabled(hre, false);
74
+ }
75
+ };
package/src/utils.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { TASK_MANAGER_ADDRESS, MOCKS_ZK_VERIFIER_ADDRESS } from './consts.js';
2
+ import { expect } from 'chai';
3
+ import { ethers } from 'ethers';
4
+ import { type HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider';
5
+
6
+ const mock_checkIsTestnet = async (fnName: string, provider: HardhatEthersProvider | ethers.JsonRpcProvider) => {
7
+ // Testnet is checked by testing if MockZkVerifier is deployed
8
+
9
+ // Get bytecode at ZK_VERIFIER_ADDRESS
10
+ const bytecode = await provider.getCode(MOCKS_ZK_VERIFIER_ADDRESS);
11
+
12
+ // If bytecode is empty, we are on a testnet
13
+ const isTestnet = bytecode.length === 0;
14
+
15
+ // Log if we are on a testnet
16
+ if (isTestnet) {
17
+ console.log(`${fnName} - skipped on non-testnet chain`);
18
+ }
19
+
20
+ return isTestnet;
21
+ };
22
+
23
+ export const mock_getPlaintext = async (provider: HardhatEthersProvider | ethers.JsonRpcProvider, ctHash: bigint) => {
24
+ // Skip with log if called on a non-testnet chain
25
+ if (await mock_checkIsTestnet(mock_getPlaintext.name, provider)) return;
26
+
27
+ // Connect to MockTaskManager
28
+ const taskManager = new ethers.Contract(
29
+ TASK_MANAGER_ADDRESS,
30
+ ['function mockStorage(uint256) view returns (uint256)'],
31
+ provider
32
+ );
33
+
34
+ // Fetch the plaintext
35
+ const plaintext = await taskManager.mockStorage(ctHash);
36
+
37
+ return plaintext;
38
+ };
39
+
40
+ export const mock_getPlaintextExists = async (
41
+ provider: HardhatEthersProvider | ethers.JsonRpcProvider,
42
+ ctHash: bigint
43
+ ) => {
44
+ // Skip with log if called on a non-testnet chain
45
+ if (await mock_checkIsTestnet(mock_getPlaintextExists.name, provider)) return;
46
+
47
+ // Connect to MockTaskManager
48
+ const taskManager = new ethers.Contract(
49
+ TASK_MANAGER_ADDRESS,
50
+ ['function inMockStorage(uint256) view returns (bool)'],
51
+ provider
52
+ );
53
+
54
+ // Fetch the plaintext exists
55
+ const plaintextExists = await taskManager.inMockStorage(ctHash);
56
+
57
+ return plaintextExists;
58
+ };
59
+
60
+ export const mock_expectPlaintext = async (
61
+ provider: HardhatEthersProvider | ethers.JsonRpcProvider,
62
+ ctHash: bigint,
63
+ expectedValue: bigint
64
+ ) => {
65
+ // Skip with log if called on a non-testnet chain
66
+ if (await mock_checkIsTestnet(mock_expectPlaintext.name, provider)) return;
67
+
68
+ // Expect the plaintext to exist
69
+ const plaintextExists = await mock_getPlaintextExists(provider, ctHash);
70
+ expect(plaintextExists).equal(true, 'Plaintext does not exist');
71
+
72
+ // Expect the plaintext to have the expected value
73
+ const plaintext = await mock_getPlaintext(provider, ctHash);
74
+ expect(plaintext).equal(expectedValue, 'Plaintext value is incorrect');
75
+ };