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,246 @@
1
+ import { expect } from "chai";
2
+ import { ethers, fhevm } from "hardhat";
3
+ import * as hre from "hardhat";
4
+
5
+ describe("BlindAuction", function () {
6
+ let auction: any;
7
+ let owner: any;
8
+ let bidder1: any;
9
+ let bidder2: any;
10
+ let bidder3: any;
11
+
12
+ const MINIMUM_BID = 100n;
13
+
14
+ async function deployAuction(durationSeconds: number = 60) {
15
+ // Get blockchain timestamp instead of Date.now()
16
+ const latestBlock = await ethers.provider.getBlock("latest");
17
+ const endTime =
18
+ (latestBlock?.timestamp || Math.floor(Date.now() / 1000)) +
19
+ durationSeconds;
20
+ const auctionContract = await ethers.deployContract("BlindAuction", [
21
+ endTime,
22
+ MINIMUM_BID,
23
+ ]);
24
+ return { auction: auctionContract, endTime };
25
+ }
26
+
27
+ beforeEach(async function () {
28
+ [owner, bidder1, bidder2, bidder3] = await ethers.getSigners();
29
+ });
30
+
31
+ describe("Auction Creation", function () {
32
+ it("should create auction with correct parameters", async function () {
33
+ const { auction: auc } = await deployAuction(120);
34
+
35
+ expect(await auc.auctionState()).to.equal(0); // Open
36
+ expect(await auc.minimumBid()).to.equal(MINIMUM_BID);
37
+ expect(await auc.getBidderCount()).to.equal(0);
38
+ });
39
+
40
+ it("should revert if end time is in the past", async function () {
41
+ const pastTime = Math.floor(Date.now() / 1000) - 100;
42
+ await expect(
43
+ ethers.deployContract("BlindAuction", [pastTime, MINIMUM_BID])
44
+ ).to.be.revertedWith("End time must be in future");
45
+ });
46
+ });
47
+
48
+ describe("Bidding", function () {
49
+ beforeEach(async function () {
50
+ const deployment = await deployAuction(3600); // 1 hour
51
+ auction = deployment.auction;
52
+ });
53
+
54
+ it("should accept encrypted bid", async function () {
55
+ const encryptedBid = await fhevm
56
+ .createEncryptedInput(await auction.getAddress(), bidder1.address)
57
+ .add64(500)
58
+ .encrypt();
59
+
60
+ await expect(
61
+ auction
62
+ .connect(bidder1)
63
+ .bid(encryptedBid.handles[0], encryptedBid.inputProof)
64
+ ).to.not.be.reverted;
65
+
66
+ expect(await auction.hasBid(bidder1.address)).to.be.true;
67
+ expect(await auction.getBidderCount()).to.equal(1);
68
+ });
69
+
70
+ it("should reject second bid from same address", async function () {
71
+ const encryptedBid1 = await fhevm
72
+ .createEncryptedInput(await auction.getAddress(), bidder1.address)
73
+ .add64(500)
74
+ .encrypt();
75
+
76
+ await auction
77
+ .connect(bidder1)
78
+ .bid(encryptedBid1.handles[0], encryptedBid1.inputProof);
79
+
80
+ const encryptedBid2 = await fhevm
81
+ .createEncryptedInput(await auction.getAddress(), bidder1.address)
82
+ .add64(600)
83
+ .encrypt();
84
+
85
+ await expect(
86
+ auction
87
+ .connect(bidder1)
88
+ .bid(encryptedBid2.handles[0], encryptedBid2.inputProof)
89
+ ).to.be.revertedWith("Already placed a bid");
90
+ });
91
+
92
+ it("should accept bids from multiple bidders", async function () {
93
+ // Bidder 1
94
+ const enc1 = await fhevm
95
+ .createEncryptedInput(await auction.getAddress(), bidder1.address)
96
+ .add64(200)
97
+ .encrypt();
98
+ await auction.connect(bidder1).bid(enc1.handles[0], enc1.inputProof);
99
+
100
+ // Bidder 2
101
+ const enc2 = await fhevm
102
+ .createEncryptedInput(await auction.getAddress(), bidder2.address)
103
+ .add64(300)
104
+ .encrypt();
105
+ await auction.connect(bidder2).bid(enc2.handles[0], enc2.inputProof);
106
+
107
+ // Bidder 3
108
+ const enc3 = await fhevm
109
+ .createEncryptedInput(await auction.getAddress(), bidder3.address)
110
+ .add64(150)
111
+ .encrypt();
112
+ await auction.connect(bidder3).bid(enc3.handles[0], enc3.inputProof);
113
+
114
+ expect(await auction.getBidderCount()).to.equal(3);
115
+ });
116
+ });
117
+
118
+ describe("End Auction", function () {
119
+ beforeEach(async function () {
120
+ // Deploy with 1 hour duration to avoid timing issues from evm_increaseTime in other tests
121
+ const deployment = await deployAuction(3600);
122
+ auction = deployment.auction;
123
+
124
+ // Place bids before it ends
125
+ const enc1 = await fhevm
126
+ .createEncryptedInput(await auction.getAddress(), bidder1.address)
127
+ .add64(200)
128
+ .encrypt();
129
+ await auction.connect(bidder1).bid(enc1.handles[0], enc1.inputProof);
130
+
131
+ const enc2 = await fhevm
132
+ .createEncryptedInput(await auction.getAddress(), bidder2.address)
133
+ .add64(500)
134
+ .encrypt();
135
+ await auction.connect(bidder2).bid(enc2.handles[0], enc2.inputProof);
136
+
137
+ const enc3 = await fhevm
138
+ .createEncryptedInput(await auction.getAddress(), bidder3.address)
139
+ .add64(300)
140
+ .encrypt();
141
+ await auction.connect(bidder3).bid(enc3.handles[0], enc3.inputProof);
142
+ });
143
+
144
+ it("should not allow ending before time", async function () {
145
+ await expect(auction.connect(owner).endAuction()).to.be.revertedWith(
146
+ "Auction not yet ended"
147
+ );
148
+ });
149
+
150
+ it("should allow owner to end after time passes", async function () {
151
+ // Fast forward time past auction end
152
+ await hre.network.provider.send("evm_increaseTime", [3601]);
153
+ await hre.network.provider.send("evm_mine");
154
+
155
+ await expect(auction.connect(owner).endAuction()).to.not.be.reverted;
156
+ expect(await auction.auctionState()).to.equal(1); // Closed
157
+ });
158
+
159
+ it("should not allow non-owner to end auction", async function () {
160
+ await hre.network.provider.send("evm_increaseTime", [3601]);
161
+ await hre.network.provider.send("evm_mine");
162
+
163
+ await expect(auction.connect(bidder1).endAuction()).to.be.revertedWith(
164
+ "Only owner can call"
165
+ );
166
+ });
167
+ });
168
+
169
+ describe("Full Auction Flow", function () {
170
+ it("should correctly identify highest bidder", async function () {
171
+ // Check if running in mock mode
172
+ if (!hre.fhevm.isMock) {
173
+ console.log("Skipping: Test requires mock environment");
174
+ this.skip();
175
+ }
176
+
177
+ // Deploy with short duration - use blockchain timestamp
178
+ const latestBlock = await ethers.provider.getBlock("latest");
179
+ const endTime = (latestBlock?.timestamp || 0) + 60; // 60 seconds from now
180
+ auction = await ethers.deployContract("BlindAuction", [
181
+ endTime,
182
+ MINIMUM_BID,
183
+ ]);
184
+
185
+ // Place bids: bidder2 has highest (500)
186
+ const enc1 = await fhevm
187
+ .createEncryptedInput(await auction.getAddress(), bidder1.address)
188
+ .add64(200)
189
+ .encrypt();
190
+ await auction.connect(bidder1).bid(enc1.handles[0], enc1.inputProof);
191
+
192
+ const enc2 = await fhevm
193
+ .createEncryptedInput(await auction.getAddress(), bidder2.address)
194
+ .add64(500) // 🏆 Highest bid
195
+ .encrypt();
196
+ await auction.connect(bidder2).bid(enc2.handles[0], enc2.inputProof);
197
+
198
+ const enc3 = await fhevm
199
+ .createEncryptedInput(await auction.getAddress(), bidder3.address)
200
+ .add64(300)
201
+ .encrypt();
202
+ await auction.connect(bidder3).bid(enc3.handles[0], enc3.inputProof);
203
+
204
+ // Fast forward time using evm commands
205
+ await hre.network.provider.send("evm_increaseTime", [65]); // 65 seconds
206
+ await hre.network.provider.send("evm_mine");
207
+
208
+ const endTx = await auction.connect(owner).endAuction();
209
+ const endReceipt = await endTx.wait();
210
+
211
+ // Parse AuctionEnded event to get encrypted handles
212
+ const auctionEndedEvent = endReceipt.logs.find((log: any) => {
213
+ try {
214
+ const parsed = auction.interface.parseLog(log);
215
+ return parsed?.name === "AuctionEnded";
216
+ } catch {
217
+ return false;
218
+ }
219
+ });
220
+
221
+ expect(auctionEndedEvent).to.not.be.undefined;
222
+
223
+ // Get encrypted handles from contract
224
+ const encWinningBid = await auction.getEncryptedWinningBid();
225
+ const encWinnerIndex = await auction.getEncryptedWinnerIndex();
226
+
227
+ // Request public decryption
228
+ const handles = [encWinningBid, encWinnerIndex];
229
+ const decryptResults = await fhevm.publicDecrypt(handles);
230
+
231
+ // Reveal winner
232
+ await auction.revealWinner(
233
+ decryptResults.abiEncodedClearValues,
234
+ decryptResults.decryptionProof
235
+ );
236
+
237
+ // Verify results
238
+ expect(await auction.auctionState()).to.equal(2); // Revealed
239
+ expect(await auction.winner()).to.equal(bidder2.address);
240
+ expect(await auction.winningAmount()).to.equal(500n);
241
+
242
+ console.log(`🏆 Winner: ${await auction.winner()}`);
243
+ console.log(`💰 Winning Amount: ${await auction.winningAmount()}`);
244
+ });
245
+ });
246
+ });
@@ -0,0 +1,295 @@
1
+ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
2
+ import { ethers, fhevm } from "hardhat";
3
+ import { EncryptedEscrow, EncryptedEscrow__factory } from "../types";
4
+ import { expect } from "chai";
5
+
6
+ type Signers = {
7
+ buyer: HardhatEthersSigner;
8
+ seller: HardhatEthersSigner;
9
+ arbiter: HardhatEthersSigner;
10
+ };
11
+
12
+ const ARBITER_FEE = 1; // 1%
13
+ const DEADLINE_OFFSET = 86400; // 1 day
14
+
15
+ async function deployFixture() {
16
+ const factory = (await ethers.getContractFactory(
17
+ "EncryptedEscrow"
18
+ )) as EncryptedEscrow__factory;
19
+ const escrow = (await factory.deploy(ARBITER_FEE)) as EncryptedEscrow;
20
+ const escrowAddress = await escrow.getAddress();
21
+
22
+ return { escrow, escrowAddress };
23
+ }
24
+
25
+ /**
26
+ * Encrypted Escrow Tests
27
+ *
28
+ * Tests secure escrow with encrypted amounts and dispute resolution.
29
+ */
30
+ describe("EncryptedEscrow", function () {
31
+ let signers: Signers;
32
+ let escrow: EncryptedEscrow;
33
+ let escrowAddress: string;
34
+
35
+ before(async function () {
36
+ const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
37
+ signers = {
38
+ buyer: ethSigners[0],
39
+ seller: ethSigners[1],
40
+ arbiter: 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
+ ({ escrow, escrowAddress } = await deployFixture());
51
+ });
52
+
53
+ describe("Initialization", function () {
54
+ it("should initialize with correct parameters", async function () {
55
+ expect(await escrow.owner()).to.equal(signers.buyer.address);
56
+ expect(await escrow.arbiterFeePercent()).to.equal(BigInt(ARBITER_FEE));
57
+ expect(await escrow.escrowCount()).to.equal(0n);
58
+ });
59
+
60
+ it("should reject fee percentage above 10%", async function () {
61
+ const factory = await ethers.getContractFactory("EncryptedEscrow");
62
+ await expect(factory.deploy(15)).to.be.revertedWith("Fee too high");
63
+ });
64
+ });
65
+
66
+ describe("Escrow Creation", function () {
67
+ it("should create escrow with encrypted amount", async function () {
68
+ const amount = ethers.parseEther("1");
69
+ const block = await ethers.provider.getBlock("latest");
70
+ const deadline = (block?.timestamp || 0) + DEADLINE_OFFSET;
71
+
72
+ const encryptedAmount = await fhevm
73
+ .createEncryptedInput(escrowAddress, signers.buyer.address)
74
+ .add64(amount)
75
+ .encrypt();
76
+
77
+ await expect(
78
+ escrow.createEscrow(
79
+ signers.seller.address,
80
+ signers.arbiter.address,
81
+ encryptedAmount.handles[0],
82
+ encryptedAmount.inputProof,
83
+ deadline
84
+ )
85
+ ).to.emit(escrow, "EscrowCreated");
86
+
87
+ expect(await escrow.escrowCount()).to.equal(1n);
88
+ });
89
+
90
+ it("should reject invalid seller address", async function () {
91
+ const block = await ethers.provider.getBlock("latest");
92
+ const deadline = (block?.timestamp || 0) + DEADLINE_OFFSET;
93
+
94
+ const enc = await fhevm
95
+ .createEncryptedInput(escrowAddress, signers.buyer.address)
96
+ .add64(1000n)
97
+ .encrypt();
98
+
99
+ await expect(
100
+ escrow.createEscrow(
101
+ ethers.ZeroAddress,
102
+ signers.arbiter.address,
103
+ enc.handles[0],
104
+ enc.inputProof,
105
+ deadline
106
+ )
107
+ ).to.be.revertedWith("Invalid seller");
108
+ });
109
+
110
+ it("should reject buyer as seller", async function () {
111
+ const block = await ethers.provider.getBlock("latest");
112
+ const deadline = (block?.timestamp || 0) + DEADLINE_OFFSET;
113
+
114
+ const enc = await fhevm
115
+ .createEncryptedInput(escrowAddress, signers.buyer.address)
116
+ .add64(1000n)
117
+ .encrypt();
118
+
119
+ await expect(
120
+ escrow.createEscrow(
121
+ signers.buyer.address,
122
+ signers.arbiter.address,
123
+ enc.handles[0],
124
+ enc.inputProof,
125
+ deadline
126
+ )
127
+ ).to.be.revertedWith("Buyer cannot be seller");
128
+ });
129
+ });
130
+
131
+ describe("Funding", function () {
132
+ let escrowId: bigint;
133
+
134
+ beforeEach(async function () {
135
+ const block = await ethers.provider.getBlock("latest");
136
+ const deadline = (block?.timestamp || 0) + DEADLINE_OFFSET;
137
+
138
+ const enc = await fhevm
139
+ .createEncryptedInput(escrowAddress, signers.buyer.address)
140
+ .add64(ethers.parseEther("1"))
141
+ .encrypt();
142
+
143
+ await escrow.createEscrow(
144
+ signers.seller.address,
145
+ signers.arbiter.address,
146
+ enc.handles[0],
147
+ enc.inputProof,
148
+ deadline
149
+ );
150
+ escrowId = 1n;
151
+ });
152
+
153
+ it("should allow buyer to fund escrow", async function () {
154
+ const amount = ethers.parseEther("1");
155
+
156
+ await expect(escrow.fundEscrow(escrowId, { value: amount }))
157
+ .to.emit(escrow, "EscrowFunded")
158
+ .withArgs(escrowId, amount);
159
+
160
+ const info = await escrow.getEscrow(escrowId);
161
+ expect(info.depositedAmount).to.equal(amount);
162
+ expect(info.state).to.equal(1); // Funded
163
+ });
164
+
165
+ it("should prevent non-buyer from funding", async function () {
166
+ await expect(
167
+ escrow
168
+ .connect(signers.seller)
169
+ .fundEscrow(escrowId, { value: ethers.parseEther("1") })
170
+ ).to.be.revertedWith("Only buyer can fund");
171
+ });
172
+ });
173
+
174
+ describe("Release and Refund", function () {
175
+ let escrowId: bigint;
176
+ const DEPOSIT = ethers.parseEther("1");
177
+
178
+ beforeEach(async function () {
179
+ const block = await ethers.provider.getBlock("latest");
180
+ const deadline = (block?.timestamp || 0) + DEADLINE_OFFSET;
181
+
182
+ const enc = await fhevm
183
+ .createEncryptedInput(escrowAddress, signers.buyer.address)
184
+ .add64(DEPOSIT)
185
+ .encrypt();
186
+
187
+ await escrow.createEscrow(
188
+ signers.seller.address,
189
+ signers.arbiter.address,
190
+ enc.handles[0],
191
+ enc.inputProof,
192
+ deadline
193
+ );
194
+ escrowId = 1n;
195
+ await escrow.fundEscrow(escrowId, { value: DEPOSIT });
196
+ });
197
+
198
+ it("should allow buyer to release funds", async function () {
199
+ const sellerBalanceBefore = await ethers.provider.getBalance(
200
+ signers.seller.address
201
+ );
202
+
203
+ await expect(escrow.release(escrowId))
204
+ .to.emit(escrow, "FundsReleased")
205
+ .withArgs(escrowId, signers.seller.address);
206
+
207
+ const sellerBalanceAfter = await ethers.provider.getBalance(
208
+ signers.seller.address
209
+ );
210
+ expect(sellerBalanceAfter - sellerBalanceBefore).to.equal(DEPOSIT);
211
+ });
212
+
213
+ it("should prevent non-buyer from releasing", async function () {
214
+ await expect(
215
+ escrow.connect(signers.seller).release(escrowId)
216
+ ).to.be.revertedWith("Only buyer can release");
217
+ });
218
+
219
+ it("should prevent refund before deadline", async function () {
220
+ await expect(escrow.requestRefund(escrowId)).to.be.revertedWith(
221
+ "Deadline not passed"
222
+ );
223
+ });
224
+ });
225
+
226
+ describe("Dispute Resolution", function () {
227
+ let escrowId: bigint;
228
+ const DEPOSIT = ethers.parseEther("1");
229
+
230
+ beforeEach(async function () {
231
+ const block = await ethers.provider.getBlock("latest");
232
+ const deadline = (block?.timestamp || 0) + DEADLINE_OFFSET;
233
+
234
+ const enc = await fhevm
235
+ .createEncryptedInput(escrowAddress, signers.buyer.address)
236
+ .add64(DEPOSIT)
237
+ .encrypt();
238
+
239
+ await escrow.createEscrow(
240
+ signers.seller.address,
241
+ signers.arbiter.address,
242
+ enc.handles[0],
243
+ enc.inputProof,
244
+ deadline
245
+ );
246
+ escrowId = 1n;
247
+ await escrow.fundEscrow(escrowId, { value: DEPOSIT });
248
+ });
249
+
250
+ it("should allow buyer to raise dispute", async function () {
251
+ await expect(escrow.raiseDispute(escrowId))
252
+ .to.emit(escrow, "DisputeRaised")
253
+ .withArgs(escrowId, signers.buyer.address);
254
+
255
+ const info = await escrow.getEscrow(escrowId);
256
+ expect(info.state).to.equal(4); // Disputed
257
+ });
258
+
259
+ it("should allow arbiter to resolve in favor of buyer", async function () {
260
+ await escrow.raiseDispute(escrowId);
261
+
262
+ const buyerBalanceBefore = await ethers.provider.getBalance(
263
+ signers.buyer.address
264
+ );
265
+
266
+ await expect(
267
+ escrow.connect(signers.arbiter).resolveDispute(escrowId, true)
268
+ )
269
+ .to.emit(escrow, "DisputeResolved")
270
+ .withArgs(escrowId, signers.buyer.address);
271
+
272
+ const buyerBalanceAfter = await ethers.provider.getBalance(
273
+ signers.buyer.address
274
+ );
275
+ // Buyer receives 99% (1% arbiter fee)
276
+ const expected = DEPOSIT - DEPOSIT / 100n;
277
+ expect(buyerBalanceAfter - buyerBalanceBefore).to.equal(expected);
278
+ });
279
+
280
+ it("should prevent non-arbiter from resolving", async function () {
281
+ await escrow.raiseDispute(escrowId);
282
+
283
+ await expect(escrow.resolveDispute(escrowId, true)).to.be.revertedWith(
284
+ "Only arbiter"
285
+ );
286
+ });
287
+ });
288
+
289
+ describe("View Functions", function () {
290
+ it("should check deadline status", async function () {
291
+ // Non-existent escrow with deadline = 0 means timestamp > 0 is always true
292
+ expect(await escrow.isDeadlinePassed(999)).to.be.true;
293
+ });
294
+ });
295
+ });