create-fhevm-example 1.3.2 → 1.4.1

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 +236 -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,253 @@
1
+ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
2
+ import { ethers, fhevm } from "hardhat";
3
+ import { PrivatePayroll, PrivatePayroll__factory } from "../types";
4
+ import { expect } from "chai";
5
+
6
+ type Signers = {
7
+ employer: HardhatEthersSigner;
8
+ employee1: HardhatEthersSigner;
9
+ employee2: HardhatEthersSigner;
10
+ };
11
+
12
+ const PAYMENT_PERIOD = 86400; // 1 day for testing
13
+
14
+ async function deployFixture() {
15
+ const factory = (await ethers.getContractFactory(
16
+ "PrivatePayroll"
17
+ )) as PrivatePayroll__factory;
18
+ const payroll = (await factory.deploy(PAYMENT_PERIOD)) as PrivatePayroll;
19
+ const payrollAddress = await payroll.getAddress();
20
+
21
+ return { payroll, payrollAddress };
22
+ }
23
+
24
+ /**
25
+ * Private Payroll Tests
26
+ *
27
+ * Tests confidential salary management and FHE-based payment processing.
28
+ */
29
+ describe("PrivatePayroll", function () {
30
+ let signers: Signers;
31
+ let payroll: PrivatePayroll;
32
+ let payrollAddress: string;
33
+
34
+ before(async function () {
35
+ const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
36
+ signers = {
37
+ employer: ethSigners[0],
38
+ employee1: ethSigners[1],
39
+ employee2: ethSigners[2],
40
+ };
41
+ });
42
+
43
+ beforeEach(async function () {
44
+ if (!fhevm.isMock) {
45
+ console.warn("This test suite cannot run on Sepolia Testnet");
46
+ this.skip();
47
+ }
48
+
49
+ ({ payroll, payrollAddress } = await deployFixture());
50
+ });
51
+
52
+ describe("Initialization", function () {
53
+ it("should initialize with correct parameters", async function () {
54
+ expect(await payroll.employer()).to.equal(signers.employer.address);
55
+ expect(await payroll.paymentPeriod()).to.equal(BigInt(PAYMENT_PERIOD));
56
+ expect(await payroll.getEmployeeCount()).to.equal(0n);
57
+ });
58
+
59
+ it("should default to 30 days if zero period provided", async function () {
60
+ const factory = await ethers.getContractFactory("PrivatePayroll");
61
+ const payroll30 = await factory.deploy(0);
62
+ expect(await payroll30.paymentPeriod()).to.equal(30n * 24n * 60n * 60n);
63
+ });
64
+ });
65
+
66
+ describe("Employee Management", function () {
67
+ it("should add employee with encrypted salary", async function () {
68
+ const salary = 5000n;
69
+
70
+ const encryptedSalary = await fhevm
71
+ .createEncryptedInput(payrollAddress, signers.employer.address)
72
+ .add64(salary)
73
+ .encrypt();
74
+
75
+ await expect(
76
+ payroll.addEmployee(
77
+ signers.employee1.address,
78
+ encryptedSalary.handles[0],
79
+ encryptedSalary.inputProof
80
+ )
81
+ )
82
+ .to.emit(payroll, "EmployeeAdded")
83
+ .withArgs(signers.employee1.address);
84
+
85
+ expect(await payroll.isEmployee(signers.employee1.address)).to.be.true;
86
+ expect(await payroll.getEmployeeCount()).to.equal(1n);
87
+ });
88
+
89
+ it("should prevent adding duplicate employee", async function () {
90
+ const enc1 = await fhevm
91
+ .createEncryptedInput(payrollAddress, signers.employer.address)
92
+ .add64(5000n)
93
+ .encrypt();
94
+
95
+ await payroll.addEmployee(
96
+ signers.employee1.address,
97
+ enc1.handles[0],
98
+ enc1.inputProof
99
+ );
100
+
101
+ const enc2 = await fhevm
102
+ .createEncryptedInput(payrollAddress, signers.employer.address)
103
+ .add64(6000n)
104
+ .encrypt();
105
+
106
+ await expect(
107
+ payroll.addEmployee(
108
+ signers.employee1.address,
109
+ enc2.handles[0],
110
+ enc2.inputProof
111
+ )
112
+ ).to.be.revertedWith("Already an employee");
113
+ });
114
+
115
+ it("should remove employee correctly", async function () {
116
+ const enc = await fhevm
117
+ .createEncryptedInput(payrollAddress, signers.employer.address)
118
+ .add64(5000n)
119
+ .encrypt();
120
+
121
+ await payroll.addEmployee(
122
+ signers.employee1.address,
123
+ enc.handles[0],
124
+ enc.inputProof
125
+ );
126
+
127
+ await expect(payroll.removeEmployee(signers.employee1.address))
128
+ .to.emit(payroll, "EmployeeRemoved")
129
+ .withArgs(signers.employee1.address);
130
+
131
+ expect(await payroll.isEmployee(signers.employee1.address)).to.be.false;
132
+ expect(await payroll.getEmployeeCount()).to.equal(0n);
133
+ });
134
+
135
+ it("should update employee salary", async function () {
136
+ // Add employee
137
+ const enc1 = await fhevm
138
+ .createEncryptedInput(payrollAddress, signers.employer.address)
139
+ .add64(5000n)
140
+ .encrypt();
141
+
142
+ await payroll.addEmployee(
143
+ signers.employee1.address,
144
+ enc1.handles[0],
145
+ enc1.inputProof
146
+ );
147
+
148
+ // Update salary
149
+ const enc2 = await fhevm
150
+ .createEncryptedInput(payrollAddress, signers.employer.address)
151
+ .add64(6000n)
152
+ .encrypt();
153
+
154
+ await expect(
155
+ payroll.updateSalary(
156
+ signers.employee1.address,
157
+ enc2.handles[0],
158
+ enc2.inputProof
159
+ )
160
+ )
161
+ .to.emit(payroll, "SalaryUpdated")
162
+ .withArgs(signers.employee1.address);
163
+ });
164
+ });
165
+
166
+ describe("Funding", function () {
167
+ it("should accept funds from employer", async function () {
168
+ const amount = ethers.parseEther("10");
169
+
170
+ await expect(payroll.fund({ value: amount }))
171
+ .to.emit(payroll, "ContractFunded")
172
+ .withArgs(amount);
173
+
174
+ expect(await payroll.getBalance()).to.equal(amount);
175
+ });
176
+
177
+ it("should accept direct ETH transfers", async function () {
178
+ const amount = ethers.parseEther("5");
179
+
180
+ await signers.employer.sendTransaction({
181
+ to: payrollAddress,
182
+ value: amount,
183
+ });
184
+
185
+ expect(await payroll.getBalance()).to.equal(amount);
186
+ });
187
+
188
+ it("should reject zero funding via fund()", async function () {
189
+ await expect(payroll.fund({ value: 0 })).to.be.revertedWith(
190
+ "Must send funds"
191
+ );
192
+ });
193
+ });
194
+
195
+ describe("Access Control", function () {
196
+ it("should prevent non-employer from adding employees", async function () {
197
+ const enc = await fhevm
198
+ .createEncryptedInput(payrollAddress, signers.employee1.address)
199
+ .add64(5000n)
200
+ .encrypt();
201
+
202
+ await expect(
203
+ payroll
204
+ .connect(signers.employee1)
205
+ .addEmployee(
206
+ signers.employee2.address,
207
+ enc.handles[0],
208
+ enc.inputProof
209
+ )
210
+ ).to.be.revertedWith("Only employer");
211
+ });
212
+
213
+ it("should allow employee to get their salary handle", async function () {
214
+ const enc = await fhevm
215
+ .createEncryptedInput(payrollAddress, signers.employer.address)
216
+ .add64(5000n)
217
+ .encrypt();
218
+
219
+ await payroll.addEmployee(
220
+ signers.employee1.address,
221
+ enc.handles[0],
222
+ enc.inputProof
223
+ );
224
+
225
+ // Employee should be able to call getMySalary
226
+ const salaryHandle = await payroll
227
+ .connect(signers.employee1)
228
+ .getMySalary();
229
+ expect(salaryHandle).to.not.equal(0n);
230
+ });
231
+
232
+ it("should prevent non-employee from getting salary", async function () {
233
+ await expect(
234
+ payroll.connect(signers.employee1).getMySalary()
235
+ ).to.be.revertedWith("Not an employee");
236
+ });
237
+ });
238
+
239
+ describe("View Functions", function () {
240
+ it("should return correct payroll info", async function () {
241
+ await payroll.fund({ value: ethers.parseEther("1") });
242
+
243
+ const info = await payroll.getPayrollInfo();
244
+ expect(info.employeeCount).to.equal(0n);
245
+ expect(info.balance).to.equal(ethers.parseEther("1"));
246
+ expect(info.period).to.equal(BigInt(PAYMENT_PERIOD));
247
+ });
248
+
249
+ it("should check payment due status", async function () {
250
+ expect(await payroll.isPaymentDue(signers.employee1.address)).to.be.false;
251
+ });
252
+ });
253
+ });
@@ -0,0 +1,254 @@
1
+ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
2
+ import type { ClearValueType } from "@zama-fhe/relayer-sdk/node";
3
+ import { expect } from "chai";
4
+ import { ethers as EthersT } from "ethers";
5
+ import { ethers, fhevm } from "hardhat";
6
+ import * as hre from "hardhat";
7
+
8
+ import { HighestDieRoll, HighestDieRoll__factory } from "../types";
9
+ import { Signers } from "./types";
10
+
11
+ async function deployFixture() {
12
+ // Contracts are deployed using the first signer/account by default
13
+ const factory = (await ethers.getContractFactory(
14
+ "HighestDieRoll"
15
+ )) as HighestDieRoll__factory;
16
+ const highestDiceRoll = (await factory.deploy()) as HighestDieRoll;
17
+ const highestDiceRoll_address = await highestDiceRoll.getAddress();
18
+
19
+ return { highestDiceRoll, highestDiceRoll_address };
20
+ }
21
+
22
+ describe("HighestDieRoll", function () {
23
+ let contract: HighestDieRoll;
24
+ let contractAddress: string;
25
+ let signers: Signers;
26
+ let playerA: HardhatEthersSigner;
27
+ let playerB: HardhatEthersSigner;
28
+
29
+ before(async function () {
30
+ // Check whether the tests are running against an FHEVM mock environment
31
+ if (!hre.fhevm.isMock) {
32
+ throw new Error(`This hardhat test suite cannot run on Sepolia Testnet`);
33
+ }
34
+
35
+ const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
36
+ signers = {
37
+ owner: ethSigners[0],
38
+ alice: ethSigners[1],
39
+ bob: ethSigners[2],
40
+ };
41
+
42
+ playerA = signers.alice;
43
+ playerB = signers.bob;
44
+ });
45
+
46
+ beforeEach(async function () {
47
+ // Deploy a new contract each time we run a new test
48
+ const deployment = await deployFixture();
49
+ contractAddress = deployment.highestDiceRoll_address;
50
+ contract = deployment.highestDiceRoll;
51
+ });
52
+
53
+ /**
54
+ * Helper: Parses the GameCreated event from a transaction receipt.
55
+ * WARNING: This function is for illustrative purposes only and is not production-ready
56
+ * (it does not handle several events in same tx).
57
+ */
58
+ function parseGameCreatedEvent(
59
+ txReceipt: EthersT.ContractTransactionReceipt | null
60
+ ): {
61
+ txHash: `0x${string}`;
62
+ gameId: number;
63
+ playerA: `0x${string}`;
64
+ playerB: `0x${string}`;
65
+ playerAEncryptedDiceRoll: `0x${string}`;
66
+ playerBEncryptedDiceRoll: `0x${string}`;
67
+ } {
68
+ const gameCreatedEvents: Array<{
69
+ txHash: `0x${string}`;
70
+ gameId: number;
71
+ playerA: `0x${string}`;
72
+ playerB: `0x${string}`;
73
+ playerAEncryptedDiceRoll: `0x${string}`;
74
+ playerBEncryptedDiceRoll: `0x${string}`;
75
+ }> = [];
76
+
77
+ if (txReceipt) {
78
+ const logs = Array.isArray(txReceipt.logs)
79
+ ? txReceipt.logs
80
+ : [txReceipt.logs];
81
+ for (let i = 0; i < logs.length; ++i) {
82
+ const parsedLog = contract.interface.parseLog(logs[i]);
83
+ if (!parsedLog || parsedLog.name !== "GameCreated") {
84
+ continue;
85
+ }
86
+ const ge = {
87
+ txHash: txReceipt.hash as `0x${string}`,
88
+ gameId: Number(parsedLog.args[0]),
89
+ playerA: parsedLog.args[1],
90
+ playerB: parsedLog.args[2],
91
+ playerAEncryptedDiceRoll: parsedLog.args[3],
92
+ playerBEncryptedDiceRoll: parsedLog.args[4],
93
+ };
94
+ gameCreatedEvents.push(ge);
95
+ }
96
+ }
97
+
98
+ // In this example, we expect on one single GameCreated event
99
+ expect(gameCreatedEvents.length).to.eq(1);
100
+
101
+ return gameCreatedEvents[0];
102
+ }
103
+
104
+ // ✅ Test should succeed
105
+ it("decryption should succeed", async function () {
106
+ console.log(``);
107
+ console.log(`🎲 HighestDieRoll Game contract address: ${contractAddress}`);
108
+ console.log(` 🤖 playerA.address: ${playerA.address}`);
109
+ console.log(` 🎃 playerB.address: ${playerB.address}`);
110
+ console.log(``);
111
+
112
+ // Starts a new Heads or Tails game. This will emit a `GameCreated` event
113
+ const tx = await contract
114
+ .connect(signers.owner)
115
+ .highestDieRoll(playerA, playerB);
116
+
117
+ // Parse the `GameCreated` event
118
+ const gameCreatedEvent = parseGameCreatedEvent(await tx.wait())!;
119
+
120
+ // GameId is 1 since we are playing the first game
121
+ expect(gameCreatedEvent.gameId).to.eq(1);
122
+ expect(gameCreatedEvent.playerA).to.eq(playerA.address);
123
+ expect(gameCreatedEvent.playerB).to.eq(playerB.address);
124
+ expect(await contract.getGamesCount()).to.eq(1);
125
+
126
+ console.log(`✅ New game #${gameCreatedEvent.gameId} created!`);
127
+ console.log(JSON.stringify(gameCreatedEvent, null, 2));
128
+
129
+ const gameId = gameCreatedEvent.gameId;
130
+ const playerADiceRoll = gameCreatedEvent.playerAEncryptedDiceRoll;
131
+ const playerBDiceRoll = gameCreatedEvent.playerBEncryptedDiceRoll;
132
+
133
+ // Call the Zama Relayer to compute the decryption
134
+ const publicDecryptResults = await fhevm.publicDecrypt([
135
+ playerADiceRoll,
136
+ playerBDiceRoll,
137
+ ]);
138
+
139
+ // The Relayer returns a `PublicDecryptResults` object containing:
140
+ // - the ORDERED clear values (here we have only one single value)
141
+ // - the ORDERED clear values in ABI-encoded form
142
+ // - the KMS decryption proof associated with the ORDERED clear values in ABI-encoded form
143
+ const abiEncodedClearGameResult =
144
+ publicDecryptResults.abiEncodedClearValues;
145
+ const decryptionProof = publicDecryptResults.decryptionProof;
146
+
147
+ const clearValueA: ClearValueType =
148
+ publicDecryptResults.clearValues[playerADiceRoll];
149
+ const clearValueB: ClearValueType =
150
+ publicDecryptResults.clearValues[playerBDiceRoll];
151
+
152
+ expect(typeof clearValueA).to.eq("bigint");
153
+ expect(typeof clearValueB).to.eq("bigint");
154
+
155
+ // playerA's 8-sided die roll result (between 1 and 8)
156
+ const a = (Number(clearValueA) % 8) + 1;
157
+ // playerB's 8-sided die roll result (between 1 and 8)
158
+ const b = (Number(clearValueB) % 8) + 1;
159
+
160
+ const isDraw = a === b;
161
+ const playerAWon = a > b;
162
+ const playerBWon = a < b;
163
+
164
+ console.log(``);
165
+ console.log(`🎲 playerA's 8-sided die roll is ${a}`);
166
+ console.log(`🎲 playerB's 8-sided die roll is ${b}`);
167
+
168
+ // Let's forward the `PublicDecryptResults` content to the on-chain contract whose job
169
+ // will simply be to verify the proof and store the final winner of the game
170
+ await contract.recordAndVerifyWinner(
171
+ gameId,
172
+ abiEncodedClearGameResult,
173
+ decryptionProof
174
+ );
175
+
176
+ const isRevealed = await contract.isGameRevealed(gameId);
177
+ const winner = await contract.getWinner(gameId);
178
+
179
+ expect(isRevealed).to.eq(true);
180
+ expect(
181
+ winner === playerA.address ||
182
+ winner === playerB.address ||
183
+ winner === EthersT.ZeroAddress
184
+ ).to.eq(true);
185
+
186
+ expect(isDraw).to.eq(winner === EthersT.ZeroAddress);
187
+ expect(playerAWon).to.eq(winner === playerA.address);
188
+ expect(playerBWon).to.eq(winner === playerB.address);
189
+
190
+ console.log(``);
191
+ if (winner === playerA.address) {
192
+ console.log(`🤖 playerA is the winner 🥇🥇`);
193
+ } else if (winner === playerB.address) {
194
+ console.log(`🎃 playerB is the winner 🥇🥇`);
195
+ } else if (winner === EthersT.ZeroAddress) {
196
+ console.log(`Game is a draw!`);
197
+ }
198
+ });
199
+
200
+ // ❌ Test should fail because clear values are ABI-encoded in the wrong order.
201
+ it("decryption should fail when ABI-encoding is wrongly ordered", async function () {
202
+ // Test Case: Verify strict ordering is enforced for cryptographic proof generation.
203
+ // The `decryptionProof` is generated based on the expected order (A, B). By ABI-encoding
204
+ // the clear values in the **reverse order** (B, A), we create a mismatch when the contract
205
+ // internally verifies the proof (e.g., checks a signature against a newly computed hash).
206
+ // This intentional failure is expected to revert with the `KMSInvalidSigner` error,
207
+ // confirming the proof's order dependency.
208
+ const tx = await contract
209
+ .connect(signers.owner)
210
+ .highestDieRoll(playerA, playerB);
211
+ const gameCreatedEvent = parseGameCreatedEvent(await tx.wait())!;
212
+ const gameId = gameCreatedEvent.gameId;
213
+ const playerADiceRoll = gameCreatedEvent.playerAEncryptedDiceRoll;
214
+ const playerBDiceRoll = gameCreatedEvent.playerBEncryptedDiceRoll;
215
+ // Call `fhevm.publicDecrypt` using order (A, B)
216
+ const publicDecryptResults = await fhevm.publicDecrypt([
217
+ playerADiceRoll,
218
+ playerBDiceRoll,
219
+ ]);
220
+ const clearValueA: ClearValueType =
221
+ publicDecryptResults.clearValues[playerADiceRoll];
222
+ const clearValueB: ClearValueType =
223
+ publicDecryptResults.clearValues[playerBDiceRoll];
224
+ const decryptionProof = publicDecryptResults.decryptionProof;
225
+ expect(typeof clearValueA).to.eq("bigint");
226
+ expect(typeof clearValueB).to.eq("bigint");
227
+ expect(
228
+ ethers.AbiCoder.defaultAbiCoder().encode(
229
+ ["uint256", "uint256"],
230
+ [clearValueA, clearValueB]
231
+ )
232
+ ).to.eq(publicDecryptResults.abiEncodedClearValues);
233
+ const wrongOrderBAInsteadOfABAbiEncodedValues =
234
+ ethers.AbiCoder.defaultAbiCoder().encode(
235
+ ["uint256", "uint256"],
236
+ [clearValueB, clearValueA]
237
+ );
238
+ // ❌ Call `contract.recordAndVerifyWinner` using order (B, A)
239
+ await expect(
240
+ contract.recordAndVerifyWinner(
241
+ gameId,
242
+ wrongOrderBAInsteadOfABAbiEncodedValues,
243
+ decryptionProof
244
+ )
245
+ ).to.be.revertedWithCustomError(
246
+ {
247
+ interface: new EthersT.Interface([
248
+ "error KMSInvalidSigner(address invalidSigner)",
249
+ ]),
250
+ },
251
+ "KMSInvalidSigner"
252
+ );
253
+ });
254
+ });