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,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
|
+
});
|