create-fhevm-example 1.4.5 → 1.4.6
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/README.md +3 -4
- package/contracts/concepts/antipatterns/ControlFlow.sol +160 -0
- package/contracts/concepts/antipatterns/OperationsGasNoise.sol +190 -0
- package/contracts/concepts/antipatterns/Permissions.sol +254 -0
- package/dist/scripts/commands/add-mode.d.ts.map +1 -1
- package/dist/scripts/commands/add-mode.js +10 -21
- package/dist/scripts/commands/doctor.js +4 -1
- package/dist/scripts/commands/generate-docs.js +4 -1
- package/dist/scripts/index.js +43 -24
- package/dist/scripts/shared/builders.d.ts.map +1 -1
- package/dist/scripts/shared/builders.js +8 -14
- package/dist/scripts/shared/config.d.ts.map +1 -1
- package/dist/scripts/shared/config.js +41 -12
- package/dist/scripts/shared/utils.js +2 -2
- package/package.json +1 -1
- package/test/concepts/FHEAntiPatterns.ts +2 -1
- package/test/concepts/antipatterns/ControlFlow.ts +125 -0
- package/test/concepts/antipatterns/OperationsGasNoise.ts +187 -0
- package/test/concepts/antipatterns/Permissions.ts +327 -0
- package/contracts/concepts/FHEAntiPatterns.sol +0 -300
|
@@ -58,7 +58,7 @@ const utils_1 = require("../shared/utils");
|
|
|
58
58
|
function detectHardhatProject(targetDir) {
|
|
59
59
|
const packageJsonPath = path.join(targetDir, "package.json");
|
|
60
60
|
const hardhatConfigTs = path.join(targetDir, "hardhat.config.ts");
|
|
61
|
-
const hardhatConfigJs = path.join(targetDir, "hardhat.config");
|
|
61
|
+
const hardhatConfigJs = path.join(targetDir, "hardhat.config.js");
|
|
62
62
|
if (!fs.existsSync(packageJsonPath)) {
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
@@ -106,7 +106,7 @@ function updateHardhatConfig(targetDir) {
|
|
|
106
106
|
? configPathJs
|
|
107
107
|
: null;
|
|
108
108
|
if (!actualPath) {
|
|
109
|
-
throw new Error(
|
|
109
|
+
throw new Error(utils_1.ERROR_MESSAGES.CONFIG_NOT_FOUND);
|
|
110
110
|
}
|
|
111
111
|
let content = fs.readFileSync(actualPath, "utf-8");
|
|
112
112
|
if (content.includes("@fhevm/hardhat-plugin")) {
|
|
@@ -139,12 +139,12 @@ function updateHardhatConfig(targetDir) {
|
|
|
139
139
|
function addExampleFiles(exampleName, targetDir) {
|
|
140
140
|
const example = config_1.EXAMPLES[exampleName];
|
|
141
141
|
if (!example) {
|
|
142
|
-
throw new Error(
|
|
142
|
+
throw new Error(utils_1.ERROR_MESSAGES.UNKNOWN_EXAMPLE(exampleName));
|
|
143
143
|
}
|
|
144
144
|
const rootDir = (0, utils_1.getRootDir)();
|
|
145
145
|
const contractName = (0, utils_1.getContractName)(example.contract);
|
|
146
146
|
if (!contractName) {
|
|
147
|
-
throw new Error(
|
|
147
|
+
throw new Error(utils_1.ERROR_MESSAGES.CONTRACT_NAME_FAILED);
|
|
148
148
|
}
|
|
149
149
|
const contractSource = path.join(rootDir, example.contract);
|
|
150
150
|
const testSource = path.join(rootDir, example.test);
|
|
@@ -154,15 +154,9 @@ function addExampleFiles(exampleName, targetDir) {
|
|
|
154
154
|
if (!fs.existsSync(contractsDir)) {
|
|
155
155
|
fs.mkdirSync(contractsDir, { recursive: true });
|
|
156
156
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
p.log.success(`Overwritten: ${contractName}.sol`);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
fs.copyFileSync(contractSource, contractDest);
|
|
164
|
-
p.log.success(`Added: ${contractName}.sol`);
|
|
165
|
-
}
|
|
157
|
+
const isContractOverwrite = fs.existsSync(contractDest);
|
|
158
|
+
fs.copyFileSync(contractSource, contractDest);
|
|
159
|
+
p.log.success(`${isContractOverwrite ? "Overwritten" : "Added"}: ${contractName}.sol`);
|
|
166
160
|
// Handle test file
|
|
167
161
|
const testFileName = path.basename(example.test);
|
|
168
162
|
const testDest = path.join(targetDir, "test", testFileName);
|
|
@@ -170,14 +164,9 @@ function addExampleFiles(exampleName, targetDir) {
|
|
|
170
164
|
if (!fs.existsSync(testDir)) {
|
|
171
165
|
fs.mkdirSync(testDir, { recursive: true });
|
|
172
166
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
fs.copyFileSync(testSource, testDest);
|
|
179
|
-
p.log.success(`Added: ${testFileName}`);
|
|
180
|
-
}
|
|
167
|
+
const isTestOverwrite = fs.existsSync(testDest);
|
|
168
|
+
fs.copyFileSync(testSource, testDest);
|
|
169
|
+
p.log.success(`${isTestOverwrite ? "Overwritten" : "Added"}: ${testFileName}`);
|
|
181
170
|
// Handle contract dependencies
|
|
182
171
|
if (example.dependencies) {
|
|
183
172
|
p.log.message("");
|
package/dist/scripts/index.js
CHANGED
|
@@ -56,6 +56,17 @@ const builders_1 = require("./shared/builders");
|
|
|
56
56
|
const ui_1 = require("./shared/ui");
|
|
57
57
|
const add_mode_1 = require("./commands/add-mode");
|
|
58
58
|
const generate_docs_1 = require("./commands/generate-docs");
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// INTERACTIVE MODE
|
|
61
|
+
// =============================================================================
|
|
62
|
+
/** Helper to check if user cancelled and show message */
|
|
63
|
+
function checkCancel(value) {
|
|
64
|
+
if (p.isCancel(value)) {
|
|
65
|
+
p.cancel("Operation cancelled.");
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
59
70
|
/** Prompts user to select mode and returns project configuration */
|
|
60
71
|
async function getProjectConfig() {
|
|
61
72
|
// Build options - docs only available in local dev mode
|
|
@@ -83,22 +94,16 @@ async function getProjectConfig() {
|
|
|
83
94
|
message: "What would you like to create?",
|
|
84
95
|
options,
|
|
85
96
|
});
|
|
86
|
-
if (
|
|
87
|
-
p.cancel("Operation cancelled.");
|
|
97
|
+
if (checkCancel(mode))
|
|
88
98
|
return null;
|
|
89
|
-
}
|
|
90
99
|
let name;
|
|
91
100
|
if (mode === "single") {
|
|
92
101
|
const category = await (0, ui_1.promptSelectCategory)();
|
|
93
|
-
if (
|
|
94
|
-
p.cancel("Operation cancelled.");
|
|
102
|
+
if (checkCancel(category))
|
|
95
103
|
return null;
|
|
96
|
-
}
|
|
97
104
|
const example = await (0, ui_1.promptSelectExampleFromCategory)(category);
|
|
98
|
-
if (
|
|
99
|
-
p.cancel("Operation cancelled.");
|
|
105
|
+
if (checkCancel(example))
|
|
100
106
|
return null;
|
|
101
|
-
}
|
|
102
107
|
name = example;
|
|
103
108
|
}
|
|
104
109
|
else if (mode === "docs") {
|
|
@@ -108,10 +113,8 @@ async function getProjectConfig() {
|
|
|
108
113
|
}
|
|
109
114
|
else {
|
|
110
115
|
const category = await (0, ui_1.promptSelectCategoryProject)();
|
|
111
|
-
if (
|
|
112
|
-
p.cancel("Operation cancelled.");
|
|
116
|
+
if (checkCancel(category))
|
|
113
117
|
return null;
|
|
114
|
-
}
|
|
115
118
|
name = category;
|
|
116
119
|
}
|
|
117
120
|
const projectName = await p.text({
|
|
@@ -119,27 +122,21 @@ async function getProjectConfig() {
|
|
|
119
122
|
placeholder: `my-${name}-project`,
|
|
120
123
|
defaultValue: `my-${name}-project`,
|
|
121
124
|
});
|
|
122
|
-
if (
|
|
123
|
-
p.cancel("Operation cancelled.");
|
|
125
|
+
if (checkCancel(projectName))
|
|
124
126
|
return null;
|
|
125
|
-
}
|
|
126
127
|
const outputDir = await p.text({
|
|
127
128
|
message: "Output directory:",
|
|
128
129
|
placeholder: `./${projectName}`,
|
|
129
130
|
defaultValue: `./${projectName}`,
|
|
130
131
|
});
|
|
131
|
-
if (
|
|
132
|
-
p.cancel("Operation cancelled.");
|
|
132
|
+
if (checkCancel(outputDir))
|
|
133
133
|
return null;
|
|
134
|
-
}
|
|
135
134
|
const shouldInstall = await p.confirm({
|
|
136
135
|
message: "Install dependencies and run tests?",
|
|
137
136
|
initialValue: false,
|
|
138
137
|
});
|
|
139
|
-
if (
|
|
140
|
-
p.cancel("Operation cancelled.");
|
|
138
|
+
if (checkCancel(shouldInstall))
|
|
141
139
|
return null;
|
|
142
|
-
}
|
|
143
140
|
return {
|
|
144
141
|
mode: mode,
|
|
145
142
|
name,
|
|
@@ -205,12 +202,22 @@ async function runInteractiveMode() {
|
|
|
205
202
|
}
|
|
206
203
|
function parseArgs(args) {
|
|
207
204
|
const parsed = {};
|
|
205
|
+
// Short flag mappings
|
|
206
|
+
const shortFlags = {
|
|
207
|
+
"-e": "example",
|
|
208
|
+
"-c": "category",
|
|
209
|
+
"-o": "output",
|
|
210
|
+
"-a": "add",
|
|
211
|
+
"-i": "install",
|
|
212
|
+
"-h": "help",
|
|
213
|
+
};
|
|
208
214
|
for (let i = 0; i < args.length; i++) {
|
|
209
215
|
const arg = args[i];
|
|
216
|
+
// Handle long flags (--example, --category, etc.)
|
|
210
217
|
if (arg.startsWith("--")) {
|
|
211
218
|
const key = arg.slice(2);
|
|
212
219
|
const nextArg = args[i + 1];
|
|
213
|
-
if (nextArg && !nextArg.startsWith("
|
|
220
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
214
221
|
parsed[key] = nextArg;
|
|
215
222
|
i++;
|
|
216
223
|
}
|
|
@@ -218,8 +225,20 @@ function parseArgs(args) {
|
|
|
218
225
|
parsed[key] = true;
|
|
219
226
|
}
|
|
220
227
|
}
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
// Handle short flags (-e, -c, -o, -a, -i, -h)
|
|
229
|
+
else if (arg.startsWith("-") && shortFlags[arg]) {
|
|
230
|
+
const key = shortFlags[arg];
|
|
231
|
+
const nextArg = args[i + 1];
|
|
232
|
+
// Flags that take values: -e, -c, -o
|
|
233
|
+
if (["example", "category", "output"].includes(key) &&
|
|
234
|
+
nextArg &&
|
|
235
|
+
!nextArg.startsWith("-")) {
|
|
236
|
+
parsed[key] = nextArg;
|
|
237
|
+
i++;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
parsed[key] = true;
|
|
241
|
+
}
|
|
223
242
|
}
|
|
224
243
|
}
|
|
225
244
|
return parsed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../../scripts/shared/builders.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../../scripts/shared/builders.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiEH;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,IAAI,CAkDN;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,IAAI,CAwEN;AAMD;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EAAE,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA6Df"}
|
|
@@ -69,7 +69,8 @@ function copyDependencies(dependencies, outputDir) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
* Initializes git repository (optional,
|
|
72
|
+
* Initializes git repository (optional, logs warning if skipped)
|
|
73
|
+
* @returns true if git init succeeded, false otherwise
|
|
73
74
|
*/
|
|
74
75
|
function initGitRepo(outputDir) {
|
|
75
76
|
try {
|
|
@@ -77,9 +78,11 @@ function initGitRepo(outputDir) {
|
|
|
77
78
|
cwd: outputDir,
|
|
78
79
|
stdio: "ignore",
|
|
79
80
|
});
|
|
81
|
+
return true;
|
|
80
82
|
}
|
|
81
83
|
catch {
|
|
82
|
-
|
|
84
|
+
utils_1.log.dim(" ⚠️ Git initialization skipped (git not available)");
|
|
85
|
+
return false;
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
// =============================================================================
|
|
@@ -208,18 +211,9 @@ async function createLocalTestProject(exampleNames, outputDir) {
|
|
|
208
211
|
Object.assign(allNpmDeps, example.npmDependencies);
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
|
-
// 3. Copy dependencies
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (fs.existsSync(depFullPath)) {
|
|
215
|
-
const relativePath = depPath.replace(/^contracts\//, "");
|
|
216
|
-
const depDestPath = path.join(outputDir, "contracts", relativePath);
|
|
217
|
-
const depDestDir = path.dirname(depDestPath);
|
|
218
|
-
if (!fs.existsSync(depDestDir)) {
|
|
219
|
-
fs.mkdirSync(depDestDir, { recursive: true });
|
|
220
|
-
}
|
|
221
|
-
fs.copyFileSync(depFullPath, depDestPath);
|
|
222
|
-
}
|
|
214
|
+
// 3. Copy dependencies (reuse helper function)
|
|
215
|
+
if (allContractDeps.size > 0) {
|
|
216
|
+
copyDependencies(Array.from(allContractDeps), outputDir);
|
|
223
217
|
}
|
|
224
218
|
// 4. Finalize project
|
|
225
219
|
(0, generators_1.updateProjectPackageJson)(outputDir, "fhevm-test-project", `Testing ${exampleNames.length} examples`, Object.keys(allNpmDeps).length > 0 ? allNpmDeps : undefined);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../scripts/shared/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAMD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../scripts/shared/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAMD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CA+SlD,CAAC;AAMF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAiKrD,CAAC;AAMF;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAE3C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAElE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEpE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|
|
@@ -152,14 +152,6 @@ exports.EXAMPLES = {
|
|
|
152
152
|
docsOutput: "docs/concepts/fhe-access-control.md",
|
|
153
153
|
title: "FHE Access Control"
|
|
154
154
|
},
|
|
155
|
-
"fhe-anti-patterns": {
|
|
156
|
-
contract: "contracts/concepts/FHEAntiPatterns.sol",
|
|
157
|
-
test: "test/concepts/FHEAntiPatterns.ts",
|
|
158
|
-
description: "Comprehensive guide to FHE anti-patterns and their solutions. Covers 9 critical mistakes: using if/else on encrypted values, incorrect permission patterns, require() statements that leak info, unbounded loops, noise accumulation, and deprecated APIs. Each pattern shows both ❌ WRONG and ✅ CORRECT implementations.",
|
|
159
|
-
category: "Concepts",
|
|
160
|
-
docsOutput: "docs/concepts/fhe-anti-patterns.md",
|
|
161
|
-
title: "FHE Anti Patterns"
|
|
162
|
-
},
|
|
163
155
|
"fhe-handles": {
|
|
164
156
|
contract: "contracts/concepts/FHEHandles.sol",
|
|
165
157
|
test: "test/concepts/FHEHandles.ts",
|
|
@@ -176,6 +168,30 @@ exports.EXAMPLES = {
|
|
|
176
168
|
docsOutput: "docs/concepts/fhe-input-proof.md",
|
|
177
169
|
title: "FHE Input Proof"
|
|
178
170
|
},
|
|
171
|
+
"control-flow": {
|
|
172
|
+
contract: "contracts/concepts/antipatterns/ControlFlow.sol",
|
|
173
|
+
test: "test/concepts/antipatterns/ControlFlow.ts",
|
|
174
|
+
description: "Control flow anti-patterns in FHE development. Demonstrates common mistakes when using conditional logic and loops with encrypted values.",
|
|
175
|
+
category: "Concepts - Antipatterns",
|
|
176
|
+
docsOutput: "docs/concepts/antipatterns/control-flow.md",
|
|
177
|
+
title: "Control Flow"
|
|
178
|
+
},
|
|
179
|
+
"operations-gas-noise": {
|
|
180
|
+
contract: "contracts/concepts/antipatterns/OperationsGasNoise.sol",
|
|
181
|
+
test: "test/concepts/antipatterns/OperationsGasNoise.ts",
|
|
182
|
+
description: "Operations, gas, and noise anti-patterns in FHE development. Demonstrates performance issues, side-channel leaks, and inefficient encrypted computation patterns.",
|
|
183
|
+
category: "Concepts - Antipatterns",
|
|
184
|
+
docsOutput: "docs/concepts/antipatterns/operations-gas-noise.md",
|
|
185
|
+
title: "Operations Gas Noise"
|
|
186
|
+
},
|
|
187
|
+
"permissions": {
|
|
188
|
+
contract: "contracts/concepts/antipatterns/Permissions.sol",
|
|
189
|
+
test: "test/concepts/antipatterns/Permissions.ts",
|
|
190
|
+
description: "Permission management anti-patterns in FHE development. Demonstrates common mistakes with allowThis, allow, and permission propagation across transfers and contracts.",
|
|
191
|
+
category: "Concepts - Antipatterns",
|
|
192
|
+
docsOutput: "docs/concepts/antipatterns/permissions.md",
|
|
193
|
+
title: "Permissions"
|
|
194
|
+
},
|
|
179
195
|
"encrypted-lottery": {
|
|
180
196
|
contract: "contracts/gaming/EncryptedLottery.sol",
|
|
181
197
|
test: "test/gaming/EncryptedLottery.ts",
|
|
@@ -368,10 +384,6 @@ exports.CATEGORIES = {
|
|
|
368
384
|
sol: "contracts/concepts/FHEAccessControl.sol",
|
|
369
385
|
test: "test/concepts/FHEAccessControl.ts",
|
|
370
386
|
},
|
|
371
|
-
{
|
|
372
|
-
sol: "contracts/concepts/FHEAntiPatterns.sol",
|
|
373
|
-
test: "test/concepts/FHEAntiPatterns.ts",
|
|
374
|
-
},
|
|
375
387
|
{
|
|
376
388
|
sol: "contracts/concepts/FHEHandles.sol",
|
|
377
389
|
test: "test/concepts/FHEHandles.ts",
|
|
@@ -382,6 +394,23 @@ exports.CATEGORIES = {
|
|
|
382
394
|
}
|
|
383
395
|
],
|
|
384
396
|
},
|
|
397
|
+
conceptsantipatterns: {
|
|
398
|
+
name: "Concepts - Antipatterns Examples",
|
|
399
|
+
contracts: [
|
|
400
|
+
{
|
|
401
|
+
sol: "contracts/concepts/antipatterns/ControlFlow.sol",
|
|
402
|
+
test: "test/concepts/antipatterns/ControlFlow.ts",
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
sol: "contracts/concepts/antipatterns/OperationsGasNoise.sol",
|
|
406
|
+
test: "test/concepts/antipatterns/OperationsGasNoise.ts",
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
sol: "contracts/concepts/antipatterns/Permissions.sol",
|
|
410
|
+
test: "test/concepts/antipatterns/Permissions.ts",
|
|
411
|
+
}
|
|
412
|
+
],
|
|
413
|
+
},
|
|
385
414
|
gaming: {
|
|
386
415
|
name: "Gaming Examples",
|
|
387
416
|
contracts: [
|
|
@@ -108,8 +108,8 @@ exports.FHEVM_DEPENDENCIES = {
|
|
|
108
108
|
"@fhevm/solidity": "^0.9.1",
|
|
109
109
|
},
|
|
110
110
|
devDependencies: {
|
|
111
|
-
"@fhevm/hardhat-plugin": "^0.3.0-
|
|
112
|
-
"@zama-fhe/relayer-sdk": "^0.3.0-
|
|
111
|
+
"@fhevm/hardhat-plugin": "^0.3.0-3",
|
|
112
|
+
"@zama-fhe/relayer-sdk": "^0.3.0-6",
|
|
113
113
|
},
|
|
114
114
|
};
|
|
115
115
|
exports.CATEGORY_ORDER = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fhevm-example",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
4
4
|
"description": "Create FHEVM example projects with a single command - A comprehensive toolkit for building privacy-preserving smart contracts",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-fhevm-example": "./dist/scripts/index.js"
|
|
@@ -26,7 +26,8 @@ describe("FHEAntiPatterns", function () {
|
|
|
26
26
|
|
|
27
27
|
before(async function () {
|
|
28
28
|
if (!hre.fhevm.isMock) {
|
|
29
|
-
|
|
29
|
+
console.warn(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
30
|
+
this.skip();
|
|
30
31
|
}
|
|
31
32
|
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
32
33
|
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FhevmType,
|
|
3
|
+
HardhatFhevmRuntimeEnvironment,
|
|
4
|
+
} from "@fhevm/hardhat-plugin";
|
|
5
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import { ethers } from "hardhat";
|
|
8
|
+
import * as hre from "hardhat";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
FHEControlFlowAntiPatterns,
|
|
12
|
+
FHEControlFlowAntiPatterns__factory,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import type { Signers } from "./types";
|
|
15
|
+
|
|
16
|
+
async function deployFixture() {
|
|
17
|
+
const factory = (await ethers.getContractFactory(
|
|
18
|
+
"FHEControlFlowAntiPatterns"
|
|
19
|
+
)) as FHEControlFlowAntiPatterns__factory;
|
|
20
|
+
const contract = (await factory.deploy()) as FHEControlFlowAntiPatterns;
|
|
21
|
+
const contractAddress = await contract.getAddress();
|
|
22
|
+
return { contract, contractAddress };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @notice Tests for FHE control flow anti-patterns
|
|
27
|
+
* Demonstrates wrong and correct patterns for conditional logic and loops
|
|
28
|
+
*/
|
|
29
|
+
describe("FHEControlFlowAntiPatterns", function () {
|
|
30
|
+
let contract: FHEControlFlowAntiPatterns;
|
|
31
|
+
let contractAddress: string;
|
|
32
|
+
let signers: Signers;
|
|
33
|
+
|
|
34
|
+
before(async function () {
|
|
35
|
+
if (!hre.fhevm.isMock) {
|
|
36
|
+
throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
|
|
37
|
+
}
|
|
38
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
39
|
+
signers = { owner: ethSigners[0], alice: ethSigners[1] };
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(async function () {
|
|
43
|
+
const deployment = await deployFixture();
|
|
44
|
+
contractAddress = deployment.contractAddress;
|
|
45
|
+
contract = deployment.contract;
|
|
46
|
+
|
|
47
|
+
// Initialize contract with test values
|
|
48
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
49
|
+
|
|
50
|
+
// Create encrypted input for balance only (threshold is fixed at 100)
|
|
51
|
+
const input = await fhevm
|
|
52
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
53
|
+
.add32(50) // balance
|
|
54
|
+
.encrypt();
|
|
55
|
+
|
|
56
|
+
await contract
|
|
57
|
+
.connect(signers.alice)
|
|
58
|
+
.initialize(input.handles[0], input.inputProof);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("Pattern 1: If/Else Branching", function () {
|
|
62
|
+
it("should execute correctConditional without leaking information", async function () {
|
|
63
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
64
|
+
|
|
65
|
+
await contract.connect(signers.alice).correctConditional();
|
|
66
|
+
|
|
67
|
+
const encrypted = await contract.getBalance();
|
|
68
|
+
const decrypted = await fhevm.userDecryptEuint(
|
|
69
|
+
FhevmType.euint32,
|
|
70
|
+
encrypted,
|
|
71
|
+
contractAddress,
|
|
72
|
+
signers.alice
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Balance (50) is not above threshold (100), so no penalty applied
|
|
76
|
+
expect(decrypted).to.equal(50);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("wrongBranching should return placeholder value", async function () {
|
|
80
|
+
const result = await contract.wrongBranching.staticCall();
|
|
81
|
+
expect(result).to.equal(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("Pattern 2: Require/Revert", function () {
|
|
86
|
+
it("should return encrypted boolean for validation", async function () {
|
|
87
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
88
|
+
|
|
89
|
+
// Execute validation (stores result)
|
|
90
|
+
await contract.connect(signers.alice).correctValidation();
|
|
91
|
+
|
|
92
|
+
// Get result via getter
|
|
93
|
+
const encrypted = await contract.getValidationResult();
|
|
94
|
+
const decrypted = await fhevm.userDecryptEbool(
|
|
95
|
+
encrypted,
|
|
96
|
+
contractAddress,
|
|
97
|
+
signers.alice
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Balance (50) < 100, so should be false
|
|
101
|
+
expect(decrypted).to.equal(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("wrongRequire should return explanation string", async function () {
|
|
105
|
+
const result = await contract.wrongRequire();
|
|
106
|
+
expect(result).to.include("doesn't work with encrypted values");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("Pattern 3: Encrypted Loop Iterations", function () {
|
|
111
|
+
it("should use fixed iterations with FHE.select", async function () {
|
|
112
|
+
const fhevm: HardhatFhevmRuntimeEnvironment = hre.fhevm;
|
|
113
|
+
|
|
114
|
+
await contract.connect(signers.alice).correctFixedIterations();
|
|
115
|
+
|
|
116
|
+
// Result should count up to min(balance, MAX_ITERATIONS)
|
|
117
|
+
// Balance is 50, MAX_ITERATIONS is 5, so result should be 5
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("wrongEncryptedLoop should return explanation string", async function () {
|
|
121
|
+
const result = await contract.wrongEncryptedLoop();
|
|
122
|
+
expect(result).to.include("Loop iterations leak");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|