@cofhe/hardhat-plugin 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.
- package/CHANGELOG.md +24 -0
- package/README.md +57 -11
- package/dist/index.d.mts +59 -3
- package/dist/index.d.ts +59 -3
- package/dist/index.js +226 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +224 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -6
- package/src/deploy.ts +46 -7
- package/src/gas.ts +227 -0
- package/src/index.ts +75 -20
- package/src/utils.ts +13 -7
package/src/gas.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { ethers } from 'ethers';
|
|
3
|
+
import { MOCKS_TASK_MANAGER_ADDRESS } from '@cofhe/sdk';
|
|
4
|
+
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
|
|
5
|
+
|
|
6
|
+
/// Topic of MockCoFHE's `MockGasConsumed(uint256)` event, emitted once per block of
|
|
7
|
+
/// mock-only work (op replication, decrypt-task storage, log building) with the gas it
|
|
8
|
+
/// consumed. Summing these per transaction gives the mock overhead of that transaction.
|
|
9
|
+
export const MOCK_GAS_CONSUMED_TOPIC = ethers.id('MockGasConsumed(uint256)');
|
|
10
|
+
|
|
11
|
+
/// Minimal receipt shape - compatible with ethers v6 TransactionReceipt and viem receipts.
|
|
12
|
+
export type AdjustableGasReceipt = {
|
|
13
|
+
gasUsed: bigint | number | string;
|
|
14
|
+
logs?: ReadonlyArray<{ address: string; topics: ReadonlyArray<string>; data: string }>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type AdjustedGasBreakdown = {
|
|
18
|
+
/** Raw gas used by the transaction (includes mock overhead). */
|
|
19
|
+
gasUsed: bigint;
|
|
20
|
+
/** Gas consumed by mock-only work (FHE op replication, decrypt-task storage, mock logging). */
|
|
21
|
+
mockGas: bigint;
|
|
22
|
+
/** gasUsed - mockGas: an estimate of what the transaction would cost on a real CoFHE network. */
|
|
23
|
+
adjustedGasUsed: bigint;
|
|
24
|
+
/** Number of MockGasConsumed events - roughly the number of mock-only blocks executed. */
|
|
25
|
+
mockGasEvents: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/// Computes the gas breakdown of a transaction receipt by summing the MockGasConsumed
|
|
29
|
+
/// events emitted by the mock task manager. Pure function of the receipt - no RPC calls.
|
|
30
|
+
/// On a real network (no mock events in the logs) `adjustedGasUsed` equals `gasUsed`.
|
|
31
|
+
/// Caveat: `mockGas` is measured pre-refund while `gasUsed` is post-refund, so transactions
|
|
32
|
+
/// that earn large gas refunds (storage clearing) can slightly overstate the mock share;
|
|
33
|
+
/// `adjustedGasUsed` is clamped at 0 for the pathological case.
|
|
34
|
+
export const mock_getAdjustedGasBreakdown = (receipt: AdjustableGasReceipt): AdjustedGasBreakdown => {
|
|
35
|
+
let mockGas = 0n;
|
|
36
|
+
let mockGasEvents = 0;
|
|
37
|
+
|
|
38
|
+
for (const log of receipt.logs ?? []) {
|
|
39
|
+
if (
|
|
40
|
+
log.address.toLowerCase() === MOCKS_TASK_MANAGER_ADDRESS.toLowerCase() &&
|
|
41
|
+
log.topics[0] === MOCK_GAS_CONSUMED_TOPIC
|
|
42
|
+
) {
|
|
43
|
+
mockGas += BigInt(log.data);
|
|
44
|
+
mockGasEvents += 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const gasUsed = BigInt(receipt.gasUsed);
|
|
49
|
+
return { gasUsed, mockGas, adjustedGasUsed: mockGas > gasUsed ? 0n : gasUsed - mockGas, mockGasEvents };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/// Returns the receipt's gas usage excluding mock overhead - an estimate of what the
|
|
53
|
+
/// transaction would cost on a real CoFHE network. See mock_getAdjustedGasBreakdown.
|
|
54
|
+
export const mock_getAdjustedGasUsed = (receipt: AdjustableGasReceipt): bigint =>
|
|
55
|
+
mock_getAdjustedGasBreakdown(receipt).adjustedGasUsed;
|
|
56
|
+
|
|
57
|
+
// =====================
|
|
58
|
+
// END-OF-RUN SUMMARY
|
|
59
|
+
// =====================
|
|
60
|
+
|
|
61
|
+
type MethodAggregate = {
|
|
62
|
+
to: string;
|
|
63
|
+
selector: string;
|
|
64
|
+
calls: number;
|
|
65
|
+
totalGasUsed: bigint;
|
|
66
|
+
totalMockGas: bigint;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const shortAddress = (address: string) => `${address.slice(0, 6)}..${address.slice(-4)}`;
|
|
70
|
+
|
|
71
|
+
/// Builds a selector -> "signature" map from every compiled artifact so summary rows can
|
|
72
|
+
/// show method names instead of raw selectors.
|
|
73
|
+
const buildSelectorNameMap = async (hre: HardhatRuntimeEnvironment): Promise<Map<string, string>> => {
|
|
74
|
+
const selectorNames = new Map<string, string>();
|
|
75
|
+
for (const fqn of await hre.artifacts.getAllFullyQualifiedNames()) {
|
|
76
|
+
try {
|
|
77
|
+
const artifact = await hre.artifacts.readArtifact(fqn);
|
|
78
|
+
const iface = new ethers.Interface(artifact.abi);
|
|
79
|
+
iface.forEachFunction((fn) => {
|
|
80
|
+
if (!selectorNames.has(fn.selector)) selectorNames.set(fn.selector, fn.format('sighash'));
|
|
81
|
+
});
|
|
82
|
+
} catch {
|
|
83
|
+
// Artifacts without a usable ABI are skipped
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return selectorNames;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/// Resolves deployed addresses to contract names by matching on-chain code against
|
|
90
|
+
/// artifact deployedBytecode. Best effort: contracts with immutables won't match and
|
|
91
|
+
/// fall back to a shortened address.
|
|
92
|
+
const buildAddressNameMap = async (
|
|
93
|
+
hre: HardhatRuntimeEnvironment,
|
|
94
|
+
addresses: string[]
|
|
95
|
+
): Promise<Map<string, string>> => {
|
|
96
|
+
const codeToName = new Map<string, string>();
|
|
97
|
+
for (const fqn of await hre.artifacts.getAllFullyQualifiedNames()) {
|
|
98
|
+
try {
|
|
99
|
+
const artifact = await hre.artifacts.readArtifact(fqn);
|
|
100
|
+
if (artifact.deployedBytecode && artifact.deployedBytecode !== '0x') {
|
|
101
|
+
codeToName.set(artifact.deployedBytecode.toLowerCase(), artifact.contractName);
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// skip
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const addressNames = new Map<string, string>();
|
|
109
|
+
for (const address of addresses) {
|
|
110
|
+
try {
|
|
111
|
+
const code: string = await hre.network.provider.send('eth_getCode', [address, 'latest']);
|
|
112
|
+
addressNames.set(address, codeToName.get(code.toLowerCase()) ?? shortAddress(address));
|
|
113
|
+
} catch {
|
|
114
|
+
addressNames.set(address, shortAddress(address));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return addressNames;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/// Prints a per-method gas summary for every transaction that performed mock FHE work,
|
|
121
|
+
/// showing raw gas (as reported on the mock network) next to adjusted gas (an estimate of
|
|
122
|
+
/// real-network cost). Enabled via `cofhe: { gasSummary: true }` in the hardhat config;
|
|
123
|
+
/// runs after the test task completes.
|
|
124
|
+
export const printMockGasSummary = async (hre: HardhatRuntimeEnvironment) => {
|
|
125
|
+
// Only meaningful against the in-process Hardhat network: elsewhere the mocks aren't
|
|
126
|
+
// auto-deployed and a genesis-to-latest eth_getLogs could hammer an external RPC.
|
|
127
|
+
if (hre.network.name !== 'hardhat') return;
|
|
128
|
+
|
|
129
|
+
let logs: Array<{ transactionHash: string; data: string }>;
|
|
130
|
+
try {
|
|
131
|
+
logs = await hre.network.provider.send('eth_getLogs', [
|
|
132
|
+
{ fromBlock: '0x0', toBlock: 'latest', address: MOCKS_TASK_MANAGER_ADDRESS, topics: [MOCK_GAS_CONSUMED_TOPIC] },
|
|
133
|
+
]);
|
|
134
|
+
} catch {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!logs || logs.length === 0) return;
|
|
138
|
+
|
|
139
|
+
// Sum mock gas per transaction
|
|
140
|
+
const mockGasPerTx = new Map<string, bigint>();
|
|
141
|
+
for (const log of logs) {
|
|
142
|
+
mockGasPerTx.set(log.transactionHash, (mockGasPerTx.get(log.transactionHash) ?? 0n) + BigInt(log.data));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Aggregate per called contract + method; deployments (FHE ops in constructors) are
|
|
146
|
+
// attributed to the created contract under a '(deployment)' method label.
|
|
147
|
+
const aggregates = new Map<string, MethodAggregate>();
|
|
148
|
+
for (const [txHash, mockGas] of mockGasPerTx) {
|
|
149
|
+
try {
|
|
150
|
+
const receipt = await hre.network.provider.send('eth_getTransactionReceipt', [txHash]);
|
|
151
|
+
const tx = await hre.network.provider.send('eth_getTransactionByHash', [txHash]);
|
|
152
|
+
if (!receipt) continue;
|
|
153
|
+
const target = tx?.to ?? receipt.contractAddress;
|
|
154
|
+
if (!target) continue;
|
|
155
|
+
|
|
156
|
+
const to = ethers.getAddress(target);
|
|
157
|
+
const selector = tx?.to ? (tx.input ?? '0x').slice(0, 10) : '(deployment)';
|
|
158
|
+
const key = `${to}:${selector}`;
|
|
159
|
+
const aggregate = aggregates.get(key) ?? { to, selector, calls: 0, totalGasUsed: 0n, totalMockGas: 0n };
|
|
160
|
+
aggregate.calls += 1;
|
|
161
|
+
aggregate.totalGasUsed += BigInt(receipt.gasUsed);
|
|
162
|
+
aggregate.totalMockGas += mockGas;
|
|
163
|
+
aggregates.set(key, aggregate);
|
|
164
|
+
} catch {
|
|
165
|
+
// Skip transactions that can't be resolved
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (aggregates.size === 0) return;
|
|
169
|
+
|
|
170
|
+
const selectorNames = await buildSelectorNameMap(hre);
|
|
171
|
+
const addressNames = await buildAddressNameMap(hre, [...new Set([...aggregates.values()].map((a) => a.to))]);
|
|
172
|
+
|
|
173
|
+
// Merge per-address aggregates by resolved contract name + method, so multiple deployed
|
|
174
|
+
// instances of the same contract (one per test, typically) share a row.
|
|
175
|
+
const merged = new Map<string, { contract: string; method: string } & Omit<MethodAggregate, 'to' | 'selector'>>();
|
|
176
|
+
for (const a of aggregates.values()) {
|
|
177
|
+
const contract = addressNames.get(a.to) ?? shortAddress(a.to);
|
|
178
|
+
const method = selectorNames.get(a.selector) ?? a.selector;
|
|
179
|
+
const key = `${contract}:${method}`;
|
|
180
|
+
const row = merged.get(key) ?? { contract, method, calls: 0, totalGasUsed: 0n, totalMockGas: 0n };
|
|
181
|
+
row.calls += a.calls;
|
|
182
|
+
row.totalGasUsed += a.totalGasUsed;
|
|
183
|
+
row.totalMockGas += a.totalMockGas;
|
|
184
|
+
merged.set(key, row);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const rows = [...merged.values()]
|
|
188
|
+
.map((r) => {
|
|
189
|
+
const avgGasUsed = r.totalGasUsed / BigInt(r.calls);
|
|
190
|
+
const avgMockGas = r.totalMockGas / BigInt(r.calls);
|
|
191
|
+
return {
|
|
192
|
+
contract: r.contract,
|
|
193
|
+
method: r.method,
|
|
194
|
+
calls: r.calls,
|
|
195
|
+
avgGasUsed,
|
|
196
|
+
avgAdjusted: avgMockGas > avgGasUsed ? 0n : avgGasUsed - avgMockGas,
|
|
197
|
+
overheadPct: avgGasUsed === 0n ? 0 : Math.min(100, Number((avgMockGas * 100n) / avgGasUsed)),
|
|
198
|
+
};
|
|
199
|
+
})
|
|
200
|
+
.sort((a, b) => a.contract.localeCompare(b.contract) || a.method.localeCompare(b.method));
|
|
201
|
+
|
|
202
|
+
const headers = ['Contract', 'Method', 'Calls', 'Avg gas (mocks)', 'Avg gas (adjusted)', 'Mock overhead'];
|
|
203
|
+
const cells = rows.map((r) => [
|
|
204
|
+
r.contract,
|
|
205
|
+
r.method,
|
|
206
|
+
String(r.calls),
|
|
207
|
+
r.avgGasUsed.toLocaleString(),
|
|
208
|
+
r.avgAdjusted.toLocaleString(),
|
|
209
|
+
`${r.overheadPct}%`,
|
|
210
|
+
]);
|
|
211
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...cells.map((row) => row[i].length)));
|
|
212
|
+
const line = (row: string[]) => `│ ${row.map((cell, i) => cell.padEnd(widths[i])).join(' │ ')} │`;
|
|
213
|
+
const divider = (l: string, m: string, r: string) => `${l}${widths.map((w) => '─'.repeat(w + 2)).join(m)}${r}`;
|
|
214
|
+
|
|
215
|
+
console.log('');
|
|
216
|
+
console.log(chalk.bold('[COFHE-MOCKS] Gas summary') + chalk.dim(' — adjusted ≈ cost excluding mock-only overhead'));
|
|
217
|
+
console.log(divider('┌', '┬', '┐'));
|
|
218
|
+
console.log(line(headers));
|
|
219
|
+
console.log(divider('├', '┼', '┤'));
|
|
220
|
+
for (const row of cells) console.log(line(row));
|
|
221
|
+
console.log(divider('└', '┴', '┘'));
|
|
222
|
+
console.log(
|
|
223
|
+
chalk.dim(
|
|
224
|
+
'Raw numbers include on-chain replication of off-chain CoFHE work; adjusted numbers estimate real-network cost. estimateGas remains unadjusted.'
|
|
225
|
+
)
|
|
226
|
+
);
|
|
227
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -22,12 +22,21 @@ import { deployMocks, type DeployMocksArgs, type LogMocksDeploy } from './deploy
|
|
|
22
22
|
import { mock_setLoggingEnabled, mock_withLogs } from './logging.js';
|
|
23
23
|
import { getFixedMockContract, mock_expectPlaintext } from './utils.js';
|
|
24
24
|
import { mock_getPlaintext } from './utils.js';
|
|
25
|
+
import {
|
|
26
|
+
mock_getAdjustedGasBreakdown,
|
|
27
|
+
mock_getAdjustedGasUsed,
|
|
28
|
+
printMockGasSummary,
|
|
29
|
+
type AdjustableGasReceipt,
|
|
30
|
+
type AdjustedGasBreakdown,
|
|
31
|
+
} from './gas.js';
|
|
25
32
|
import { hardhat } from '@cofhe/sdk/chains';
|
|
26
33
|
import {
|
|
27
34
|
MockThresholdNetworkArtifact,
|
|
35
|
+
MockCoFHEAddressBookArtifact,
|
|
28
36
|
MockTaskManagerArtifact,
|
|
29
37
|
MockZkVerifierArtifact,
|
|
30
38
|
type MockACL,
|
|
39
|
+
type MockCoFHEAddressBook,
|
|
31
40
|
type MockTaskManager,
|
|
32
41
|
type MockThresholdNetwork,
|
|
33
42
|
type MockZkVerifier,
|
|
@@ -44,6 +53,14 @@ declare module 'hardhat/types/config' {
|
|
|
44
53
|
logMocks?: boolean;
|
|
45
54
|
/** Whether to show gas usage warnings for mock operations (default: true) */
|
|
46
55
|
gasWarning?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Print a per-method gas summary after `hardhat test`, showing raw gas next to
|
|
58
|
+
* adjusted gas (mock-only overhead excluded - an estimate of real-network cost).
|
|
59
|
+
* Reconstructed from chain history: transactions rolled back by loadFixture /
|
|
60
|
+
* evm_revert won't appear - use getAdjustedGasUsed(receipt) for exact numbers.
|
|
61
|
+
* (default: false)
|
|
62
|
+
*/
|
|
63
|
+
gasSummary?: boolean;
|
|
47
64
|
/**
|
|
48
65
|
* Controls deploy-mocks console output.
|
|
49
66
|
* - `''` — silent, no output
|
|
@@ -60,6 +77,8 @@ declare module 'hardhat/types/config' {
|
|
|
60
77
|
logMocks: boolean;
|
|
61
78
|
/** Whether to show gas usage warnings for mock operations (default: true) */
|
|
62
79
|
gasWarning: boolean;
|
|
80
|
+
/** Print a per-method adjusted-gas summary after `hardhat test` (default: false) */
|
|
81
|
+
gasSummary: boolean;
|
|
63
82
|
mocksDeployVerbosity: LogMocksDeploy;
|
|
64
83
|
};
|
|
65
84
|
}
|
|
@@ -132,27 +151,26 @@ subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (taskArgs: { sou
|
|
|
132
151
|
});
|
|
133
152
|
|
|
134
153
|
extendConfig((config, userConfig) => {
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
154
|
+
// Inject localcofhe preset only when the user hasn't defined it.
|
|
155
|
+
// Must not early-return: sepolia presets and config.cofhe still need to run
|
|
156
|
+
// (Hardhat 3 plugin already scopes the skip this way).
|
|
157
|
+
if (!userConfig.networks?.localcofhe) {
|
|
158
|
+
config.networks.localcofhe = {
|
|
159
|
+
gas: 'auto',
|
|
160
|
+
gasMultiplier: 1.2,
|
|
161
|
+
gasPrice: 'auto',
|
|
162
|
+
timeout: 10_000,
|
|
163
|
+
httpHeaders: {},
|
|
164
|
+
url: 'http://127.0.0.1:42069',
|
|
165
|
+
accounts: [
|
|
166
|
+
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
|
|
167
|
+
'0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',
|
|
168
|
+
'0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a',
|
|
169
|
+
'0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6',
|
|
170
|
+
],
|
|
171
|
+
};
|
|
138
172
|
}
|
|
139
173
|
|
|
140
|
-
// Default config
|
|
141
|
-
config.networks.localcofhe = {
|
|
142
|
-
gas: 'auto',
|
|
143
|
-
gasMultiplier: 1.2,
|
|
144
|
-
gasPrice: 'auto',
|
|
145
|
-
timeout: 10_000,
|
|
146
|
-
httpHeaders: {},
|
|
147
|
-
url: 'http://127.0.0.1:42069',
|
|
148
|
-
accounts: [
|
|
149
|
-
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
|
|
150
|
-
'0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',
|
|
151
|
-
'0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a',
|
|
152
|
-
'0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6',
|
|
153
|
-
],
|
|
154
|
-
};
|
|
155
|
-
|
|
156
174
|
// Only add Sepolia config if user hasn't defined it
|
|
157
175
|
if (!userConfig.networks?.['eth-sepolia']) {
|
|
158
176
|
config.networks['eth-sepolia'] = {
|
|
@@ -185,6 +203,7 @@ extendConfig((config, userConfig) => {
|
|
|
185
203
|
config.cofhe = {
|
|
186
204
|
logMocks: userConfig.cofhe?.logMocks ?? false,
|
|
187
205
|
gasWarning: userConfig.cofhe?.gasWarning ?? false,
|
|
206
|
+
gasSummary: userConfig.cofhe?.gasSummary ?? false,
|
|
188
207
|
mocksDeployVerbosity: (userConfig.cofhe?.mocksDeployVerbosity ?? 'v') as LogMocksDeploy,
|
|
189
208
|
};
|
|
190
209
|
});
|
|
@@ -243,7 +262,12 @@ task(TASK_TEST, 'Deploy mock contracts on hardhat').setAction(async ({}, hre, ru
|
|
|
243
262
|
mocksDeployVerbosity: hre.config.cofhe.mocksDeployVerbosity,
|
|
244
263
|
});
|
|
245
264
|
}
|
|
246
|
-
|
|
265
|
+
|
|
266
|
+
const result = await runSuper();
|
|
267
|
+
if (hre.config.cofhe.gasSummary) {
|
|
268
|
+
await printMockGasSummary(hre);
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
247
271
|
});
|
|
248
272
|
|
|
249
273
|
task(TASK_NODE, 'Deploy mock contracts on hardhat').setAction(async ({}, hre, runSuper) => {
|
|
@@ -277,6 +301,7 @@ export * from './utils.js';
|
|
|
277
301
|
export * from './fund.js';
|
|
278
302
|
export * from './logging.js';
|
|
279
303
|
export * from './deploy.js';
|
|
304
|
+
export * from './gas.js';
|
|
280
305
|
|
|
281
306
|
/**
|
|
282
307
|
* Runtime environment extensions for the CoFHE Hardhat plugin.
|
|
@@ -321,6 +346,26 @@ declare module 'hardhat/types/runtime' {
|
|
|
321
346
|
*/
|
|
322
347
|
createClientWithBatteries: (signer?: HardhatEthersSigner) => Promise<CofheClient>;
|
|
323
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Returns a transaction's gas usage excluding mock-only overhead (FHE op replication,
|
|
351
|
+
* decrypt-task storage, mock logging) - an estimate of what the transaction would cost
|
|
352
|
+
* on a real CoFHE network. Pure function of the receipt (sums the mock task manager's
|
|
353
|
+
* MockGasConsumed events from `receipt.logs`); on a real network the receipt carries no
|
|
354
|
+
* such events and the raw `gasUsed` is returned unchanged.
|
|
355
|
+
* @param {AdjustableGasReceipt} receipt - The transaction receipt (ethers or viem shape)
|
|
356
|
+
* @returns {bigint} gasUsed minus mock overhead
|
|
357
|
+
*/
|
|
358
|
+
getAdjustedGasUsed: (receipt: AdjustableGasReceipt) => bigint;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Full gas breakdown of a transaction receipt: raw `gasUsed`, the mock-only `mockGas`,
|
|
362
|
+
* the `adjustedGasUsed` (raw minus mock), and the number of mock gas events.
|
|
363
|
+
* See getAdjustedGasUsed.
|
|
364
|
+
* @param {AdjustableGasReceipt} receipt - The transaction receipt (ethers or viem shape)
|
|
365
|
+
* @returns {AdjustedGasBreakdown} The breakdown
|
|
366
|
+
*/
|
|
367
|
+
getAdjustedGasBreakdown: (receipt: AdjustableGasReceipt) => AdjustedGasBreakdown;
|
|
368
|
+
|
|
324
369
|
mocks: {
|
|
325
370
|
/**
|
|
326
371
|
* **[MOCKS ONLY]**
|
|
@@ -393,6 +438,12 @@ declare module 'hardhat/types/runtime' {
|
|
|
393
438
|
*/
|
|
394
439
|
expectPlaintext: (ctHash: bigint | string, expectedValue: bigint) => Promise<void>;
|
|
395
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Get the MockCoFHEAddressBook contract (typed via typechain)
|
|
443
|
+
* @returns {Promise<MockCoFHEAddressBook>} The MockCoFHEAddressBook contract
|
|
444
|
+
*/
|
|
445
|
+
getMockCoFHEAddressBook: () => Promise<MockCoFHEAddressBook>;
|
|
446
|
+
|
|
396
447
|
/**
|
|
397
448
|
* Get the MockTaskManager contract (typed via typechain)
|
|
398
449
|
* @returns {Promise<MockTaskManager>} The MockTaskManager contract
|
|
@@ -493,6 +544,8 @@ extendEnvironment((hre) => {
|
|
|
493
544
|
// Return client
|
|
494
545
|
return client;
|
|
495
546
|
},
|
|
547
|
+
getAdjustedGasUsed: mock_getAdjustedGasUsed,
|
|
548
|
+
getAdjustedGasBreakdown: mock_getAdjustedGasBreakdown,
|
|
496
549
|
mocks: {
|
|
497
550
|
withLogs: async (closureName: string, closure: () => Promise<void>) => {
|
|
498
551
|
return mock_withLogs(hre, closureName, closure);
|
|
@@ -514,6 +567,8 @@ extendEnvironment((hre) => {
|
|
|
514
567
|
const [signer] = await hre.ethers.getSigners();
|
|
515
568
|
return mock_expectPlaintext(signer.provider, ctHash, expectedValue);
|
|
516
569
|
},
|
|
570
|
+
getMockCoFHEAddressBook: async () =>
|
|
571
|
+
getFixedMockContract(hre, MockCoFHEAddressBookArtifact) as unknown as Promise<MockCoFHEAddressBook>,
|
|
517
572
|
getMockTaskManager: async () =>
|
|
518
573
|
getFixedMockContract(hre, MockTaskManagerArtifact) as unknown as Promise<MockTaskManager>,
|
|
519
574
|
getMockACL: async () => {
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MOCKS_TASK_MANAGER_ADDRESS, MOCKS_ZK_VERIFIER_ADDRESS } from '@cofhe/sdk';
|
|
2
2
|
import { expect } from 'chai';
|
|
3
3
|
import { Contract, ethers } from 'ethers';
|
|
4
4
|
import { type HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider';
|
|
@@ -12,7 +12,11 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types';
|
|
|
12
12
|
/// so that Hardhat can decode reverts and trace calls against these contracts.
|
|
13
13
|
/// If the mock contract should be deployed to a fixed address, `hardhat_setCode` is used.
|
|
14
14
|
/// Otherwise, ethers.js deploys it to a normal address.
|
|
15
|
-
export const deployMockContractFromArtifact = async (
|
|
15
|
+
export const deployMockContractFromArtifact = async (
|
|
16
|
+
hre: HardhatRuntimeEnvironment,
|
|
17
|
+
artifact: MockArtifact,
|
|
18
|
+
constructorArgs: readonly unknown[] = []
|
|
19
|
+
) => {
|
|
16
20
|
const hardhatArtifact = await hre.artifacts.readArtifact(artifact.contractName);
|
|
17
21
|
|
|
18
22
|
if (artifact.isFixed) {
|
|
@@ -22,7 +26,7 @@ export const deployMockContractFromArtifact = async (hre: HardhatRuntimeEnvironm
|
|
|
22
26
|
|
|
23
27
|
const [signer] = await hre.ethers.getSigners();
|
|
24
28
|
const factory = new hre.ethers.ContractFactory(hardhatArtifact.abi, hardhatArtifact.bytecode, signer);
|
|
25
|
-
const contract = await factory.deploy();
|
|
29
|
+
const contract = await factory.deploy(...constructorArgs);
|
|
26
30
|
await contract.waitForDeployment();
|
|
27
31
|
return contract as Contract;
|
|
28
32
|
};
|
|
@@ -43,8 +47,10 @@ const mock_checkIsTestnet = async (fnName: string, provider: HardhatEthersProvid
|
|
|
43
47
|
// Get bytecode at ZK_VERIFIER_ADDRESS
|
|
44
48
|
const bytecode = await provider.getCode(MOCKS_ZK_VERIFIER_ADDRESS);
|
|
45
49
|
|
|
46
|
-
//
|
|
47
|
-
|
|
50
|
+
// provider.getCode() (eth_getCode) returns the string "0x" -- never a zero-length string --
|
|
51
|
+
// when there is no contract deployed at the address, so a real "no bytecode" check must
|
|
52
|
+
// compare against that, not against string length 0.
|
|
53
|
+
const isTestnet = !bytecode || bytecode.length <= 2;
|
|
48
54
|
|
|
49
55
|
// Log if we are on a testnet
|
|
50
56
|
if (isTestnet) {
|
|
@@ -63,7 +69,7 @@ export const mock_getPlaintext = async (
|
|
|
63
69
|
|
|
64
70
|
// Connect to MockTaskManager
|
|
65
71
|
const taskManager = new ethers.Contract(
|
|
66
|
-
|
|
72
|
+
MOCKS_TASK_MANAGER_ADDRESS,
|
|
67
73
|
['function mockStorage(uint256) view returns (uint256)'],
|
|
68
74
|
provider
|
|
69
75
|
);
|
|
@@ -83,7 +89,7 @@ export const mock_getPlaintextExists = async (
|
|
|
83
89
|
|
|
84
90
|
// Connect to MockTaskManager
|
|
85
91
|
const taskManager = new ethers.Contract(
|
|
86
|
-
|
|
92
|
+
MOCKS_TASK_MANAGER_ADDRESS,
|
|
87
93
|
['function inMockStorage(uint256) view returns (bool)'],
|
|
88
94
|
provider
|
|
89
95
|
);
|