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.
Files changed (125) hide show
  1. package/contracts/advanced/BlindAuction.sol +255 -0
  2. package/contracts/advanced/EncryptedEscrow.sol +315 -0
  3. package/contracts/advanced/HiddenVoting.sol +231 -0
  4. package/contracts/advanced/PrivateKYC.sol +309 -0
  5. package/contracts/advanced/PrivatePayroll.sol +285 -0
  6. package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
  7. package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
  8. package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
  9. package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
  10. package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
  11. package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
  12. package/contracts/basic/encryption/FHECounter.sol +54 -0
  13. package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
  14. package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
  15. package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
  16. package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
  17. package/contracts/concepts/FHEAccessControl.sol +94 -0
  18. package/contracts/concepts/FHEAntiPatterns.sol +329 -0
  19. package/contracts/concepts/FHEHandles.sol +128 -0
  20. package/contracts/concepts/FHEInputProof.sol +104 -0
  21. package/contracts/gaming/EncryptedLottery.sol +298 -0
  22. package/contracts/gaming/EncryptedPoker.sol +337 -0
  23. package/contracts/gaming/RockPaperScissors.sol +213 -0
  24. package/contracts/openzeppelin/ERC7984.sol +85 -0
  25. package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
  26. package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
  27. package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
  28. package/contracts/openzeppelin/VestingWallet.sol +147 -0
  29. package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
  30. package/dist/scripts/commands/add-mode.d.ts.map +1 -0
  31. package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
  32. package/dist/scripts/commands/doctor.d.ts.map +1 -0
  33. package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
  34. package/dist/scripts/commands/generate-config.d.ts.map +1 -0
  35. package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
  36. package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
  37. package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
  38. package/dist/scripts/commands/maintenance.d.ts.map +1 -0
  39. package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
  40. package/dist/scripts/index.js +14 -33
  41. package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
  42. package/dist/scripts/shared/builders.d.ts.map +1 -0
  43. package/dist/scripts/{builders.js → shared/builders.js} +49 -30
  44. package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
  45. package/dist/scripts/shared/config.d.ts.map +1 -0
  46. package/dist/scripts/{config.js → shared/config.js} +48 -59
  47. package/dist/scripts/shared/generators.d.ts +42 -0
  48. package/dist/scripts/shared/generators.d.ts.map +1 -0
  49. package/dist/scripts/{utils.js → shared/generators.js} +34 -271
  50. package/dist/scripts/shared/ui.d.ts.map +1 -0
  51. package/dist/scripts/{ui.js → shared/ui.js} +3 -2
  52. package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
  53. package/dist/scripts/shared/utils.d.ts.map +1 -0
  54. package/dist/scripts/shared/utils.js +228 -0
  55. package/fhevm-hardhat-template/.eslintignore +26 -0
  56. package/fhevm-hardhat-template/.eslintrc.yml +21 -0
  57. package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
  58. package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
  59. package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
  60. package/fhevm-hardhat-template/.prettierignore +25 -0
  61. package/fhevm-hardhat-template/.prettierrc.yml +15 -0
  62. package/fhevm-hardhat-template/.solcover.js +4 -0
  63. package/fhevm-hardhat-template/.solhint.json +12 -0
  64. package/fhevm-hardhat-template/.solhintignore +3 -0
  65. package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
  66. package/fhevm-hardhat-template/.vscode/settings.json +9 -0
  67. package/fhevm-hardhat-template/LICENSE +33 -0
  68. package/fhevm-hardhat-template/README.md +110 -0
  69. package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
  70. package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
  71. package/fhevm-hardhat-template/hardhat.config.ts +90 -0
  72. package/fhevm-hardhat-template/package-lock.json +10405 -0
  73. package/fhevm-hardhat-template/package.json +104 -0
  74. package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
  75. package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
  76. package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
  77. package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
  78. package/fhevm-hardhat-template/tsconfig.json +23 -0
  79. package/package.json +11 -8
  80. package/test/advanced/BlindAuction.ts +246 -0
  81. package/test/advanced/EncryptedEscrow.ts +295 -0
  82. package/test/advanced/HiddenVoting.ts +268 -0
  83. package/test/advanced/PrivateKYC.ts +382 -0
  84. package/test/advanced/PrivatePayroll.ts +253 -0
  85. package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
  86. package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
  87. package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
  88. package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
  89. package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
  90. package/test/basic/encryption/EncryptSingleValue.ts +124 -0
  91. package/test/basic/encryption/FHECounter.ts +112 -0
  92. package/test/basic/fhe-operations/FHEAdd.ts +97 -0
  93. package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
  94. package/test/basic/fhe-operations/FHEComparison.ts +167 -0
  95. package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
  96. package/test/concepts/FHEAccessControl.ts +154 -0
  97. package/test/concepts/FHEAntiPatterns.ts +111 -0
  98. package/test/concepts/FHEHandles.ts +156 -0
  99. package/test/concepts/FHEInputProof.ts +151 -0
  100. package/test/gaming/EncryptedLottery.ts +214 -0
  101. package/test/gaming/EncryptedPoker.ts +349 -0
  102. package/test/gaming/RockPaperScissors.ts +205 -0
  103. package/test/openzeppelin/ERC7984.ts +142 -0
  104. package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
  105. package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
  106. package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
  107. package/test/openzeppelin/VestingWallet.ts +89 -0
  108. package/dist/scripts/add-mode.d.ts.map +0 -1
  109. package/dist/scripts/builders.d.ts.map +0 -1
  110. package/dist/scripts/config.d.ts.map +0 -1
  111. package/dist/scripts/doctor.d.ts.map +0 -1
  112. package/dist/scripts/generate-config.d.ts.map +0 -1
  113. package/dist/scripts/generate-docs.d.ts.map +0 -1
  114. package/dist/scripts/help.d.ts +0 -9
  115. package/dist/scripts/help.d.ts.map +0 -1
  116. package/dist/scripts/help.js +0 -73
  117. package/dist/scripts/maintenance.d.ts.map +0 -1
  118. package/dist/scripts/ui.d.ts.map +0 -1
  119. package/dist/scripts/utils.d.ts.map +0 -1
  120. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  121. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  122. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  123. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  124. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  125. /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
+ });