create-fhevm-example 1.3.2 → 1.4.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/contracts/advanced/BlindAuction.sol +255 -0
- package/contracts/advanced/EncryptedEscrow.sol +315 -0
- package/contracts/advanced/HiddenVoting.sol +231 -0
- package/contracts/advanced/PrivateKYC.sol +309 -0
- package/contracts/advanced/PrivatePayroll.sol +285 -0
- package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
- package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
- package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
- package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
- package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
- package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
- package/contracts/basic/encryption/FHECounter.sol +54 -0
- package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
- package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
- package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
- package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
- package/contracts/concepts/FHEAccessControl.sol +94 -0
- package/contracts/concepts/FHEAntiPatterns.sol +329 -0
- package/contracts/concepts/FHEHandles.sol +128 -0
- package/contracts/concepts/FHEInputProof.sol +104 -0
- package/contracts/gaming/EncryptedLottery.sol +298 -0
- package/contracts/gaming/EncryptedPoker.sol +337 -0
- package/contracts/gaming/RockPaperScissors.sol +213 -0
- package/contracts/openzeppelin/ERC7984.sol +85 -0
- package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
- package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
- package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
- package/contracts/openzeppelin/VestingWallet.sol +147 -0
- package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
- package/dist/scripts/commands/add-mode.d.ts.map +1 -0
- package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
- package/dist/scripts/commands/doctor.d.ts.map +1 -0
- package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
- package/dist/scripts/commands/generate-config.d.ts.map +1 -0
- package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
- package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
- package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
- package/dist/scripts/commands/maintenance.d.ts.map +1 -0
- package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
- package/dist/scripts/index.js +14 -33
- package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
- package/dist/scripts/shared/builders.d.ts.map +1 -0
- package/dist/scripts/{builders.js → shared/builders.js} +49 -30
- package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
- package/dist/scripts/shared/config.d.ts.map +1 -0
- package/dist/scripts/{config.js → shared/config.js} +48 -59
- package/dist/scripts/shared/generators.d.ts +42 -0
- package/dist/scripts/shared/generators.d.ts.map +1 -0
- package/dist/scripts/{utils.js → shared/generators.js} +34 -271
- package/dist/scripts/shared/ui.d.ts.map +1 -0
- package/dist/scripts/{ui.js → shared/ui.js} +3 -2
- package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
- package/dist/scripts/shared/utils.d.ts.map +1 -0
- package/dist/scripts/shared/utils.js +228 -0
- package/fhevm-hardhat-template/.eslintignore +26 -0
- package/fhevm-hardhat-template/.eslintrc.yml +21 -0
- package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
- package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
- package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
- package/fhevm-hardhat-template/.prettierignore +25 -0
- package/fhevm-hardhat-template/.prettierrc.yml +15 -0
- package/fhevm-hardhat-template/.solcover.js +4 -0
- package/fhevm-hardhat-template/.solhint.json +12 -0
- package/fhevm-hardhat-template/.solhintignore +3 -0
- package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
- package/fhevm-hardhat-template/.vscode/settings.json +9 -0
- package/fhevm-hardhat-template/LICENSE +33 -0
- package/fhevm-hardhat-template/README.md +110 -0
- package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
- package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
- package/fhevm-hardhat-template/hardhat.config.ts +90 -0
- package/fhevm-hardhat-template/package-lock.json +10405 -0
- package/fhevm-hardhat-template/package.json +104 -0
- package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
- package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
- package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
- package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
- package/fhevm-hardhat-template/tsconfig.json +23 -0
- package/package.json +11 -8
- package/test/advanced/BlindAuction.ts +246 -0
- package/test/advanced/EncryptedEscrow.ts +295 -0
- package/test/advanced/HiddenVoting.ts +268 -0
- package/test/advanced/PrivateKYC.ts +382 -0
- package/test/advanced/PrivatePayroll.ts +253 -0
- package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
- package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
- package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
- package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
- package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
- package/test/basic/encryption/EncryptSingleValue.ts +124 -0
- package/test/basic/encryption/FHECounter.ts +112 -0
- package/test/basic/fhe-operations/FHEAdd.ts +97 -0
- package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
- package/test/basic/fhe-operations/FHEComparison.ts +167 -0
- package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
- package/test/concepts/FHEAccessControl.ts +154 -0
- package/test/concepts/FHEAntiPatterns.ts +111 -0
- package/test/concepts/FHEHandles.ts +156 -0
- package/test/concepts/FHEInputProof.ts +151 -0
- package/test/gaming/EncryptedLottery.ts +214 -0
- package/test/gaming/EncryptedPoker.ts +349 -0
- package/test/gaming/RockPaperScissors.ts +205 -0
- package/test/openzeppelin/ERC7984.ts +142 -0
- package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
- package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
- package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
- package/test/openzeppelin/VestingWallet.ts +89 -0
- package/dist/scripts/add-mode.d.ts.map +0 -1
- package/dist/scripts/builders.d.ts.map +0 -1
- package/dist/scripts/config.d.ts.map +0 -1
- package/dist/scripts/doctor.d.ts.map +0 -1
- package/dist/scripts/generate-config.d.ts.map +0 -1
- package/dist/scripts/generate-docs.d.ts.map +0 -1
- package/dist/scripts/help.d.ts +0 -9
- package/dist/scripts/help.d.ts.map +0 -1
- package/dist/scripts/help.js +0 -73
- package/dist/scripts/maintenance.d.ts.map +0 -1
- package/dist/scripts/ui.d.ts.map +0 -1
- package/dist/scripts/utils.d.ts.map +0 -1
- /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
- /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
- /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
- /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
- /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
- /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { ethers, fhevm } from "hardhat";
|
|
3
|
+
import * as hre from "hardhat";
|
|
4
|
+
|
|
5
|
+
describe("HiddenVoting", function () {
|
|
6
|
+
let voting: any;
|
|
7
|
+
let owner: any;
|
|
8
|
+
let voter1: any;
|
|
9
|
+
let voter2: any;
|
|
10
|
+
let voter3: any;
|
|
11
|
+
|
|
12
|
+
const PROPOSAL = "Should we upgrade to v2?";
|
|
13
|
+
|
|
14
|
+
async function deployVoting(durationSeconds: number = 60) {
|
|
15
|
+
const votingContract = await ethers.deployContract("HiddenVoting", [
|
|
16
|
+
PROPOSAL,
|
|
17
|
+
durationSeconds,
|
|
18
|
+
]);
|
|
19
|
+
return votingContract;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
beforeEach(async function () {
|
|
23
|
+
[owner, voter1, voter2, voter3] = await ethers.getSigners();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("Voting Creation", function () {
|
|
27
|
+
it("should create voting with correct parameters", async function () {
|
|
28
|
+
voting = await deployVoting(120);
|
|
29
|
+
|
|
30
|
+
expect(await voting.votingState()).to.equal(0); // Active
|
|
31
|
+
expect(await voting.proposal()).to.equal(PROPOSAL);
|
|
32
|
+
expect(await voting.voterCount()).to.equal(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should revert with empty proposal", async function () {
|
|
36
|
+
await expect(
|
|
37
|
+
ethers.deployContract("HiddenVoting", ["", 60])
|
|
38
|
+
).to.be.revertedWith("Empty proposal");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should revert with zero duration", async function () {
|
|
42
|
+
await expect(
|
|
43
|
+
ethers.deployContract("HiddenVoting", [PROPOSAL, 0])
|
|
44
|
+
).to.be.revertedWith("Duration must be positive");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("Casting Votes", function () {
|
|
49
|
+
beforeEach(async function () {
|
|
50
|
+
voting = await deployVoting(3600); // 1 hour
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should accept Yes vote (1)", async function () {
|
|
54
|
+
const encryptedVote = await fhevm
|
|
55
|
+
.createEncryptedInput(await voting.getAddress(), voter1.address)
|
|
56
|
+
.add8(1) // Yes
|
|
57
|
+
.encrypt();
|
|
58
|
+
|
|
59
|
+
await expect(
|
|
60
|
+
voting
|
|
61
|
+
.connect(voter1)
|
|
62
|
+
.vote(encryptedVote.handles[0], encryptedVote.inputProof)
|
|
63
|
+
).to.not.be.reverted;
|
|
64
|
+
|
|
65
|
+
expect(await voting.hasVoted(voter1.address)).to.be.true;
|
|
66
|
+
expect(await voting.voterCount()).to.equal(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should accept No vote (0)", async function () {
|
|
70
|
+
const encryptedVote = await fhevm
|
|
71
|
+
.createEncryptedInput(await voting.getAddress(), voter1.address)
|
|
72
|
+
.add8(0) // No
|
|
73
|
+
.encrypt();
|
|
74
|
+
|
|
75
|
+
await expect(
|
|
76
|
+
voting
|
|
77
|
+
.connect(voter1)
|
|
78
|
+
.vote(encryptedVote.handles[0], encryptedVote.inputProof)
|
|
79
|
+
).to.not.be.reverted;
|
|
80
|
+
|
|
81
|
+
expect(await voting.hasVoted(voter1.address)).to.be.true;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should reject second vote from same address", async function () {
|
|
85
|
+
const enc1 = await fhevm
|
|
86
|
+
.createEncryptedInput(await voting.getAddress(), voter1.address)
|
|
87
|
+
.add8(1)
|
|
88
|
+
.encrypt();
|
|
89
|
+
await voting.connect(voter1).vote(enc1.handles[0], enc1.inputProof);
|
|
90
|
+
|
|
91
|
+
const enc2 = await fhevm
|
|
92
|
+
.createEncryptedInput(await voting.getAddress(), voter1.address)
|
|
93
|
+
.add8(0)
|
|
94
|
+
.encrypt();
|
|
95
|
+
|
|
96
|
+
await expect(
|
|
97
|
+
voting.connect(voter1).vote(enc2.handles[0], enc2.inputProof)
|
|
98
|
+
).to.be.revertedWith("Already voted");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should track multiple voters", async function () {
|
|
102
|
+
// Voter 1 - Yes
|
|
103
|
+
const enc1 = await fhevm
|
|
104
|
+
.createEncryptedInput(await voting.getAddress(), voter1.address)
|
|
105
|
+
.add8(1)
|
|
106
|
+
.encrypt();
|
|
107
|
+
await voting.connect(voter1).vote(enc1.handles[0], enc1.inputProof);
|
|
108
|
+
|
|
109
|
+
// Voter 2 - No
|
|
110
|
+
const enc2 = await fhevm
|
|
111
|
+
.createEncryptedInput(await voting.getAddress(), voter2.address)
|
|
112
|
+
.add8(0)
|
|
113
|
+
.encrypt();
|
|
114
|
+
await voting.connect(voter2).vote(enc2.handles[0], enc2.inputProof);
|
|
115
|
+
|
|
116
|
+
// Voter 3 - Yes
|
|
117
|
+
const enc3 = await fhevm
|
|
118
|
+
.createEncryptedInput(await voting.getAddress(), voter3.address)
|
|
119
|
+
.add8(1)
|
|
120
|
+
.encrypt();
|
|
121
|
+
await voting.connect(voter3).vote(enc3.handles[0], enc3.inputProof);
|
|
122
|
+
|
|
123
|
+
expect(await voting.voterCount()).to.equal(3);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("Close Voting", function () {
|
|
128
|
+
beforeEach(async function () {
|
|
129
|
+
voting = await deployVoting(10); // 10 seconds
|
|
130
|
+
|
|
131
|
+
// Cast some votes
|
|
132
|
+
const enc1 = await fhevm
|
|
133
|
+
.createEncryptedInput(await voting.getAddress(), voter1.address)
|
|
134
|
+
.add8(1)
|
|
135
|
+
.encrypt();
|
|
136
|
+
await voting.connect(voter1).vote(enc1.handles[0], enc1.inputProof);
|
|
137
|
+
|
|
138
|
+
const enc2 = await fhevm
|
|
139
|
+
.createEncryptedInput(await voting.getAddress(), voter2.address)
|
|
140
|
+
.add8(0)
|
|
141
|
+
.encrypt();
|
|
142
|
+
await voting.connect(voter2).vote(enc2.handles[0], enc2.inputProof);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should not allow closing before time ends", async function () {
|
|
146
|
+
await expect(voting.connect(owner).closeVoting()).to.be.revertedWith(
|
|
147
|
+
"Voting not yet ended"
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should allow owner to close after time passes", async function () {
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, 11000));
|
|
153
|
+
await hre.network.provider.send("evm_increaseTime", [12]);
|
|
154
|
+
await hre.network.provider.send("evm_mine");
|
|
155
|
+
|
|
156
|
+
await expect(voting.connect(owner).closeVoting()).to.not.be.reverted;
|
|
157
|
+
expect(await voting.votingState()).to.equal(1); // Closed
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should not allow non-owner to close", async function () {
|
|
161
|
+
await hre.network.provider.send("evm_increaseTime", [12]);
|
|
162
|
+
await hre.network.provider.send("evm_mine");
|
|
163
|
+
|
|
164
|
+
await expect(voting.connect(voter1).closeVoting()).to.be.revertedWith(
|
|
165
|
+
"Only owner can call"
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("Full Voting Flow", function () {
|
|
171
|
+
it("should correctly tally votes", async function () {
|
|
172
|
+
if (!hre.fhevm.isMock) {
|
|
173
|
+
console.log("Skipping: Test requires mock environment");
|
|
174
|
+
this.skip();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Deploy with short duration
|
|
178
|
+
voting = await ethers.deployContract("HiddenVoting", [PROPOSAL, 10]);
|
|
179
|
+
|
|
180
|
+
// Cast votes: 2 Yes, 1 No
|
|
181
|
+
const enc1 = await fhevm
|
|
182
|
+
.createEncryptedInput(await voting.getAddress(), voter1.address)
|
|
183
|
+
.add8(1) // Yes
|
|
184
|
+
.encrypt();
|
|
185
|
+
await voting.connect(voter1).vote(enc1.handles[0], enc1.inputProof);
|
|
186
|
+
|
|
187
|
+
const enc2 = await fhevm
|
|
188
|
+
.createEncryptedInput(await voting.getAddress(), voter2.address)
|
|
189
|
+
.add8(0) // No
|
|
190
|
+
.encrypt();
|
|
191
|
+
await voting.connect(voter2).vote(enc2.handles[0], enc2.inputProof);
|
|
192
|
+
|
|
193
|
+
const enc3 = await fhevm
|
|
194
|
+
.createEncryptedInput(await voting.getAddress(), voter3.address)
|
|
195
|
+
.add8(1) // Yes
|
|
196
|
+
.encrypt();
|
|
197
|
+
await voting.connect(voter3).vote(enc3.handles[0], enc3.inputProof);
|
|
198
|
+
|
|
199
|
+
// Wait and close voting
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, 11000));
|
|
201
|
+
await hre.network.provider.send("evm_increaseTime", [12]);
|
|
202
|
+
await hre.network.provider.send("evm_mine");
|
|
203
|
+
|
|
204
|
+
await voting.connect(owner).closeVoting();
|
|
205
|
+
|
|
206
|
+
// Get encrypted results
|
|
207
|
+
const encYes = await voting.getEncryptedYesVotes();
|
|
208
|
+
const encNo = await voting.getEncryptedNoVotes();
|
|
209
|
+
|
|
210
|
+
// Request public decryption
|
|
211
|
+
const handles = [encYes, encNo];
|
|
212
|
+
const decryptResults = await fhevm.publicDecrypt(handles);
|
|
213
|
+
|
|
214
|
+
// Reveal results
|
|
215
|
+
await voting.revealResults(
|
|
216
|
+
decryptResults.abiEncodedClearValues,
|
|
217
|
+
decryptResults.decryptionProof
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Verify results
|
|
221
|
+
expect(await voting.votingState()).to.equal(2); // Revealed
|
|
222
|
+
expect(await voting.revealedYesVotes()).to.equal(2n);
|
|
223
|
+
expect(await voting.revealedNoVotes()).to.equal(1n);
|
|
224
|
+
expect(await voting.hasPassed()).to.be.true;
|
|
225
|
+
|
|
226
|
+
console.log(`✅ Proposal: ${await voting.proposal()}`);
|
|
227
|
+
console.log(`📊 Yes: ${await voting.revealedYesVotes()}`);
|
|
228
|
+
console.log(`📊 No: ${await voting.revealedNoVotes()}`);
|
|
229
|
+
console.log(`🏆 Passed: ${await voting.hasPassed()}`);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should handle unanimous No votes", async function () {
|
|
233
|
+
if (!hre.fhevm.isMock) {
|
|
234
|
+
this.skip();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
voting = await ethers.deployContract("HiddenVoting", [PROPOSAL, 10]);
|
|
238
|
+
|
|
239
|
+
// All vote No
|
|
240
|
+
for (const voter of [voter1, voter2, voter3]) {
|
|
241
|
+
const enc = await fhevm
|
|
242
|
+
.createEncryptedInput(await voting.getAddress(), voter.address)
|
|
243
|
+
.add8(0)
|
|
244
|
+
.encrypt();
|
|
245
|
+
await voting.connect(voter).vote(enc.handles[0], enc.inputProof);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await new Promise((resolve) => setTimeout(resolve, 11000));
|
|
249
|
+
await hre.network.provider.send("evm_increaseTime", [12]);
|
|
250
|
+
await hre.network.provider.send("evm_mine");
|
|
251
|
+
|
|
252
|
+
await voting.connect(owner).closeVoting();
|
|
253
|
+
|
|
254
|
+
const encYes = await voting.getEncryptedYesVotes();
|
|
255
|
+
const encNo = await voting.getEncryptedNoVotes();
|
|
256
|
+
const decryptResults = await fhevm.publicDecrypt([encYes, encNo]);
|
|
257
|
+
|
|
258
|
+
await voting.revealResults(
|
|
259
|
+
decryptResults.abiEncodedClearValues,
|
|
260
|
+
decryptResults.decryptionProof
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
expect(await voting.revealedYesVotes()).to.equal(0n);
|
|
264
|
+
expect(await voting.revealedNoVotes()).to.equal(3n);
|
|
265
|
+
expect(await voting.hasPassed()).to.be.false;
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
2
|
+
import { ethers, fhevm } from "hardhat";
|
|
3
|
+
import { PrivateKYC, PrivateKYC__factory } from "../types";
|
|
4
|
+
import { expect } from "chai";
|
|
5
|
+
|
|
6
|
+
type Signers = {
|
|
7
|
+
owner: HardhatEthersSigner;
|
|
8
|
+
user1: HardhatEthersSigner;
|
|
9
|
+
user2: HardhatEthersSigner;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Country codes (ISO 3166-1 numeric simplified)
|
|
13
|
+
const COUNTRY_US = 1;
|
|
14
|
+
const COUNTRY_UK = 44;
|
|
15
|
+
const COUNTRY_BLOCKED = 99;
|
|
16
|
+
|
|
17
|
+
async function deployFixture() {
|
|
18
|
+
const factory = (await ethers.getContractFactory(
|
|
19
|
+
"PrivateKYC"
|
|
20
|
+
)) as PrivateKYC__factory;
|
|
21
|
+
// Allow US and UK initially
|
|
22
|
+
const kyc = (await factory.deploy([COUNTRY_US, COUNTRY_UK])) as PrivateKYC;
|
|
23
|
+
const kycAddress = await kyc.getAddress();
|
|
24
|
+
|
|
25
|
+
return { kyc, kycAddress };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Private KYC Tests
|
|
30
|
+
*
|
|
31
|
+
* Tests encrypted identity verification and predicate proofs.
|
|
32
|
+
* Demonstrates privacy-preserving KYC without revealing personal data.
|
|
33
|
+
*/
|
|
34
|
+
describe("PrivateKYC", function () {
|
|
35
|
+
let signers: Signers;
|
|
36
|
+
let kyc: PrivateKYC;
|
|
37
|
+
let kycAddress: string;
|
|
38
|
+
|
|
39
|
+
before(async function () {
|
|
40
|
+
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
|
|
41
|
+
signers = {
|
|
42
|
+
owner: ethSigners[0],
|
|
43
|
+
user1: ethSigners[1],
|
|
44
|
+
user2: ethSigners[2],
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
beforeEach(async function () {
|
|
49
|
+
if (!fhevm.isMock) {
|
|
50
|
+
console.warn("This test suite cannot run on Sepolia Testnet");
|
|
51
|
+
this.skip();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
({ kyc, kycAddress } = await deployFixture());
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("Initialization", function () {
|
|
58
|
+
it("should initialize with correct owner", async function () {
|
|
59
|
+
expect(await kyc.owner()).to.equal(signers.owner.address);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should set allowed countries from constructor", async function () {
|
|
63
|
+
expect(await kyc.isCountryAllowed(COUNTRY_US)).to.be.true;
|
|
64
|
+
expect(await kyc.isCountryAllowed(COUNTRY_UK)).to.be.true;
|
|
65
|
+
expect(await kyc.isCountryAllowed(COUNTRY_BLOCKED)).to.be.false;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should have correct age constants", async function () {
|
|
69
|
+
expect(await kyc.MIN_AGE_ADULT()).to.equal(18);
|
|
70
|
+
expect(await kyc.MIN_AGE_DRINKING_US()).to.equal(21);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("KYC Submission", function () {
|
|
75
|
+
it("should allow user to submit encrypted KYC", async function () {
|
|
76
|
+
// User1: 25 years old, US, credit score 750
|
|
77
|
+
const enc = await fhevm
|
|
78
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
79
|
+
.add8(25) // age
|
|
80
|
+
.add8(COUNTRY_US) // country
|
|
81
|
+
.add16(750) // credit score
|
|
82
|
+
.encrypt();
|
|
83
|
+
|
|
84
|
+
await expect(
|
|
85
|
+
kyc
|
|
86
|
+
.connect(signers.user1)
|
|
87
|
+
.submitKYC(
|
|
88
|
+
enc.handles[0],
|
|
89
|
+
enc.handles[1],
|
|
90
|
+
enc.handles[2],
|
|
91
|
+
enc.inputProof
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
.to.emit(kyc, "KYCSubmitted")
|
|
95
|
+
.withArgs(signers.user1.address);
|
|
96
|
+
|
|
97
|
+
expect(await kyc.hasSubmittedKYC(signers.user1.address)).to.be.true;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should prevent duplicate KYC submission", async function () {
|
|
101
|
+
const enc1 = await fhevm
|
|
102
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
103
|
+
.add8(25)
|
|
104
|
+
.add8(COUNTRY_US)
|
|
105
|
+
.add16(750)
|
|
106
|
+
.encrypt();
|
|
107
|
+
|
|
108
|
+
await kyc
|
|
109
|
+
.connect(signers.user1)
|
|
110
|
+
.submitKYC(
|
|
111
|
+
enc1.handles[0],
|
|
112
|
+
enc1.handles[1],
|
|
113
|
+
enc1.handles[2],
|
|
114
|
+
enc1.inputProof
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const enc2 = await fhevm
|
|
118
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
119
|
+
.add8(30)
|
|
120
|
+
.add8(COUNTRY_UK)
|
|
121
|
+
.add16(800)
|
|
122
|
+
.encrypt();
|
|
123
|
+
|
|
124
|
+
await expect(
|
|
125
|
+
kyc
|
|
126
|
+
.connect(signers.user1)
|
|
127
|
+
.submitKYC(
|
|
128
|
+
enc2.handles[0],
|
|
129
|
+
enc2.handles[1],
|
|
130
|
+
enc2.handles[2],
|
|
131
|
+
enc2.inputProof
|
|
132
|
+
)
|
|
133
|
+
).to.be.revertedWith("Already verified");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should allow user to revoke their KYC", async function () {
|
|
137
|
+
const enc = await fhevm
|
|
138
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
139
|
+
.add8(25)
|
|
140
|
+
.add8(COUNTRY_US)
|
|
141
|
+
.add16(750)
|
|
142
|
+
.encrypt();
|
|
143
|
+
|
|
144
|
+
await kyc
|
|
145
|
+
.connect(signers.user1)
|
|
146
|
+
.submitKYC(
|
|
147
|
+
enc.handles[0],
|
|
148
|
+
enc.handles[1],
|
|
149
|
+
enc.handles[2],
|
|
150
|
+
enc.inputProof
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
await expect(kyc.connect(signers.user1).revokeKYC())
|
|
154
|
+
.to.emit(kyc, "KYCRevoked")
|
|
155
|
+
.withArgs(signers.user1.address);
|
|
156
|
+
|
|
157
|
+
expect(await kyc.hasSubmittedKYC(signers.user1.address)).to.be.false;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("Age Verification", function () {
|
|
162
|
+
beforeEach(async function () {
|
|
163
|
+
// User1: 25 years old (adult)
|
|
164
|
+
const enc1 = await fhevm
|
|
165
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
166
|
+
.add8(25)
|
|
167
|
+
.add8(COUNTRY_US)
|
|
168
|
+
.add16(750)
|
|
169
|
+
.encrypt();
|
|
170
|
+
|
|
171
|
+
await kyc
|
|
172
|
+
.connect(signers.user1)
|
|
173
|
+
.submitKYC(
|
|
174
|
+
enc1.handles[0],
|
|
175
|
+
enc1.handles[1],
|
|
176
|
+
enc1.handles[2],
|
|
177
|
+
enc1.inputProof
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// User2: 17 years old (minor)
|
|
181
|
+
const enc2 = await fhevm
|
|
182
|
+
.createEncryptedInput(kycAddress, signers.user2.address)
|
|
183
|
+
.add8(17)
|
|
184
|
+
.add8(COUNTRY_US)
|
|
185
|
+
.add16(0)
|
|
186
|
+
.encrypt();
|
|
187
|
+
|
|
188
|
+
await kyc
|
|
189
|
+
.connect(signers.user2)
|
|
190
|
+
.submitKYC(
|
|
191
|
+
enc2.handles[0],
|
|
192
|
+
enc2.handles[1],
|
|
193
|
+
enc2.handles[2],
|
|
194
|
+
enc2.inputProof
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should return encrypted result for age verification", async function () {
|
|
199
|
+
// Verify age 18+ returns encrypted boolean handle
|
|
200
|
+
const result = await kyc.verifyAge18.staticCall(signers.user1.address);
|
|
201
|
+
expect(result).to.not.equal(0n);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should verify 21+ for adult user", async function () {
|
|
205
|
+
const result = await kyc.verifyAge21.staticCall(signers.user1.address);
|
|
206
|
+
expect(result).to.not.equal(0n);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should verify custom age threshold", async function () {
|
|
210
|
+
const result = await kyc.verifyAgeAbove.staticCall(
|
|
211
|
+
signers.user1.address,
|
|
212
|
+
20
|
|
213
|
+
);
|
|
214
|
+
expect(result).to.not.equal(0n);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("Credit Score Verification", function () {
|
|
219
|
+
beforeEach(async function () {
|
|
220
|
+
// User1: credit score 750 (good)
|
|
221
|
+
const enc1 = await fhevm
|
|
222
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
223
|
+
.add8(25)
|
|
224
|
+
.add8(COUNTRY_US)
|
|
225
|
+
.add16(750)
|
|
226
|
+
.encrypt();
|
|
227
|
+
|
|
228
|
+
await kyc
|
|
229
|
+
.connect(signers.user1)
|
|
230
|
+
.submitKYC(
|
|
231
|
+
enc1.handles[0],
|
|
232
|
+
enc1.handles[1],
|
|
233
|
+
enc1.handles[2],
|
|
234
|
+
enc1.inputProof
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should verify good credit (700+)", async function () {
|
|
239
|
+
const result = await kyc.verifyGoodCredit.staticCall(
|
|
240
|
+
signers.user1.address
|
|
241
|
+
);
|
|
242
|
+
expect(result).to.not.equal(0n);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should verify credit above custom threshold", async function () {
|
|
246
|
+
const result = await kyc.verifyCreditAbove.staticCall(
|
|
247
|
+
signers.user1.address,
|
|
248
|
+
600
|
|
249
|
+
);
|
|
250
|
+
expect(result).to.not.equal(0n);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("Combined Verification", function () {
|
|
255
|
+
beforeEach(async function () {
|
|
256
|
+
const enc = await fhevm
|
|
257
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
258
|
+
.add8(25)
|
|
259
|
+
.add8(COUNTRY_US)
|
|
260
|
+
.add16(750)
|
|
261
|
+
.encrypt();
|
|
262
|
+
|
|
263
|
+
await kyc
|
|
264
|
+
.connect(signers.user1)
|
|
265
|
+
.submitKYC(
|
|
266
|
+
enc.handles[0],
|
|
267
|
+
enc.handles[1],
|
|
268
|
+
enc.handles[2],
|
|
269
|
+
enc.inputProof
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should verify adult with good credit combined", async function () {
|
|
274
|
+
const result = await kyc.verifyAdultWithGoodCredit.staticCall(
|
|
275
|
+
signers.user1.address
|
|
276
|
+
);
|
|
277
|
+
expect(result).to.not.equal(0n);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("Decryptable Verification", function () {
|
|
282
|
+
beforeEach(async function () {
|
|
283
|
+
const enc = await fhevm
|
|
284
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
285
|
+
.add8(25)
|
|
286
|
+
.add8(COUNTRY_US)
|
|
287
|
+
.add16(750)
|
|
288
|
+
.encrypt();
|
|
289
|
+
|
|
290
|
+
await kyc
|
|
291
|
+
.connect(signers.user1)
|
|
292
|
+
.submitKYC(
|
|
293
|
+
enc.handles[0],
|
|
294
|
+
enc.handles[1],
|
|
295
|
+
enc.handles[2],
|
|
296
|
+
enc.inputProof
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should request age verification with event", async function () {
|
|
301
|
+
await expect(kyc.requestAgeVerification(signers.user1.address, 18))
|
|
302
|
+
.to.emit(kyc, "AgeVerificationRequested")
|
|
303
|
+
.withArgs(signers.user1.address, 18);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("Admin Functions", function () {
|
|
308
|
+
it("should allow owner to update country allowlist", async function () {
|
|
309
|
+
await expect(kyc.setCountryAllowed(COUNTRY_BLOCKED, true))
|
|
310
|
+
.to.emit(kyc, "CountryAllowlistUpdated")
|
|
311
|
+
.withArgs(COUNTRY_BLOCKED, true);
|
|
312
|
+
|
|
313
|
+
expect(await kyc.isCountryAllowed(COUNTRY_BLOCKED)).to.be.true;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should allow batch country update", async function () {
|
|
317
|
+
await kyc.setCountriesAllowed([50, 51, 52], true);
|
|
318
|
+
|
|
319
|
+
expect(await kyc.isCountryAllowed(50)).to.be.true;
|
|
320
|
+
expect(await kyc.isCountryAllowed(51)).to.be.true;
|
|
321
|
+
expect(await kyc.isCountryAllowed(52)).to.be.true;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("should prevent non-owner from updating allowlist", async function () {
|
|
325
|
+
await expect(
|
|
326
|
+
kyc.connect(signers.user1).setCountryAllowed(COUNTRY_BLOCKED, true)
|
|
327
|
+
).to.be.revertedWith("Only owner");
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("View Functions", function () {
|
|
332
|
+
it("should return verification time", async function () {
|
|
333
|
+
const enc = await fhevm
|
|
334
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
335
|
+
.add8(25)
|
|
336
|
+
.add8(COUNTRY_US)
|
|
337
|
+
.add16(750)
|
|
338
|
+
.encrypt();
|
|
339
|
+
|
|
340
|
+
await kyc
|
|
341
|
+
.connect(signers.user1)
|
|
342
|
+
.submitKYC(
|
|
343
|
+
enc.handles[0],
|
|
344
|
+
enc.handles[1],
|
|
345
|
+
enc.handles[2],
|
|
346
|
+
enc.inputProof
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const verifiedAt = await kyc.getVerificationTime(signers.user1.address);
|
|
350
|
+
expect(verifiedAt).to.be.greaterThan(0n);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should allow user to get own identity handles", async function () {
|
|
354
|
+
const enc = await fhevm
|
|
355
|
+
.createEncryptedInput(kycAddress, signers.user1.address)
|
|
356
|
+
.add8(25)
|
|
357
|
+
.add8(COUNTRY_US)
|
|
358
|
+
.add16(750)
|
|
359
|
+
.encrypt();
|
|
360
|
+
|
|
361
|
+
await kyc
|
|
362
|
+
.connect(signers.user1)
|
|
363
|
+
.submitKYC(
|
|
364
|
+
enc.handles[0],
|
|
365
|
+
enc.handles[1],
|
|
366
|
+
enc.handles[2],
|
|
367
|
+
enc.inputProof
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const identity = await kyc.connect(signers.user1).getMyIdentity();
|
|
371
|
+
expect(identity.age).to.not.equal(0n);
|
|
372
|
+
expect(identity.countryCode).to.not.equal(0n);
|
|
373
|
+
expect(identity.creditScore).to.not.equal(0n);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("should prevent non-verified user from getting identity", async function () {
|
|
377
|
+
await expect(
|
|
378
|
+
kyc.connect(signers.user1).getMyIdentity()
|
|
379
|
+
).to.be.revertedWith("No KYC submitted");
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|