create-fhevm-example 1.3.1 → 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 (122) 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 +63 -59
  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 +13 -10
  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/maintenance.d.ts.map +0 -1
  115. package/dist/scripts/ui.d.ts.map +0 -1
  116. package/dist/scripts/utils.d.ts.map +0 -1
  117. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  118. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  119. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  120. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  121. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  122. /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
@@ -0,0 +1,214 @@
1
+ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
2
+ import { ethers, fhevm } from "hardhat";
3
+ import { EncryptedLottery, EncryptedLottery__factory } from "../types";
4
+ import { expect } from "chai";
5
+
6
+ type Signers = {
7
+ deployer: HardhatEthersSigner;
8
+ player1: HardhatEthersSigner;
9
+ player2: HardhatEthersSigner;
10
+ };
11
+
12
+ const TICKET_PRICE = ethers.parseEther("0.01");
13
+ const DURATION = 3600; // 1 hour
14
+
15
+ async function deployFixture() {
16
+ const factory = (await ethers.getContractFactory(
17
+ "EncryptedLottery"
18
+ )) as EncryptedLottery__factory;
19
+ const lottery = (await factory.deploy(
20
+ TICKET_PRICE,
21
+ DURATION
22
+ )) as EncryptedLottery;
23
+ const lotteryAddress = await lottery.getAddress();
24
+
25
+ return { lottery, lotteryAddress };
26
+ }
27
+
28
+ /**
29
+ * Encrypted Lottery Tests
30
+ *
31
+ * Tests private ticket purchases and FHE-based winner determination.
32
+ */
33
+ describe("EncryptedLottery", function () {
34
+ let signers: Signers;
35
+ let lottery: EncryptedLottery;
36
+ let lotteryAddress: string;
37
+
38
+ before(async function () {
39
+ const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
40
+ signers = {
41
+ deployer: ethSigners[0],
42
+ player1: ethSigners[1],
43
+ player2: ethSigners[2],
44
+ };
45
+ });
46
+
47
+ beforeEach(async function () {
48
+ if (!fhevm.isMock) {
49
+ console.warn("This test suite cannot run on Sepolia Testnet");
50
+ this.skip();
51
+ }
52
+
53
+ ({ lottery, lotteryAddress } = await deployFixture());
54
+ });
55
+
56
+ describe("Initialization", function () {
57
+ it("should initialize with correct parameters", async function () {
58
+ expect(await lottery.ticketPrice()).to.equal(TICKET_PRICE);
59
+ expect(await lottery.state()).to.equal(0); // Open
60
+ expect(await lottery.roundNumber()).to.equal(1n);
61
+ expect(await lottery.owner()).to.equal(signers.deployer.address);
62
+ });
63
+
64
+ it("should reject zero ticket price", async function () {
65
+ const factory = await ethers.getContractFactory("EncryptedLottery");
66
+ await expect(factory.deploy(0, DURATION)).to.be.revertedWith(
67
+ "Ticket price must be > 0"
68
+ );
69
+ });
70
+
71
+ it("should reject zero duration", async function () {
72
+ const factory = await ethers.getContractFactory("EncryptedLottery");
73
+ await expect(factory.deploy(TICKET_PRICE, 0)).to.be.revertedWith(
74
+ "Duration must be > 0"
75
+ );
76
+ });
77
+ });
78
+
79
+ describe("Ticket Purchase", function () {
80
+ it("should allow ticket purchase with encrypted number", async function () {
81
+ const encryptedNumber = await fhevm
82
+ .createEncryptedInput(lotteryAddress, signers.player1.address)
83
+ .add64(123456789n)
84
+ .encrypt();
85
+
86
+ await lottery
87
+ .connect(signers.player1)
88
+ .buyTicket(encryptedNumber.handles[0], encryptedNumber.inputProof, {
89
+ value: TICKET_PRICE,
90
+ });
91
+
92
+ expect(await lottery.getTicketCount()).to.equal(1n);
93
+ const playerTickets = await lottery.getPlayerTickets(
94
+ signers.player1.address
95
+ );
96
+ expect(playerTickets.length).to.equal(1);
97
+ });
98
+
99
+ it("should reject insufficient payment", async function () {
100
+ const encryptedNumber = await fhevm
101
+ .createEncryptedInput(lotteryAddress, signers.player1.address)
102
+ .add64(123456789n)
103
+ .encrypt();
104
+
105
+ await expect(
106
+ lottery
107
+ .connect(signers.player1)
108
+ .buyTicket(encryptedNumber.handles[0], encryptedNumber.inputProof, {
109
+ value: ethers.parseEther("0.001"),
110
+ })
111
+ ).to.be.revertedWith("Insufficient payment");
112
+ });
113
+
114
+ it("should accumulate prize pool", async function () {
115
+ // Player 1 buys ticket
116
+ const enc1 = await fhevm
117
+ .createEncryptedInput(lotteryAddress, signers.player1.address)
118
+ .add64(111111n)
119
+ .encrypt();
120
+
121
+ await lottery
122
+ .connect(signers.player1)
123
+ .buyTicket(enc1.handles[0], enc1.inputProof, { value: TICKET_PRICE });
124
+
125
+ // Player 2 buys ticket
126
+ const enc2 = await fhevm
127
+ .createEncryptedInput(lotteryAddress, signers.player2.address)
128
+ .add64(222222n)
129
+ .encrypt();
130
+
131
+ await lottery
132
+ .connect(signers.player2)
133
+ .buyTicket(enc2.handles[0], enc2.inputProof, { value: TICKET_PRICE });
134
+
135
+ expect(await lottery.prizePool()).to.equal(TICKET_PRICE * 2n);
136
+ expect(await lottery.getTicketCount()).to.equal(2n);
137
+ });
138
+ });
139
+
140
+ describe("Drawing", function () {
141
+ it("should prevent drawing before lottery ends", async function () {
142
+ const enc = await fhevm
143
+ .createEncryptedInput(lotteryAddress, signers.player1.address)
144
+ .add64(123456n)
145
+ .encrypt();
146
+
147
+ await lottery
148
+ .connect(signers.player1)
149
+ .buyTicket(enc.handles[0], enc.inputProof, { value: TICKET_PRICE });
150
+
151
+ await expect(lottery.startDrawing()).to.be.revertedWith(
152
+ "Lottery not ended"
153
+ );
154
+ });
155
+
156
+ it("should prevent drawing with no tickets", async function () {
157
+ // Fast forward past end time
158
+ await ethers.provider.send("evm_increaseTime", [DURATION + 1]);
159
+ await ethers.provider.send("evm_mine", []);
160
+
161
+ await expect(lottery.startDrawing()).to.be.revertedWith(
162
+ "No tickets sold"
163
+ );
164
+ });
165
+
166
+ it("should start drawing after lottery ends", async function () {
167
+ const enc = await fhevm
168
+ .createEncryptedInput(lotteryAddress, signers.player1.address)
169
+ .add64(123456n)
170
+ .encrypt();
171
+
172
+ await lottery
173
+ .connect(signers.player1)
174
+ .buyTicket(enc.handles[0], enc.inputProof, { value: TICKET_PRICE });
175
+
176
+ // Fast forward past end time
177
+ await ethers.provider.send("evm_increaseTime", [DURATION + 1]);
178
+ await ethers.provider.send("evm_mine", []);
179
+
180
+ await expect(lottery.startDrawing())
181
+ .to.emit(lottery, "DrawingStarted")
182
+ .withArgs(1);
183
+
184
+ expect(await lottery.state()).to.equal(1); // Drawing
185
+ });
186
+ });
187
+
188
+ describe("View Functions", function () {
189
+ it("should return correct lottery info", async function () {
190
+ const info = await lottery.getLotteryInfo();
191
+ expect(info.currentState).to.equal(0); // Open
192
+ expect(info.currentPrizePool).to.equal(0n);
193
+ expect(info.currentRound).to.equal(1n);
194
+ expect(info.totalTickets).to.equal(0n);
195
+ });
196
+
197
+ it("should track time remaining", async function () {
198
+ const remaining = await lottery.timeRemaining();
199
+ expect(remaining).to.be.lessThanOrEqual(BigInt(DURATION));
200
+ expect(remaining).to.be.greaterThan(0n);
201
+ });
202
+ });
203
+
204
+ describe("Access Control", function () {
205
+ it("should only allow owner to start drawing", async function () {
206
+ await ethers.provider.send("evm_increaseTime", [DURATION + 1]);
207
+ await ethers.provider.send("evm_mine", []);
208
+
209
+ await expect(
210
+ lottery.connect(signers.player1).startDrawing()
211
+ ).to.be.revertedWith("Only owner");
212
+ });
213
+ });
214
+ });
@@ -0,0 +1,349 @@
1
+ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
2
+ import { ethers, fhevm } from "hardhat";
3
+ import { EncryptedPoker, EncryptedPoker__factory } from "../types";
4
+ import { expect } from "chai";
5
+
6
+ type Signers = {
7
+ deployer: HardhatEthersSigner;
8
+ player0: HardhatEthersSigner;
9
+ player1: HardhatEthersSigner;
10
+ };
11
+
12
+ const MIN_BET = ethers.parseEther("0.01");
13
+
14
+ async function deployFixture() {
15
+ const factory = (await ethers.getContractFactory(
16
+ "EncryptedPoker"
17
+ )) as EncryptedPoker__factory;
18
+ const poker = (await factory.deploy(MIN_BET)) as EncryptedPoker;
19
+ const pokerAddress = await poker.getAddress();
20
+
21
+ return { poker, pokerAddress };
22
+ }
23
+
24
+ /**
25
+ * Encrypted Poker Tests
26
+ *
27
+ * Tests encrypted hole cards and FHE-based hand comparison.
28
+ * Demonstrates multi-player private state in gaming.
29
+ */
30
+ describe("EncryptedPoker", function () {
31
+ let signers: Signers;
32
+ let poker: EncryptedPoker;
33
+ let pokerAddress: string;
34
+
35
+ before(async function () {
36
+ const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
37
+ signers = {
38
+ deployer: ethSigners[0],
39
+ player0: ethSigners[1],
40
+ player1: ethSigners[2],
41
+ };
42
+ });
43
+
44
+ beforeEach(async function () {
45
+ if (!fhevm.isMock) {
46
+ console.warn("This test suite cannot run on Sepolia Testnet");
47
+ this.skip();
48
+ }
49
+
50
+ ({ poker, pokerAddress } = await deployFixture());
51
+ });
52
+
53
+ describe("Initialization", function () {
54
+ it("should initialize with correct parameters", async function () {
55
+ expect(await poker.minBet()).to.equal(MIN_BET);
56
+ expect(await poker.state()).to.equal(0); // WaitingForPlayers
57
+ expect(await poker.gameId()).to.equal(1n);
58
+ expect(await poker.pot()).to.equal(0n);
59
+ });
60
+
61
+ it("should reject zero min bet", async function () {
62
+ const factory = await ethers.getContractFactory("EncryptedPoker");
63
+ await expect(factory.deploy(0)).to.be.revertedWith("Min bet must be > 0");
64
+ });
65
+ });
66
+
67
+ describe("Joining Game", function () {
68
+ it("should allow first player to join with encrypted cards", async function () {
69
+ // Player 0 joins with cards (King=13, Queen=12)
70
+ const enc = await fhevm
71
+ .createEncryptedInput(pokerAddress, signers.player0.address)
72
+ .add8(13) // King
73
+ .add8(12) // Queen
74
+ .encrypt();
75
+
76
+ await poker
77
+ .connect(signers.player0)
78
+ .joinGame(enc.handles[0], enc.handles[1], enc.inputProof, {
79
+ value: MIN_BET,
80
+ });
81
+
82
+ const info = await poker.getGameInfo();
83
+ expect(info.player0).to.equal(signers.player0.address);
84
+ expect(info.currentPot).to.equal(MIN_BET);
85
+ expect(info.currentState).to.equal(0); // Still waiting
86
+ });
87
+
88
+ it("should transition to CardsDealt when second player joins", async function () {
89
+ // Player 0 joins
90
+ const enc0 = await fhevm
91
+ .createEncryptedInput(pokerAddress, signers.player0.address)
92
+ .add8(10)
93
+ .add8(10)
94
+ .encrypt();
95
+
96
+ await poker
97
+ .connect(signers.player0)
98
+ .joinGame(enc0.handles[0], enc0.handles[1], enc0.inputProof, {
99
+ value: MIN_BET,
100
+ });
101
+
102
+ // Player 1 joins
103
+ const enc1 = await fhevm
104
+ .createEncryptedInput(pokerAddress, signers.player1.address)
105
+ .add8(5)
106
+ .add8(5)
107
+ .encrypt();
108
+
109
+ await expect(
110
+ poker
111
+ .connect(signers.player1)
112
+ .joinGame(enc1.handles[0], enc1.handles[1], enc1.inputProof, {
113
+ value: MIN_BET,
114
+ })
115
+ ).to.emit(poker, "CardsDealt");
116
+
117
+ const info = await poker.getGameInfo();
118
+ expect(info.player1).to.equal(signers.player1.address);
119
+ expect(info.currentState).to.equal(1); // CardsDealt
120
+ expect(info.currentPot).to.equal(MIN_BET * 2n);
121
+ });
122
+
123
+ it("should reject insufficient bet", async function () {
124
+ const enc = await fhevm
125
+ .createEncryptedInput(pokerAddress, signers.player0.address)
126
+ .add8(1)
127
+ .add8(2)
128
+ .encrypt();
129
+
130
+ await expect(
131
+ poker
132
+ .connect(signers.player0)
133
+ .joinGame(enc.handles[0], enc.handles[1], enc.inputProof, {
134
+ value: ethers.parseEther("0.001"),
135
+ })
136
+ ).to.be.revertedWith("Must pay min bet to join");
137
+ });
138
+ });
139
+
140
+ describe("Betting", function () {
141
+ beforeEach(async function () {
142
+ // Setup: both players join
143
+ const enc0 = await fhevm
144
+ .createEncryptedInput(pokerAddress, signers.player0.address)
145
+ .add8(10)
146
+ .add8(10)
147
+ .encrypt();
148
+
149
+ await poker
150
+ .connect(signers.player0)
151
+ .joinGame(enc0.handles[0], enc0.handles[1], enc0.inputProof, {
152
+ value: MIN_BET,
153
+ });
154
+
155
+ const enc1 = await fhevm
156
+ .createEncryptedInput(pokerAddress, signers.player1.address)
157
+ .add8(5)
158
+ .add8(5)
159
+ .encrypt();
160
+
161
+ await poker
162
+ .connect(signers.player1)
163
+ .joinGame(enc1.handles[0], enc1.handles[1], enc1.inputProof, {
164
+ value: MIN_BET,
165
+ });
166
+ });
167
+
168
+ it("should allow betting", async function () {
169
+ const betAmount = ethers.parseEther("0.05");
170
+
171
+ await expect(poker.connect(signers.player0).bet({ value: betAmount }))
172
+ .to.emit(poker, "BetPlaced")
173
+ .withArgs(signers.player0.address, betAmount);
174
+
175
+ expect(await poker.getPlayerBet(signers.player0.address)).to.equal(
176
+ MIN_BET + betAmount
177
+ );
178
+ });
179
+
180
+ it("should accumulate pot", async function () {
181
+ await poker
182
+ .connect(signers.player0)
183
+ .bet({ value: ethers.parseEther("0.02") });
184
+ await poker
185
+ .connect(signers.player1)
186
+ .bet({ value: ethers.parseEther("0.03") });
187
+
188
+ const expectedPot =
189
+ MIN_BET * 2n + ethers.parseEther("0.02") + ethers.parseEther("0.03");
190
+ expect(await poker.pot()).to.equal(expectedPot);
191
+ });
192
+ });
193
+
194
+ describe("Folding", function () {
195
+ beforeEach(async function () {
196
+ const enc0 = await fhevm
197
+ .createEncryptedInput(pokerAddress, signers.player0.address)
198
+ .add8(10)
199
+ .add8(10)
200
+ .encrypt();
201
+
202
+ await poker
203
+ .connect(signers.player0)
204
+ .joinGame(enc0.handles[0], enc0.handles[1], enc0.inputProof, {
205
+ value: MIN_BET,
206
+ });
207
+
208
+ const enc1 = await fhevm
209
+ .createEncryptedInput(pokerAddress, signers.player1.address)
210
+ .add8(5)
211
+ .add8(5)
212
+ .encrypt();
213
+
214
+ await poker
215
+ .connect(signers.player1)
216
+ .joinGame(enc1.handles[0], enc1.handles[1], enc1.inputProof, {
217
+ value: MIN_BET,
218
+ });
219
+ });
220
+
221
+ it("should award pot to non-folding player", async function () {
222
+ const player1BalanceBefore = await ethers.provider.getBalance(
223
+ signers.player1.address
224
+ );
225
+
226
+ await expect(poker.connect(signers.player0).fold())
227
+ .to.emit(poker, "PlayerFolded")
228
+ .withArgs(signers.player0.address);
229
+
230
+ const info = await poker.getGameInfo();
231
+ expect(info.currentWinner).to.equal(signers.player1.address);
232
+ expect(info.currentState).to.equal(4); // Finished
233
+
234
+ const player1BalanceAfter = await ethers.provider.getBalance(
235
+ signers.player1.address
236
+ );
237
+ expect(player1BalanceAfter - player1BalanceBefore).to.equal(MIN_BET * 2n);
238
+ });
239
+ });
240
+
241
+ describe("Showdown", function () {
242
+ beforeEach(async function () {
243
+ // Player 0: high hand (King + Queen = 25)
244
+ const enc0 = await fhevm
245
+ .createEncryptedInput(pokerAddress, signers.player0.address)
246
+ .add8(13) // King
247
+ .add8(12) // Queen
248
+ .encrypt();
249
+
250
+ await poker
251
+ .connect(signers.player0)
252
+ .joinGame(enc0.handles[0], enc0.handles[1], enc0.inputProof, {
253
+ value: MIN_BET,
254
+ });
255
+
256
+ // Player 1: low hand (2 + 3 = 5)
257
+ const enc1 = await fhevm
258
+ .createEncryptedInput(pokerAddress, signers.player1.address)
259
+ .add8(2)
260
+ .add8(3)
261
+ .encrypt();
262
+
263
+ await poker
264
+ .connect(signers.player1)
265
+ .joinGame(enc1.handles[0], enc1.handles[1], enc1.inputProof, {
266
+ value: MIN_BET,
267
+ });
268
+ });
269
+
270
+ it("should start showdown and emit event", async function () {
271
+ await expect(poker.showdown()).to.emit(poker, "ShowdownStarted");
272
+
273
+ const info = await poker.getGameInfo();
274
+ expect(info.currentState).to.equal(3); // Showdown
275
+ });
276
+
277
+ it("should prevent showdown if someone folded", async function () {
278
+ await poker.connect(signers.player0).fold();
279
+
280
+ await expect(poker.showdown()).to.be.revertedWith(
281
+ "Not ready for showdown"
282
+ );
283
+ });
284
+ });
285
+
286
+ describe("Game Reset", function () {
287
+ it("should reset game after completion", async function () {
288
+ // Setup and fold to finish game
289
+ const enc0 = await fhevm
290
+ .createEncryptedInput(pokerAddress, signers.player0.address)
291
+ .add8(1)
292
+ .add8(2)
293
+ .encrypt();
294
+
295
+ await poker
296
+ .connect(signers.player0)
297
+ .joinGame(enc0.handles[0], enc0.handles[1], enc0.inputProof, {
298
+ value: MIN_BET,
299
+ });
300
+
301
+ const enc1 = await fhevm
302
+ .createEncryptedInput(pokerAddress, signers.player1.address)
303
+ .add8(3)
304
+ .add8(4)
305
+ .encrypt();
306
+
307
+ await poker
308
+ .connect(signers.player1)
309
+ .joinGame(enc1.handles[0], enc1.handles[1], enc1.inputProof, {
310
+ value: MIN_BET,
311
+ });
312
+
313
+ await poker.connect(signers.player0).fold();
314
+
315
+ // Reset
316
+ await poker.resetGame();
317
+
318
+ const info = await poker.getGameInfo();
319
+ expect(info.player0).to.equal(ethers.ZeroAddress);
320
+ expect(info.player1).to.equal(ethers.ZeroAddress);
321
+ expect(info.currentState).to.equal(0); // WaitingForPlayers
322
+ expect(info.currentGameId).to.equal(2n);
323
+ });
324
+
325
+ it("should prevent reset before game finishes", async function () {
326
+ await expect(poker.resetGame()).to.be.revertedWith("Game not finished");
327
+ });
328
+ });
329
+
330
+ describe("View Functions", function () {
331
+ it("should check player status", async function () {
332
+ expect(await poker.isPlayer(signers.player0.address)).to.be.false;
333
+
334
+ const enc = await fhevm
335
+ .createEncryptedInput(pokerAddress, signers.player0.address)
336
+ .add8(1)
337
+ .add8(2)
338
+ .encrypt();
339
+
340
+ await poker
341
+ .connect(signers.player0)
342
+ .joinGame(enc.handles[0], enc.handles[1], enc.inputProof, {
343
+ value: MIN_BET,
344
+ });
345
+
346
+ expect(await poker.isPlayer(signers.player0.address)).to.be.true;
347
+ });
348
+ });
349
+ });