@cofhe/hardhat-3-plugin 0.0.0-alpha-20260409113701
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 +11 -0
- package/README.md +270 -0
- package/dist/chunk-5FCSZD6A.js +223 -0
- package/dist/chunk-IVRNGFNF.js +64 -0
- package/dist/chunk-Z2FAJP4Y.js +42 -0
- package/dist/config-YK3LB5IH.js +64 -0
- package/dist/hre-YCSQIPJL.js +143 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +52 -0
- package/dist/set-log-ops-GVEQWMZP.js +15 -0
- package/dist/use-faucet-O732FNWJ.js +33 -0
- package/package.json +54 -0
- package/src/config.ts +64 -0
- package/src/consts.ts +2 -0
- package/src/deploy.ts +250 -0
- package/src/fund.ts +62 -0
- package/src/hooks/config.ts +13 -0
- package/src/hooks/hre.ts +172 -0
- package/src/index.ts +41 -0
- package/src/logging.ts +84 -0
- package/src/tasks/set-log-ops.ts +17 -0
- package/src/tasks/use-faucet.ts +39 -0
- package/src/type-extensions.ts +107 -0
- package/src/utils.ts +79 -0
package/src/deploy.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { PublicClient, WalletClient } from 'viem';
|
|
2
|
+
import { createTestClient, custom } from 'viem';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
MockTaskManagerArtifact,
|
|
8
|
+
MockACLArtifact,
|
|
9
|
+
MockZkVerifierArtifact,
|
|
10
|
+
MockThresholdNetworkArtifact,
|
|
11
|
+
TestBedArtifact,
|
|
12
|
+
} from '@cofhe/mock-contracts';
|
|
13
|
+
import {
|
|
14
|
+
TASK_MANAGER_ADDRESS,
|
|
15
|
+
MOCKS_ZK_VERIFIER_ADDRESS,
|
|
16
|
+
MOCKS_ZK_VERIFIER_SIGNER_ADDRESS,
|
|
17
|
+
MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY,
|
|
18
|
+
MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY,
|
|
19
|
+
} from '@cofhe/sdk';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Controls deploy-mocks console output.
|
|
23
|
+
* - `''` — silent, no output
|
|
24
|
+
* - `'v'` — single summary line (default)
|
|
25
|
+
* - `'vv'` — full per-contract deployment logs
|
|
26
|
+
*/
|
|
27
|
+
export type LogMocksDeploy = '' | 'v' | 'vv';
|
|
28
|
+
|
|
29
|
+
export type DeployMocksArgs = {
|
|
30
|
+
deployTestBed?: boolean;
|
|
31
|
+
gasWarning?: boolean;
|
|
32
|
+
mocksDeployVerbosity?: LogMocksDeploy;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type DeployContext = {
|
|
36
|
+
publicClient: PublicClient;
|
|
37
|
+
walletClient: WalletClient;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let verbosity: LogMocksDeploy = 'v';
|
|
41
|
+
|
|
42
|
+
// ─── Public entrypoint ────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns true when the provider supports Hardhat-specific RPC methods (i.e.
|
|
46
|
+
* it is an in-process Hardhat / EDR network, or localcofhe).
|
|
47
|
+
*/
|
|
48
|
+
async function isLocalHardhatNetwork(publicClient: PublicClient): Promise<boolean> {
|
|
49
|
+
try {
|
|
50
|
+
await (publicClient as any).request({ method: 'hardhat_metadata', params: [] });
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function deployMocks(ctx: DeployContext, options: DeployMocksArgs = {}): Promise<void> {
|
|
58
|
+
const { deployTestBed = true, gasWarning = true } = options;
|
|
59
|
+
verbosity = options.mocksDeployVerbosity ?? 'v';
|
|
60
|
+
|
|
61
|
+
if (!(await isLocalHardhatNetwork(ctx.publicClient))) {
|
|
62
|
+
log('v', `cofhe-hardhat-3-plugin - deploy mocks - skipped on non-hardhat network`, 0);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
log('vv', chalk.bold('cofhe-hardhat-3-plugin :: deploy mocks'), 0);
|
|
67
|
+
|
|
68
|
+
// 1. Deploy TaskManager to its fixed address
|
|
69
|
+
await deployFixed(ctx.publicClient, MockTaskManagerArtifact);
|
|
70
|
+
logDeployment('MockTaskManager', TASK_MANAGER_ADDRESS);
|
|
71
|
+
|
|
72
|
+
// 2. Initialize TaskManager — owner = first connected account
|
|
73
|
+
const [account] = await ctx.walletClient.getAddresses();
|
|
74
|
+
await ctx.walletClient.writeContract({
|
|
75
|
+
address: TASK_MANAGER_ADDRESS,
|
|
76
|
+
abi: MockTaskManagerArtifact.abi,
|
|
77
|
+
functionName: 'initialize',
|
|
78
|
+
args: [account],
|
|
79
|
+
account,
|
|
80
|
+
chain: null,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// 3. Configure security zones
|
|
84
|
+
await ctx.walletClient.writeContract({
|
|
85
|
+
address: TASK_MANAGER_ADDRESS,
|
|
86
|
+
abi: MockTaskManagerArtifact.abi,
|
|
87
|
+
functionName: 'setSecurityZones',
|
|
88
|
+
args: [0, 1],
|
|
89
|
+
account,
|
|
90
|
+
chain: null,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 4. Deploy MockACL normally so its EIP-712 constructor runs
|
|
94
|
+
const aclAddress = await deployVariable(ctx, MockACLArtifact, []);
|
|
95
|
+
logDeployment('MockACL', aclAddress);
|
|
96
|
+
|
|
97
|
+
// 5. Link ACL into TaskManager
|
|
98
|
+
await ctx.walletClient.writeContract({
|
|
99
|
+
address: TASK_MANAGER_ADDRESS,
|
|
100
|
+
abi: MockTaskManagerArtifact.abi,
|
|
101
|
+
functionName: 'setACLContract',
|
|
102
|
+
args: [aclAddress],
|
|
103
|
+
account,
|
|
104
|
+
chain: null,
|
|
105
|
+
});
|
|
106
|
+
log('vv', 'ACL address set in TaskManager', 2);
|
|
107
|
+
|
|
108
|
+
// 6. Set ZkVerifier signer (the key is well-known and shared with the SDK)
|
|
109
|
+
const verifierSigner = privateKeyToAccount(MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY);
|
|
110
|
+
await ctx.walletClient.writeContract({
|
|
111
|
+
address: TASK_MANAGER_ADDRESS,
|
|
112
|
+
abi: MockTaskManagerArtifact.abi,
|
|
113
|
+
functionName: 'setVerifierSigner',
|
|
114
|
+
args: [verifierSigner.address],
|
|
115
|
+
account,
|
|
116
|
+
chain: null,
|
|
117
|
+
});
|
|
118
|
+
log('vv', 'Verifier signer set', 2);
|
|
119
|
+
|
|
120
|
+
// 7. Set decrypt result signer
|
|
121
|
+
const decryptSigner = privateKeyToAccount(MOCKS_DECRYPT_RESULT_SIGNER_PRIVATE_KEY);
|
|
122
|
+
await ctx.walletClient.writeContract({
|
|
123
|
+
address: TASK_MANAGER_ADDRESS,
|
|
124
|
+
abi: MockTaskManagerArtifact.abi,
|
|
125
|
+
functionName: 'setDecryptResultSigner',
|
|
126
|
+
args: [decryptSigner.address],
|
|
127
|
+
account,
|
|
128
|
+
chain: null,
|
|
129
|
+
});
|
|
130
|
+
log('vv', 'Decrypt result signer set', 2);
|
|
131
|
+
|
|
132
|
+
// 8. Fund the ZkVerifier signer account so it can send transactions.
|
|
133
|
+
// We create a testClient on-the-fly from the publicClient's transport.
|
|
134
|
+
const testClient = createTestClient({
|
|
135
|
+
mode: 'hardhat',
|
|
136
|
+
transport: custom(ctx.publicClient.transport as any),
|
|
137
|
+
});
|
|
138
|
+
await testClient.setBalance({
|
|
139
|
+
address: MOCKS_ZK_VERIFIER_SIGNER_ADDRESS,
|
|
140
|
+
value: BigInt('10000000000000000000'), // 10 ETH
|
|
141
|
+
});
|
|
142
|
+
log('vv', `ZkVerifier signer (${MOCKS_ZK_VERIFIER_SIGNER_ADDRESS}) funded`, 1);
|
|
143
|
+
|
|
144
|
+
const zkVerifierBalance = await ctx.publicClient.getBalance({ address: MOCKS_ZK_VERIFIER_SIGNER_ADDRESS });
|
|
145
|
+
log('vv', `ETH balance: ${zkVerifierBalance.toString()}`, 2);
|
|
146
|
+
|
|
147
|
+
// 9. Deploy MockZkVerifier to its fixed address
|
|
148
|
+
await deployFixed(ctx.publicClient, MockZkVerifierArtifact);
|
|
149
|
+
logDeployment('MockZkVerifier', MOCKS_ZK_VERIFIER_ADDRESS);
|
|
150
|
+
|
|
151
|
+
// 10. Deploy MockThresholdNetwork to its fixed address + initialize
|
|
152
|
+
await deployFixed(ctx.publicClient, MockThresholdNetworkArtifact);
|
|
153
|
+
logDeployment('MockThresholdNetwork', MockThresholdNetworkArtifact.fixedAddress);
|
|
154
|
+
await ctx.walletClient.writeContract({
|
|
155
|
+
address: MockThresholdNetworkArtifact.fixedAddress as `0x${string}`,
|
|
156
|
+
abi: MockThresholdNetworkArtifact.abi,
|
|
157
|
+
functionName: 'initialize',
|
|
158
|
+
args: [TASK_MANAGER_ADDRESS, aclAddress],
|
|
159
|
+
account,
|
|
160
|
+
chain: null,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 11. Optionally deploy TestBed
|
|
164
|
+
if (deployTestBed) {
|
|
165
|
+
await deployFixed(ctx.publicClient, TestBedArtifact);
|
|
166
|
+
logDeployment('TestBed', TestBedArtifact.fixedAddress);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
log('v', chalk.bold('cofhe-hardhat-3-plugin :: mocks deployed'), 0);
|
|
170
|
+
|
|
171
|
+
if (gasWarning) {
|
|
172
|
+
logEmpty('v');
|
|
173
|
+
logWarning(
|
|
174
|
+
'When using mocks, FHE operations report a higher gas price due to on-chain mocking logic. ' +
|
|
175
|
+
'Deploy your contracts on a testnet to check true gas costs.\n' +
|
|
176
|
+
"(Disable this warning by setting 'cofhe.gasWarning: false' in your Hardhat config)",
|
|
177
|
+
0
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
logEmpty('v');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── Deployment helpers ───────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
/** Sets code at a fixed address via hardhat_setCode. */
|
|
187
|
+
async function deployFixed(
|
|
188
|
+
publicClient: PublicClient,
|
|
189
|
+
artifact: { fixedAddress: string; deployedBytecode: string }
|
|
190
|
+
): Promise<void> {
|
|
191
|
+
await (publicClient as any).request({
|
|
192
|
+
method: 'hardhat_setCode',
|
|
193
|
+
params: [artifact.fixedAddress, artifact.deployedBytecode],
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Deploys a variable-address artifact and returns the deployed address. */
|
|
198
|
+
async function deployVariable(
|
|
199
|
+
ctx: DeployContext,
|
|
200
|
+
artifact: { abi: readonly unknown[]; bytecode: string },
|
|
201
|
+
constructorArgs: readonly unknown[]
|
|
202
|
+
): Promise<`0x${string}`> {
|
|
203
|
+
const [account] = await ctx.walletClient.getAddresses();
|
|
204
|
+
|
|
205
|
+
const hash = await ctx.walletClient.deployContract({
|
|
206
|
+
abi: artifact.abi,
|
|
207
|
+
bytecode: artifact.bytecode as `0x${string}`,
|
|
208
|
+
args: constructorArgs,
|
|
209
|
+
account,
|
|
210
|
+
chain: null,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const receipt = await ctx.publicClient.waitForTransactionReceipt({ hash });
|
|
214
|
+
|
|
215
|
+
if (!receipt.contractAddress) {
|
|
216
|
+
throw new Error('deployVariable: no contractAddress in receipt');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return receipt.contractAddress;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Logging ──────────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Emit a green ✓ line at `minVerbosity` or above.
|
|
226
|
+
* - `minVerbosity = 'v'` → prints for both 'v' and 'vv'
|
|
227
|
+
* - `minVerbosity = 'vv'` → prints only for 'vv'
|
|
228
|
+
*/
|
|
229
|
+
const log = (minVerbosity: 'v' | 'vv', message: string, indent = 1) => {
|
|
230
|
+
if (verbosity === '') return;
|
|
231
|
+
if (minVerbosity === 'vv' && verbosity !== 'vv') return;
|
|
232
|
+
console.log(chalk.green(`${' '.repeat(indent)}✓ ${message}`));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const logEmpty = (minVerbosity: 'v' | 'vv' = 'v') => {
|
|
236
|
+
if (verbosity === '') return;
|
|
237
|
+
if (minVerbosity === 'vv' && verbosity !== 'vv') return;
|
|
238
|
+
console.log('');
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const logWarning = (message: string, indent = 1) => {
|
|
242
|
+
if (verbosity === '') return;
|
|
243
|
+
console.log(chalk.bold(chalk.yellow(`${' '.repeat(indent)}⚠ NOTE:`)), message);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const logDeployment = (contractName: string, address: string) => {
|
|
247
|
+
if (verbosity !== 'vv') return;
|
|
248
|
+
const paddedName = `${contractName} deployed`.padEnd(36);
|
|
249
|
+
console.log(chalk.green(` ✓ ${paddedName} ${chalk.bold(address)}`));
|
|
250
|
+
};
|
package/src/fund.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { WalletClient, PublicClient } from 'viem';
|
|
2
|
+
import { parseEther } from 'viem';
|
|
3
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
+
|
|
5
|
+
const FUNDER_PRIVATE_KEY = '0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sends ETH to `toAddress` on the localcofhe network using the funder wallet.
|
|
9
|
+
* The funder private key can be overridden via the FUNDER_PRIVATE_KEY environment variable.
|
|
10
|
+
*/
|
|
11
|
+
export async function localcofheFundAccount(
|
|
12
|
+
publicClient: PublicClient,
|
|
13
|
+
walletClient: WalletClient,
|
|
14
|
+
toAddress: `0x${string}`,
|
|
15
|
+
amount: string = '10'
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
const privateKey = (process.env['FUNDER_PRIVATE_KEY'] ?? FUNDER_PRIVATE_KEY) as `0x${string}`;
|
|
18
|
+
const funderAccount = privateKeyToAccount(privateKey);
|
|
19
|
+
|
|
20
|
+
const balance = await publicClient.getBalance({ address: funderAccount.address });
|
|
21
|
+
console.log(`Funder wallet address: ${funderAccount.address}`);
|
|
22
|
+
console.log(`Funder wallet balance: ${balance} wei`);
|
|
23
|
+
|
|
24
|
+
const amountWei = parseEther(amount);
|
|
25
|
+
if (balance < amountWei) {
|
|
26
|
+
console.error(`Funder wallet doesn't have enough funds. Balance: ${balance} wei, needed: ${amountWei} wei`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`Sending ${amount} ETH to ${toAddress}...`);
|
|
31
|
+
const hash = await walletClient.sendTransaction({
|
|
32
|
+
account: funderAccount,
|
|
33
|
+
to: toAddress,
|
|
34
|
+
value: amountWei,
|
|
35
|
+
chain: null,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log(`Transaction sent! Hash: ${hash}`);
|
|
39
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
40
|
+
console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
|
|
41
|
+
console.log(`Successfully sent ${amount} ETH to ${toAddress}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Checks a wallet's balance and funds it if below 1 ETH.
|
|
46
|
+
*/
|
|
47
|
+
export async function localcofheFundWalletIfNeeded(
|
|
48
|
+
publicClient: PublicClient,
|
|
49
|
+
walletClient: WalletClient,
|
|
50
|
+
walletAddress: `0x${string}`
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
const balance = await publicClient.getBalance({ address: walletAddress });
|
|
53
|
+
console.log(`Wallet balance: ${balance} wei`);
|
|
54
|
+
|
|
55
|
+
if (balance < parseEther('1')) {
|
|
56
|
+
console.log(`Balance below 1 ETH. Funding ${walletAddress}...`);
|
|
57
|
+
await localcofheFundAccount(publicClient, walletClient, walletAddress);
|
|
58
|
+
|
|
59
|
+
const newBalance = await publicClient.getBalance({ address: walletAddress });
|
|
60
|
+
console.log(`New wallet balance: ${newBalance} wei`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ConfigHooks } from 'hardhat/types/hooks';
|
|
2
|
+
|
|
3
|
+
import { resolvePluginConfig } from '../config.js';
|
|
4
|
+
|
|
5
|
+
const configHooks: Partial<ConfigHooks> = {
|
|
6
|
+
async resolveUserConfig(userConfig, resolveConfigurationVariable, next) {
|
|
7
|
+
const resolvedConfig = await next(userConfig, resolveConfigurationVariable);
|
|
8
|
+
resolvePluginConfig(userConfig, resolvedConfig);
|
|
9
|
+
return resolvedConfig;
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default async (): Promise<Partial<ConfigHooks>> => configHooks;
|
package/src/hooks/hre.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { HardhatRuntimeEnvironmentHooks, NetworkHooks } from 'hardhat/types/hooks';
|
|
2
|
+
import type { HardhatRuntimeEnvironment } from 'hardhat/types/hre';
|
|
3
|
+
import { createPublicClient, createWalletClient, custom, type PublicClient, type WalletClient } from 'viem';
|
|
4
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
MockTaskManagerArtifact,
|
|
8
|
+
MockACLArtifact,
|
|
9
|
+
MockZkVerifierArtifact,
|
|
10
|
+
MockThresholdNetworkArtifact,
|
|
11
|
+
TestBedArtifact,
|
|
12
|
+
} from '@cofhe/mock-contracts';
|
|
13
|
+
import { MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY, type CofheInputConfig } from '@cofhe/sdk';
|
|
14
|
+
import { createCofheConfig, createCofheClient } from '@cofhe/sdk/node';
|
|
15
|
+
import { hardhat as hardhatChain } from '@cofhe/sdk/chains';
|
|
16
|
+
|
|
17
|
+
import { deployMocks, type DeployMocksArgs } from '../deploy.js';
|
|
18
|
+
import { mock_setLoggingEnabled, mock_withLogs } from '../logging.js';
|
|
19
|
+
import { mock_getPlaintext, mock_expectPlaintext } from '../utils.js';
|
|
20
|
+
import type { CofheConnection } from '../type-extensions.js';
|
|
21
|
+
|
|
22
|
+
// ─── Per-connection cofhe object factory ─────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function createCofheConnection(
|
|
25
|
+
publicClient: PublicClient,
|
|
26
|
+
walletClient: WalletClient,
|
|
27
|
+
/** Reusable transport factory derived from the walletClient's request method */
|
|
28
|
+
transport: ReturnType<typeof custom>
|
|
29
|
+
): CofheConnection {
|
|
30
|
+
return {
|
|
31
|
+
async createConfig(config: Partial<CofheInputConfig> = {}) {
|
|
32
|
+
const zkvAccount = privateKeyToAccount(MOCKS_ZK_VERIFIER_SIGNER_PRIVATE_KEY);
|
|
33
|
+
const zkvWalletClient = createWalletClient({ account: zkvAccount, transport });
|
|
34
|
+
|
|
35
|
+
return createCofheConfig({
|
|
36
|
+
environment: 'hardhat',
|
|
37
|
+
supportedChains: [hardhatChain],
|
|
38
|
+
...config,
|
|
39
|
+
mocks: {
|
|
40
|
+
encryptDelay: 0,
|
|
41
|
+
...config.mocks,
|
|
42
|
+
},
|
|
43
|
+
_internal: {
|
|
44
|
+
...config._internal,
|
|
45
|
+
zkvWalletClient,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
createClient(config) {
|
|
51
|
+
return createCofheClient(config);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async createClientWithBatteries(signerWalletClient?: WalletClient) {
|
|
55
|
+
let signerClient: WalletClient;
|
|
56
|
+
let address: `0x${string}`;
|
|
57
|
+
|
|
58
|
+
if (signerWalletClient) {
|
|
59
|
+
const [addr] = await signerWalletClient.getAddresses();
|
|
60
|
+
if (!addr) throw new Error('createClientWithBatteries: provided walletClient has no accounts');
|
|
61
|
+
signerClient = signerWalletClient;
|
|
62
|
+
address = addr;
|
|
63
|
+
} else {
|
|
64
|
+
const [addr] = await walletClient.getAddresses();
|
|
65
|
+
if (!addr) throw new Error('createClientWithBatteries: no accounts available on this connection');
|
|
66
|
+
address = addr;
|
|
67
|
+
signerClient = createWalletClient({ account: address, transport });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const config = await this.createConfig();
|
|
71
|
+
const client = this.createClient(config);
|
|
72
|
+
await client.connect(publicClient, signerClient);
|
|
73
|
+
await client.permits.createSelf({ issuer: address });
|
|
74
|
+
return client;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
mocks: {
|
|
78
|
+
async deployMocks(options?: DeployMocksArgs) {
|
|
79
|
+
await deployMocks({ publicClient, walletClient }, options);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async withLogs(closureName: string, closure: () => Promise<void>) {
|
|
83
|
+
await mock_withLogs(publicClient, walletClient, closureName, closure);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async enableLogs(closureName?: string) {
|
|
87
|
+
await mock_setLoggingEnabled(publicClient, walletClient, true, closureName);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async disableLogs() {
|
|
91
|
+
await mock_setLoggingEnabled(publicClient, walletClient, false);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async getPlaintext(ctHash: bigint | string) {
|
|
95
|
+
const value = await mock_getPlaintext(publicClient, ctHash);
|
|
96
|
+
if (value === undefined) throw new Error('getPlaintext: not on mock network');
|
|
97
|
+
return value;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async expectPlaintext(ctHash: bigint | string, expectedValue: bigint) {
|
|
101
|
+
await mock_expectPlaintext(publicClient, ctHash, expectedValue);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
MockTaskManager: {
|
|
105
|
+
address: MockTaskManagerArtifact.fixedAddress as `0x${string}`,
|
|
106
|
+
abi: MockTaskManagerArtifact.abi,
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async MockACL() {
|
|
110
|
+
const address = await publicClient.readContract({
|
|
111
|
+
address: MockTaskManagerArtifact.fixedAddress as `0x${string}`,
|
|
112
|
+
abi: [
|
|
113
|
+
{ name: 'acl', type: 'function', inputs: [], outputs: [{ type: 'address' }], stateMutability: 'view' },
|
|
114
|
+
] as const,
|
|
115
|
+
functionName: 'acl',
|
|
116
|
+
});
|
|
117
|
+
return { address, abi: MockACLArtifact.abi };
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
MockZkVerifier: {
|
|
121
|
+
address: MockZkVerifierArtifact.fixedAddress as `0x${string}`,
|
|
122
|
+
abi: MockZkVerifierArtifact.abi,
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
MockThresholdNetwork: {
|
|
126
|
+
address: MockThresholdNetworkArtifact.fixedAddress as `0x${string}`,
|
|
127
|
+
abi: MockThresholdNetworkArtifact.abi,
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
TestBed: {
|
|
131
|
+
address: TestBedArtifact.fixedAddress as `0x${string}`,
|
|
132
|
+
abi: TestBedArtifact.abi,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── HRE hook ─────────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
const hreHooks: Partial<HardhatRuntimeEnvironmentHooks> = {
|
|
141
|
+
async created(_context, hre: HardhatRuntimeEnvironment) {
|
|
142
|
+
// Register a permanent newConnection handler so that every network.connect()
|
|
143
|
+
// call automatically deploys mocks and attaches conn.cofhe.
|
|
144
|
+
const networkHandlers: Partial<NetworkHooks> = {
|
|
145
|
+
async newConnection(context, next) {
|
|
146
|
+
const conn = await next(context);
|
|
147
|
+
const transport = custom(conn.provider);
|
|
148
|
+
const publicClient = createPublicClient({ transport });
|
|
149
|
+
const walletClient = createWalletClient({ transport });
|
|
150
|
+
// Derive a reusable transport factory by wrapping the walletClient's
|
|
151
|
+
// request method — lets us create account-bound clients later.
|
|
152
|
+
const connTransport = custom({ request: (args: any) => (walletClient as any).request(args) });
|
|
153
|
+
|
|
154
|
+
await deployMocks(
|
|
155
|
+
{ publicClient, walletClient },
|
|
156
|
+
{
|
|
157
|
+
deployTestBed: true,
|
|
158
|
+
gasWarning: hre.config.cofhe.gasWarning,
|
|
159
|
+
mocksDeployVerbosity: hre.config.cofhe.mocksDeployVerbosity,
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
(conn as any).cofhe = createCofheConnection(publicClient, walletClient, connTransport);
|
|
164
|
+
return conn;
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
hre.hooks.registerHandlers('network', networkHandlers);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export default async (): Promise<Partial<HardhatRuntimeEnvironmentHooks>> => hreHooks;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { HardhatPlugin } from 'hardhat/types/plugins';
|
|
2
|
+
import { task } from 'hardhat/config';
|
|
3
|
+
import { ArgumentType } from 'hardhat/types/arguments';
|
|
4
|
+
|
|
5
|
+
import { TASK_COFHE_USE_FAUCET, TASK_COFHE_SET_LOG_OPS } from './consts.js';
|
|
6
|
+
|
|
7
|
+
import './type-extensions.js';
|
|
8
|
+
|
|
9
|
+
const plugin: HardhatPlugin = {
|
|
10
|
+
id: '@cofhe/hardhat-3-plugin',
|
|
11
|
+
|
|
12
|
+
hookHandlers: {
|
|
13
|
+
config: async () => import('./hooks/config.js'),
|
|
14
|
+
hre: async () => import('./hooks/hre.js'),
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
tasks: [
|
|
18
|
+
task(TASK_COFHE_USE_FAUCET, 'Fund an account via the localcofhe faucet')
|
|
19
|
+
.addOption({
|
|
20
|
+
name: 'address',
|
|
21
|
+
description: 'Address to fund',
|
|
22
|
+
type: ArgumentType.STRING_WITHOUT_DEFAULT,
|
|
23
|
+
defaultValue: undefined,
|
|
24
|
+
})
|
|
25
|
+
.setAction(async () => import('./tasks/use-faucet.js'))
|
|
26
|
+
.build(),
|
|
27
|
+
|
|
28
|
+
task(TASK_COFHE_SET_LOG_OPS, 'Enable or disable CoFHE mock contract logging')
|
|
29
|
+
.addFlag({ name: 'enable', description: 'Whether to enable logging' })
|
|
30
|
+
.setAction(async () => import('./tasks/set-log-ops.js'))
|
|
31
|
+
.build(),
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default plugin;
|
|
36
|
+
|
|
37
|
+
export * from './consts.js';
|
|
38
|
+
export * from './deploy.js';
|
|
39
|
+
export * from './fund.js';
|
|
40
|
+
export * from './logging.js';
|
|
41
|
+
export * from './utils.js';
|
package/src/logging.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { PublicClient, WalletClient } from 'viem';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
import { MockTaskManagerArtifact } from '@cofhe/mock-contracts';
|
|
5
|
+
import { TASK_MANAGER_ADDRESS } from '@cofhe/sdk';
|
|
6
|
+
|
|
7
|
+
async function getLoggingEnabled(publicClient: PublicClient): Promise<boolean> {
|
|
8
|
+
return publicClient.readContract({
|
|
9
|
+
address: TASK_MANAGER_ADDRESS,
|
|
10
|
+
abi: MockTaskManagerArtifact.abi,
|
|
11
|
+
functionName: 'logOps',
|
|
12
|
+
args: [],
|
|
13
|
+
}) as Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function setLoggingEnabled(
|
|
17
|
+
publicClient: PublicClient,
|
|
18
|
+
walletClient: WalletClient,
|
|
19
|
+
enabled: boolean
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const [account] = await walletClient.getAddresses();
|
|
22
|
+
const hash = await walletClient.writeContract({
|
|
23
|
+
address: TASK_MANAGER_ADDRESS,
|
|
24
|
+
abi: MockTaskManagerArtifact.abi,
|
|
25
|
+
functionName: 'setLogOps',
|
|
26
|
+
args: [enabled],
|
|
27
|
+
account,
|
|
28
|
+
chain: null,
|
|
29
|
+
});
|
|
30
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const printLogsEnabledMessage = (closureMessage: string) => {
|
|
34
|
+
console.log('┌──────────────────┬──────────────────────────────────────────────────');
|
|
35
|
+
console.log(`│ [COFHE-MOCKS] │ ${closureMessage}`);
|
|
36
|
+
console.log('├──────────────────┴──────────────────────────────────────────────────');
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const printLogsBlockEnd = () => {
|
|
40
|
+
console.log('└─────────────────────────────────────────────────────────────────────');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export async function mock_setLoggingEnabled(
|
|
44
|
+
publicClient: PublicClient,
|
|
45
|
+
walletClient: WalletClient,
|
|
46
|
+
enabled: boolean,
|
|
47
|
+
closureName?: string
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
const initiallyEnabled = await getLoggingEnabled(publicClient);
|
|
51
|
+
await setLoggingEnabled(publicClient, walletClient, enabled);
|
|
52
|
+
|
|
53
|
+
if (enabled) {
|
|
54
|
+
printLogsEnabledMessage(`${closureName ? `"${chalk.bold(closureName)}" logs:` : 'Logs:'}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!enabled && initiallyEnabled) {
|
|
58
|
+
printLogsBlockEnd();
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.log(chalk.red('mock_setLoggingEnabled error'), error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function mock_withLogs(
|
|
66
|
+
publicClient: PublicClient,
|
|
67
|
+
walletClient: WalletClient,
|
|
68
|
+
closureName: string,
|
|
69
|
+
closure: () => Promise<void>
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
const initiallyEnabled = await getLoggingEnabled(publicClient);
|
|
72
|
+
|
|
73
|
+
await setLoggingEnabled(publicClient, walletClient, true);
|
|
74
|
+
printLogsEnabledMessage(`"${chalk.bold(closureName)}" logs:`);
|
|
75
|
+
try {
|
|
76
|
+
await closure();
|
|
77
|
+
} finally {
|
|
78
|
+
printLogsBlockEnd();
|
|
79
|
+
|
|
80
|
+
if (!initiallyEnabled) {
|
|
81
|
+
await setLoggingEnabled(publicClient, walletClient, false);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NewTaskActionFunction } from 'hardhat/types/tasks';
|
|
2
|
+
import { createPublicClient, createWalletClient, custom } from 'viem';
|
|
3
|
+
|
|
4
|
+
import { mock_setLoggingEnabled } from '../logging.js';
|
|
5
|
+
|
|
6
|
+
interface SetLogOpsTaskArgs {
|
|
7
|
+
enable: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const action: NewTaskActionFunction<SetLogOpsTaskArgs> = async ({ enable }, hre) => {
|
|
11
|
+
const connection = await hre.network.connect();
|
|
12
|
+
const transport = custom(connection.provider);
|
|
13
|
+
|
|
14
|
+
await mock_setLoggingEnabled(createPublicClient({ transport }), createWalletClient({ transport }), enable);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default action;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NewTaskActionFunction } from 'hardhat/types/tasks';
|
|
2
|
+
import { createPublicClient, createWalletClient, custom } from 'viem';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
import { localcofheFundAccount } from '../fund.js';
|
|
6
|
+
|
|
7
|
+
interface UseFaucetTaskArgs {
|
|
8
|
+
address: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const action: NewTaskActionFunction<UseFaucetTaskArgs> = async ({ address }, hre) => {
|
|
12
|
+
const connection = await hre.network.connect();
|
|
13
|
+
|
|
14
|
+
if (connection.networkName !== 'localcofhe') {
|
|
15
|
+
console.info(chalk.yellow('Programmatic faucet only supported for localcofhe'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!address) {
|
|
20
|
+
console.info(chalk.red('Failed to get address to fund'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.info(chalk.green(`Getting funds from faucet for ${address}`));
|
|
25
|
+
|
|
26
|
+
const transport = custom(connection.provider);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await localcofheFundAccount(
|
|
30
|
+
createPublicClient({ transport }),
|
|
31
|
+
createWalletClient({ transport }),
|
|
32
|
+
address as `0x${string}`
|
|
33
|
+
);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.info(chalk.red(`Failed to get funds from localcofhe for ${address}: ${e}`));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default action;
|