@0xflydev/labz 1.0.22 → 1.0.24
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/dist/index.js +5 -2
- package/package.json +1 -1
- package/templates/buildable/projects/auction/deploy/deploy.ts.tmpl +34 -33
- package/templates/buildable/projects/counter/test/Counter.test.ts.tmpl +47 -53
- package/templates/buildable/projects/prediction-market/test/PredictionMarket.test.ts.tmpl +10 -6
- package/templates/buildable/projects/token/deploy/deploy.ts.tmpl +30 -23
- package/templates/buildable/projects/token/test/Token.test.ts.tmpl +187 -0
- package/templates/buildable/projects/voting/contracts/Voting.sol.tmpl +5 -5
- package/templates/buildable/projects/voting/deploy/deploy.ts.tmpl +30 -19
- package/templates/buildable/projects/voting/test/Voting.test.ts.tmpl +135 -0
package/dist/index.js
CHANGED
|
@@ -1538,7 +1538,7 @@ Base: ${import_chalk7.default.cyan(base.name)} - ${base.description}`));
|
|
|
1538
1538
|
const spinner = (0, import_ora5.default)("Generating project...").start();
|
|
1539
1539
|
try {
|
|
1540
1540
|
const result = (0, import_composer.merge)(base, modules, {
|
|
1541
|
-
projectName: finalProjectName,
|
|
1541
|
+
projectName: path4.basename(finalProjectName),
|
|
1542
1542
|
typeParams: options.type ? { COUNTER_TYPE: options.type, EXTERNAL_TYPE: `external${options.type.charAt(0).toUpperCase()}${options.type.slice(1)}` } : void 0
|
|
1543
1543
|
});
|
|
1544
1544
|
if (!result.success) {
|
|
@@ -1651,8 +1651,11 @@ function copyDirSync(src, dest) {
|
|
|
1651
1651
|
}
|
|
1652
1652
|
}
|
|
1653
1653
|
}
|
|
1654
|
+
function toPascalCase(name) {
|
|
1655
|
+
return name.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1656
|
+
}
|
|
1654
1657
|
function generateBuildReadme(projectName, base, result) {
|
|
1655
|
-
const contractName =
|
|
1658
|
+
const contractName = toPascalCase(result.stats.baseTemplate);
|
|
1656
1659
|
const modules = result.stats.modulesApplied || [];
|
|
1657
1660
|
const lines = [];
|
|
1658
1661
|
lines.push(`# ${projectName}`);
|
package/package.json
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
import
|
|
1
|
+
import hre from "hardhat";
|
|
2
2
|
|
|
3
3
|
async function main() {
|
|
4
|
-
const itemDescription = "Rare Digital Collectible";
|
|
5
|
-
const durationSeconds = 86400; // 1 day
|
|
6
|
-
|
|
7
4
|
console.log("Deploying {{CONTRACT_NAME}}...");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
await
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
5
|
+
|
|
6
|
+
const Factory = await hre.ethers.getContractFactory("{{CONTRACT_NAME}}");
|
|
7
|
+
const contract = await Factory.deploy();
|
|
8
|
+
|
|
9
|
+
await contract.waitForDeployment();
|
|
10
|
+
|
|
11
|
+
const address = await contract.getAddress();
|
|
12
|
+
console.log(`{{CONTRACT_NAME}} deployed to: ${address}`);
|
|
13
|
+
|
|
14
|
+
// Verify on explorer if not local
|
|
15
|
+
if (hre.network.name !== "hardhat" && hre.network.name !== "localhost") {
|
|
16
|
+
console.log("Waiting for block confirmations...");
|
|
17
|
+
await contract.deploymentTransaction()?.wait(5);
|
|
18
|
+
|
|
19
|
+
console.log("Verifying contract...");
|
|
20
|
+
try {
|
|
21
|
+
await hre.run("verify:verify", {
|
|
22
|
+
address: address,
|
|
23
|
+
constructorArguments: [],
|
|
24
|
+
});
|
|
25
|
+
console.log("Contract verified!");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.log("Verification failed:", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return address;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
main()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
main()
|
|
35
|
+
.then(() => process.exit(0))
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
console.error(error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
@@ -1,71 +1,65 @@
|
|
|
1
1
|
import { expect } from "chai";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { ethers, fhevm } from "hardhat";
|
|
3
|
+
import { FhevmType } from "@fhevm/hardhat-plugin";
|
|
4
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
4
5
|
|
|
5
6
|
describe("{{CONTRACT_NAME}}", function () {
|
|
6
7
|
let contract: any;
|
|
7
8
|
let contractAddress: string;
|
|
8
|
-
let
|
|
9
|
-
let signers: any[];
|
|
9
|
+
let signers: HardhatEthersSigner[];
|
|
10
10
|
|
|
11
11
|
before(async function () {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
signers = await ethers.getSigners();
|
|
13
|
+
});
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
const Factory = await
|
|
15
|
+
beforeEach(async function () {
|
|
16
|
+
const Factory = await ethers.getContractFactory("{{CONTRACT_NAME}}");
|
|
17
17
|
contract = await Factory.deploy();
|
|
18
18
|
await contract.waitForDeployment();
|
|
19
19
|
contractAddress = await contract.getAddress();
|
|
20
|
-
|
|
21
|
-
// Create FHEVM instance
|
|
22
|
-
fhevm = await createInstance({
|
|
23
|
-
networkUrl: hre.network.config.url || "http://localhost:8545",
|
|
24
|
-
gatewayUrl: "http://localhost:7077",
|
|
25
|
-
});
|
|
26
20
|
});
|
|
27
21
|
|
|
28
22
|
describe("Increment", function () {
|
|
29
23
|
it("should increment the counter with encrypted value", async function () {
|
|
30
24
|
const [signer] = signers;
|
|
31
|
-
|
|
32
|
-
// Encrypt the value to add
|
|
33
25
|
const valueToAdd = 5;
|
|
34
|
-
const encrypted = await fhevm.encrypt32(valueToAdd);
|
|
35
26
|
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
// Create encrypted input using hardhat plugin
|
|
28
|
+
const encryptedInput = await fhevm
|
|
29
|
+
.createEncryptedInput(contractAddress, signer.address)
|
|
30
|
+
.add32(valueToAdd)
|
|
31
|
+
.encrypt();
|
|
32
|
+
|
|
33
|
+
// Call increment with encrypted value and proof
|
|
34
|
+
const tx = await contract
|
|
35
|
+
.connect(signer)
|
|
36
|
+
.increment(encryptedInput.handles[0], encryptedInput.inputProof);
|
|
41
37
|
await tx.wait();
|
|
42
38
|
|
|
43
39
|
// Get the encrypted count
|
|
44
40
|
const encryptedCount = await contract.getCount();
|
|
45
|
-
|
|
46
|
-
// Request decryption (in real scenario, would use gateway)
|
|
47
|
-
expect(encryptedCount).to.not.equal(0n);
|
|
41
|
+
expect(encryptedCount).to.not.equal(ethers.ZeroHash);
|
|
48
42
|
});
|
|
49
43
|
|
|
50
44
|
it("should allow multiple increments", async function () {
|
|
51
45
|
const [signer] = signers;
|
|
52
46
|
|
|
53
47
|
// First increment
|
|
54
|
-
const encrypted1 = await fhevm
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
48
|
+
const encrypted1 = await fhevm
|
|
49
|
+
.createEncryptedInput(contractAddress, signer.address)
|
|
50
|
+
.add32(10)
|
|
51
|
+
.encrypt();
|
|
52
|
+
await contract.connect(signer).increment(encrypted1.handles[0], encrypted1.inputProof);
|
|
59
53
|
|
|
60
54
|
// Second increment
|
|
61
|
-
const encrypted2 = await fhevm
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
);
|
|
55
|
+
const encrypted2 = await fhevm
|
|
56
|
+
.createEncryptedInput(contractAddress, signer.address)
|
|
57
|
+
.add32(20)
|
|
58
|
+
.encrypt();
|
|
59
|
+
await contract.connect(signer).increment(encrypted2.handles[0], encrypted2.inputProof);
|
|
66
60
|
|
|
67
61
|
const encryptedCount = await contract.getCount();
|
|
68
|
-
expect(encryptedCount).to.not.equal(
|
|
62
|
+
expect(encryptedCount).to.not.equal(ethers.ZeroHash);
|
|
69
63
|
});
|
|
70
64
|
});
|
|
71
65
|
|
|
@@ -74,21 +68,21 @@ describe("{{CONTRACT_NAME}}", function () {
|
|
|
74
68
|
const [signer] = signers;
|
|
75
69
|
|
|
76
70
|
// First add some value
|
|
77
|
-
const addValue = await fhevm
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
71
|
+
const addValue = await fhevm
|
|
72
|
+
.createEncryptedInput(contractAddress, signer.address)
|
|
73
|
+
.add32(100)
|
|
74
|
+
.encrypt();
|
|
75
|
+
await contract.connect(signer).increment(addValue.handles[0], addValue.inputProof);
|
|
82
76
|
|
|
83
77
|
// Then decrement
|
|
84
|
-
const subValue = await fhevm
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
);
|
|
78
|
+
const subValue = await fhevm
|
|
79
|
+
.createEncryptedInput(contractAddress, signer.address)
|
|
80
|
+
.add32(30)
|
|
81
|
+
.encrypt();
|
|
82
|
+
await contract.connect(signer).decrement(subValue.handles[0], subValue.inputProof);
|
|
89
83
|
|
|
90
84
|
const encryptedCount = await contract.getCount();
|
|
91
|
-
expect(encryptedCount).to.not.equal(
|
|
85
|
+
expect(encryptedCount).to.not.equal(ethers.ZeroHash);
|
|
92
86
|
});
|
|
93
87
|
});
|
|
94
88
|
|
|
@@ -96,15 +90,15 @@ describe("{{CONTRACT_NAME}}", function () {
|
|
|
96
90
|
it("should grant access to caller after increment", async function () {
|
|
97
91
|
const [signer] = signers;
|
|
98
92
|
|
|
99
|
-
const encrypted = await fhevm
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
93
|
+
const encrypted = await fhevm
|
|
94
|
+
.createEncryptedInput(contractAddress, signer.address)
|
|
95
|
+
.add32(1)
|
|
96
|
+
.encrypt();
|
|
97
|
+
await contract.connect(signer).increment(encrypted.handles[0], encrypted.inputProof);
|
|
104
98
|
|
|
105
99
|
// Caller should be able to get the count
|
|
106
100
|
const count = await contract.connect(signer).getCount();
|
|
107
|
-
expect(count).to.not.equal(
|
|
101
|
+
expect(count).to.not.equal(ethers.ZeroHash);
|
|
108
102
|
});
|
|
109
103
|
});
|
|
110
104
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { expect } from "chai";
|
|
2
2
|
import { ethers } from "hardhat";
|
|
3
|
+
import { time } from "@nomicfoundation/hardhat-network-helpers";
|
|
3
4
|
import { {{CONTRACT_NAME}} } from "../typechain-types";
|
|
4
5
|
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
5
6
|
|
|
@@ -72,13 +73,15 @@ describe("{{CONTRACT_NAME}}", function () {
|
|
|
72
73
|
let deadline: number;
|
|
73
74
|
|
|
74
75
|
beforeEach(async function () {
|
|
75
|
-
|
|
76
|
+
// Get current block timestamp and add 60 seconds
|
|
77
|
+
const block = await ethers.provider.getBlock("latest");
|
|
78
|
+
deadline = block!.timestamp + 60;
|
|
76
79
|
const tx = await market.createMarket("Test question?", deadline);
|
|
77
80
|
await tx.wait();
|
|
78
81
|
marketId = 0;
|
|
79
82
|
|
|
80
|
-
//
|
|
81
|
-
await
|
|
83
|
+
// Advance blockchain time past deadline
|
|
84
|
+
await time.increaseTo(deadline + 1);
|
|
82
85
|
});
|
|
83
86
|
|
|
84
87
|
it("Should allow oracle to resolve market", async function () {
|
|
@@ -140,11 +143,12 @@ describe("{{CONTRACT_NAME}}", function () {
|
|
|
140
143
|
it("Should handle complete market lifecycle", async function () {
|
|
141
144
|
// 1. Create market
|
|
142
145
|
const question = "Will BTC hit $100k?";
|
|
143
|
-
const
|
|
146
|
+
const block = await ethers.provider.getBlock("latest");
|
|
147
|
+
const deadline = block!.timestamp + 60;
|
|
144
148
|
await market.createMarket(question, deadline);
|
|
145
149
|
|
|
146
|
-
// 2.
|
|
147
|
-
await
|
|
150
|
+
// 2. Advance time past deadline
|
|
151
|
+
await time.increaseTo(deadline + 1);
|
|
148
152
|
|
|
149
153
|
// 3. Resolve market
|
|
150
154
|
await market.resolveMarket(0, true);
|
|
@@ -1,32 +1,39 @@
|
|
|
1
|
-
import
|
|
1
|
+
import hre from "hardhat";
|
|
2
2
|
|
|
3
3
|
async function main() {
|
|
4
|
-
const tokenName = "Confidential Token";
|
|
5
|
-
const tokenSymbol = "CFHE";
|
|
6
|
-
|
|
7
4
|
console.log("Deploying {{CONTRACT_NAME}}...");
|
|
8
|
-
console.log(" Name:", tokenName);
|
|
9
|
-
console.log(" Symbol:", tokenSymbol);
|
|
10
5
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
6
|
+
const Factory = await hre.ethers.getContractFactory("{{CONTRACT_NAME}}");
|
|
7
|
+
const contract = await Factory.deploy();
|
|
8
|
+
|
|
9
|
+
await contract.waitForDeployment();
|
|
10
|
+
|
|
11
|
+
const address = await contract.getAddress();
|
|
12
|
+
console.log(`{{CONTRACT_NAME}} deployed to: ${address}`);
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Verify on explorer if not local
|
|
15
|
+
if (hre.network.name !== "hardhat" && hre.network.name !== "localhost") {
|
|
16
|
+
console.log("Waiting for block confirmations...");
|
|
17
|
+
await contract.deploymentTransaction()?.wait(5);
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
console.log("Verifying contract...");
|
|
20
|
+
try {
|
|
21
|
+
await hre.run("verify:verify", {
|
|
22
|
+
address: address,
|
|
23
|
+
constructorArguments: [],
|
|
24
|
+
});
|
|
25
|
+
console.log("Contract verified!");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.log("Verification failed:", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
console.log(" - mint(to, encryptedAmount): Mint tokens (owner only)");
|
|
21
|
-
console.log(" - transfer(to, encryptedAmount): Transfer tokens");
|
|
22
|
-
console.log(" - balanceOf(account): Get encrypted balance handle");
|
|
23
|
-
console.log("\nPrivacy Guarantees:");
|
|
24
|
-
console.log(" - Balances are encrypted");
|
|
25
|
-
console.log(" - Transfer amounts are private");
|
|
26
|
-
console.log(" - Only balance owner can decrypt");
|
|
31
|
+
return address;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
main()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
main()
|
|
35
|
+
.then(() => process.exit(0))
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
console.error(error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { ethers, fhevm } from "hardhat";
|
|
3
|
+
import { FhevmType } from "@fhevm/hardhat-plugin";
|
|
4
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
5
|
+
|
|
6
|
+
describe("{{CONTRACT_NAME}}", function () {
|
|
7
|
+
let contract: any;
|
|
8
|
+
let contractAddress: string;
|
|
9
|
+
let signers: { alice: HardhatEthersSigner; bob: HardhatEthersSigner };
|
|
10
|
+
|
|
11
|
+
before(async function () {
|
|
12
|
+
const [alice, bob] = await ethers.getSigners();
|
|
13
|
+
signers = { alice, bob };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
beforeEach(async function () {
|
|
17
|
+
const Factory = await ethers.getContractFactory("{{CONTRACT_NAME}}");
|
|
18
|
+
contract = await Factory.deploy("TestToken", "TTK");
|
|
19
|
+
await contract.waitForDeployment();
|
|
20
|
+
contractAddress = await contract.getAddress();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("Metadata", function () {
|
|
24
|
+
it("should return correct name", async function () {
|
|
25
|
+
expect(await contract.name()).to.eq("TestToken");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return correct symbol", async function () {
|
|
29
|
+
expect(await contract.symbol()).to.eq("TTK");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("Mint", function () {
|
|
34
|
+
it("should mint tokens to alice", async function () {
|
|
35
|
+
const encryptedInput = await fhevm
|
|
36
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
37
|
+
.add64(1000)
|
|
38
|
+
.encrypt();
|
|
39
|
+
|
|
40
|
+
const tx = await contract.mint(
|
|
41
|
+
signers.alice.address,
|
|
42
|
+
encryptedInput.handles[0],
|
|
43
|
+
encryptedInput.inputProof
|
|
44
|
+
);
|
|
45
|
+
await tx.wait();
|
|
46
|
+
|
|
47
|
+
// Get alice's encrypted balance
|
|
48
|
+
const encryptedBalance = await contract.balanceOf(signers.alice.address);
|
|
49
|
+
|
|
50
|
+
// Decrypt and verify
|
|
51
|
+
const clearBalance = await fhevm.userDecryptEuint(
|
|
52
|
+
FhevmType.euint64,
|
|
53
|
+
encryptedBalance,
|
|
54
|
+
contractAddress,
|
|
55
|
+
signers.alice
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(clearBalance).to.eq(1000);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should mint more tokens and update balance", async function () {
|
|
62
|
+
// First mint
|
|
63
|
+
const input1 = await fhevm
|
|
64
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
65
|
+
.add64(1000)
|
|
66
|
+
.encrypt();
|
|
67
|
+
|
|
68
|
+
await contract.mint(
|
|
69
|
+
signers.alice.address,
|
|
70
|
+
input1.handles[0],
|
|
71
|
+
input1.inputProof
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Mint 500 more tokens
|
|
75
|
+
const input2 = await fhevm
|
|
76
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
77
|
+
.add64(500)
|
|
78
|
+
.encrypt();
|
|
79
|
+
|
|
80
|
+
await contract.mint(
|
|
81
|
+
signers.alice.address,
|
|
82
|
+
input2.handles[0],
|
|
83
|
+
input2.inputProof
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Get alice's encrypted balance (should be 1500)
|
|
87
|
+
const encryptedBalance = await contract.balanceOf(signers.alice.address);
|
|
88
|
+
|
|
89
|
+
const clearBalance = await fhevm.userDecryptEuint(
|
|
90
|
+
FhevmType.euint64,
|
|
91
|
+
encryptedBalance,
|
|
92
|
+
contractAddress,
|
|
93
|
+
signers.alice
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(clearBalance).to.eq(1500);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("Transfer", function () {
|
|
101
|
+
it("should transfer tokens from alice to bob", async function () {
|
|
102
|
+
// First mint tokens to alice
|
|
103
|
+
const mintInput = await fhevm
|
|
104
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
105
|
+
.add64(1000)
|
|
106
|
+
.encrypt();
|
|
107
|
+
|
|
108
|
+
await contract.mint(
|
|
109
|
+
signers.alice.address,
|
|
110
|
+
mintInput.handles[0],
|
|
111
|
+
mintInput.inputProof
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Alice transfers 300 tokens to bob
|
|
115
|
+
const transferInput = await fhevm
|
|
116
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
117
|
+
.add64(300)
|
|
118
|
+
.encrypt();
|
|
119
|
+
|
|
120
|
+
const tx = await contract.connect(signers.alice).transfer(
|
|
121
|
+
signers.bob.address,
|
|
122
|
+
transferInput.handles[0],
|
|
123
|
+
transferInput.inputProof
|
|
124
|
+
);
|
|
125
|
+
await tx.wait();
|
|
126
|
+
|
|
127
|
+
// Check alice's balance (should be 1000 - 300 = 700)
|
|
128
|
+
const aliceEncryptedBalance = await contract.balanceOf(signers.alice.address);
|
|
129
|
+
const aliceClearBalance = await fhevm.userDecryptEuint(
|
|
130
|
+
FhevmType.euint64,
|
|
131
|
+
aliceEncryptedBalance,
|
|
132
|
+
contractAddress,
|
|
133
|
+
signers.alice
|
|
134
|
+
);
|
|
135
|
+
expect(aliceClearBalance).to.eq(700);
|
|
136
|
+
|
|
137
|
+
// Check bob's balance (should be 300)
|
|
138
|
+
const bobEncryptedBalance = await contract.balanceOf(signers.bob.address);
|
|
139
|
+
const bobClearBalance = await fhevm.userDecryptEuint(
|
|
140
|
+
FhevmType.euint64,
|
|
141
|
+
bobEncryptedBalance,
|
|
142
|
+
contractAddress,
|
|
143
|
+
signers.bob
|
|
144
|
+
);
|
|
145
|
+
expect(bobClearBalance).to.eq(300);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should handle transfer exceeding balance (no transfer occurs)", async function () {
|
|
149
|
+
// Mint 300 tokens to bob
|
|
150
|
+
const mintInput = await fhevm
|
|
151
|
+
.createEncryptedInput(contractAddress, signers.bob.address)
|
|
152
|
+
.add64(300)
|
|
153
|
+
.encrypt();
|
|
154
|
+
|
|
155
|
+
await contract.mint(
|
|
156
|
+
signers.bob.address,
|
|
157
|
+
mintInput.handles[0],
|
|
158
|
+
mintInput.inputProof
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Bob tries to transfer 500 tokens (only has 300)
|
|
162
|
+
const transferInput = await fhevm
|
|
163
|
+
.createEncryptedInput(contractAddress, signers.bob.address)
|
|
164
|
+
.add64(500)
|
|
165
|
+
.encrypt();
|
|
166
|
+
|
|
167
|
+
const tx = await contract.connect(signers.bob).transfer(
|
|
168
|
+
signers.alice.address,
|
|
169
|
+
transferInput.handles[0],
|
|
170
|
+
transferInput.inputProof
|
|
171
|
+
);
|
|
172
|
+
await tx.wait();
|
|
173
|
+
|
|
174
|
+
// Bob's balance should still be 300 (transfer didn't happen)
|
|
175
|
+
const bobEncryptedBalance = await contract.balanceOf(signers.bob.address);
|
|
176
|
+
const bobClearBalance = await fhevm.userDecryptEuint(
|
|
177
|
+
FhevmType.euint64,
|
|
178
|
+
bobEncryptedBalance,
|
|
179
|
+
contractAddress,
|
|
180
|
+
signers.bob
|
|
181
|
+
);
|
|
182
|
+
expect(bobClearBalance).to.eq(300);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
{{TEST_EXTENSIONS}}
|
|
187
|
+
});
|
|
@@ -80,8 +80,8 @@ contract {{CONTRACT_NAME}} is {{INHERITS}} {
|
|
|
80
80
|
Proposal storage proposal = _proposals[proposalId];
|
|
81
81
|
proposal.description = description;
|
|
82
82
|
proposal.endTime = block.timestamp + durationSeconds;
|
|
83
|
-
proposal.yesVotes = FHE.
|
|
84
|
-
proposal.noVotes = FHE.
|
|
83
|
+
proposal.yesVotes = FHE.asEuint32(0);
|
|
84
|
+
proposal.noVotes = FHE.asEuint32(0);
|
|
85
85
|
proposal.exists = true;
|
|
86
86
|
|
|
87
87
|
// Set ACL for vote counts
|
|
@@ -118,13 +118,13 @@ contract {{CONTRACT_NAME}} is {{INHERITS}} {
|
|
|
118
118
|
[[VOTE_TYPE]] eVote = FHE.fromExternal(encryptedVote, inputProof);
|
|
119
119
|
|
|
120
120
|
// Encrypted vote: if vote > 0, it's a yes vote
|
|
121
|
-
ebool isYes = FHE.gt(eVote, FHE.
|
|
121
|
+
ebool isYes = FHE.gt(eVote, FHE.asEuint32(0));
|
|
122
122
|
|
|
123
123
|
// Homomorphic conditional addition
|
|
124
124
|
// yesVotes += isYes ? 1 : 0
|
|
125
125
|
// noVotes += isYes ? 0 : 1
|
|
126
|
-
[[VOTE_TYPE]] one = FHE.
|
|
127
|
-
[[VOTE_TYPE]] zero = FHE.
|
|
126
|
+
[[VOTE_TYPE]] one = FHE.asEuint32(1);
|
|
127
|
+
[[VOTE_TYPE]] zero = FHE.asEuint32(0);
|
|
128
128
|
|
|
129
129
|
proposal.yesVotes = FHE.add(proposal.yesVotes, FHE.select(isYes, one, zero));
|
|
130
130
|
proposal.noVotes = FHE.add(proposal.noVotes, FHE.select(isYes, zero, one));
|
|
@@ -1,28 +1,39 @@
|
|
|
1
|
-
import
|
|
1
|
+
import hre from "hardhat";
|
|
2
2
|
|
|
3
3
|
async function main() {
|
|
4
4
|
console.log("Deploying {{CONTRACT_NAME}}...");
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const Factory = await hre.ethers.getContractFactory("{{CONTRACT_NAME}}");
|
|
7
|
+
const contract = await Factory.deploy();
|
|
8
8
|
|
|
9
|
-
await
|
|
9
|
+
await contract.waitForDeployment();
|
|
10
10
|
|
|
11
|
-
const address = await
|
|
12
|
-
console.log(
|
|
11
|
+
const address = await contract.getAddress();
|
|
12
|
+
console.log(`{{CONTRACT_NAME}} deployed to: ${address}`);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
// Verify on explorer if not local
|
|
15
|
+
if (hre.network.name !== "hardhat" && hre.network.name !== "localhost") {
|
|
16
|
+
console.log("Waiting for block confirmations...");
|
|
17
|
+
await contract.deploymentTransaction()?.wait(5);
|
|
18
|
+
|
|
19
|
+
console.log("Verifying contract...");
|
|
20
|
+
try {
|
|
21
|
+
await hre.run("verify:verify", {
|
|
22
|
+
address: address,
|
|
23
|
+
constructorArguments: [],
|
|
24
|
+
});
|
|
25
|
+
console.log("Contract verified!");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.log("Verification failed:", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return address;
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
main()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
main()
|
|
35
|
+
.then(() => process.exit(0))
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
console.error(error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { ethers, fhevm } from "hardhat";
|
|
3
|
+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
|
|
4
|
+
|
|
5
|
+
describe("{{CONTRACT_NAME}}", function () {
|
|
6
|
+
let contract: any;
|
|
7
|
+
let contractAddress: string;
|
|
8
|
+
let signers: { alice: HardhatEthersSigner; bob: HardhatEthersSigner; charlie: HardhatEthersSigner };
|
|
9
|
+
|
|
10
|
+
before(async function () {
|
|
11
|
+
const [alice, bob, charlie] = await ethers.getSigners();
|
|
12
|
+
signers = { alice, bob, charlie };
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
beforeEach(async function () {
|
|
16
|
+
const Factory = await ethers.getContractFactory("{{CONTRACT_NAME}}");
|
|
17
|
+
contract = await Factory.deploy();
|
|
18
|
+
await contract.waitForDeployment();
|
|
19
|
+
contractAddress = await contract.getAddress();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("Proposal Creation", function () {
|
|
23
|
+
it("should create a proposal", async function () {
|
|
24
|
+
const tx = await contract.createProposal("Should we upgrade?", 3600); // 1 hour duration
|
|
25
|
+
await tx.wait();
|
|
26
|
+
|
|
27
|
+
const [description, endTime, exists, finalized] = await contract.getProposal(1);
|
|
28
|
+
|
|
29
|
+
expect(description).to.eq("Should we upgrade?");
|
|
30
|
+
expect(exists).to.be.true;
|
|
31
|
+
expect(finalized).to.be.false;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should increment proposal ID", async function () {
|
|
35
|
+
await contract.createProposal("Proposal 1", 3600);
|
|
36
|
+
expect(await contract.nextProposalId()).to.eq(2);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("Voting", function () {
|
|
41
|
+
beforeEach(async function () {
|
|
42
|
+
// Create a proposal for voting tests
|
|
43
|
+
await contract.createProposal("Test Proposal", 3600);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should allow alice to vote YES (vote = 1)", async function () {
|
|
47
|
+
const encryptedInput = await fhevm
|
|
48
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
49
|
+
.add32(1) // YES vote
|
|
50
|
+
.encrypt();
|
|
51
|
+
|
|
52
|
+
const tx = await contract.connect(signers.alice).vote(
|
|
53
|
+
1, // proposalId
|
|
54
|
+
encryptedInput.handles[0],
|
|
55
|
+
encryptedInput.inputProof
|
|
56
|
+
);
|
|
57
|
+
await tx.wait();
|
|
58
|
+
|
|
59
|
+
expect(await contract.hasVoted(1, signers.alice.address)).to.be.true;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should allow bob to vote NO (vote = 0)", async function () {
|
|
63
|
+
const encryptedInput = await fhevm
|
|
64
|
+
.createEncryptedInput(contractAddress, signers.bob.address)
|
|
65
|
+
.add32(0) // NO vote
|
|
66
|
+
.encrypt();
|
|
67
|
+
|
|
68
|
+
const tx = await contract.connect(signers.bob).vote(
|
|
69
|
+
1,
|
|
70
|
+
encryptedInput.handles[0],
|
|
71
|
+
encryptedInput.inputProof
|
|
72
|
+
);
|
|
73
|
+
await tx.wait();
|
|
74
|
+
|
|
75
|
+
expect(await contract.hasVoted(1, signers.bob.address)).to.be.true;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should allow charlie to vote YES", async function () {
|
|
79
|
+
const encryptedInput = await fhevm
|
|
80
|
+
.createEncryptedInput(contractAddress, signers.charlie.address)
|
|
81
|
+
.add32(5) // Any value > 0 is YES
|
|
82
|
+
.encrypt();
|
|
83
|
+
|
|
84
|
+
const tx = await contract.connect(signers.charlie).vote(
|
|
85
|
+
1,
|
|
86
|
+
encryptedInput.handles[0],
|
|
87
|
+
encryptedInput.inputProof
|
|
88
|
+
);
|
|
89
|
+
await tx.wait();
|
|
90
|
+
|
|
91
|
+
expect(await contract.hasVoted(1, signers.charlie.address)).to.be.true;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should prevent double voting", async function () {
|
|
95
|
+
// First vote
|
|
96
|
+
const input1 = await fhevm
|
|
97
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
98
|
+
.add32(1)
|
|
99
|
+
.encrypt();
|
|
100
|
+
await contract.connect(signers.alice).vote(1, input1.handles[0], input1.inputProof);
|
|
101
|
+
|
|
102
|
+
// Try to vote again
|
|
103
|
+
const input2 = await fhevm
|
|
104
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
105
|
+
.add32(1)
|
|
106
|
+
.encrypt();
|
|
107
|
+
|
|
108
|
+
await expect(
|
|
109
|
+
contract.connect(signers.alice).vote(1, input2.handles[0], input2.inputProof)
|
|
110
|
+
).to.be.revertedWithCustomError(contract, "AlreadyVoted");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("Vote Counts", function () {
|
|
115
|
+
it("should return encrypted vote counts (private by design)", async function () {
|
|
116
|
+
// Create proposal and cast votes
|
|
117
|
+
await contract.createProposal("Test Proposal", 3600);
|
|
118
|
+
|
|
119
|
+
const input1 = await fhevm
|
|
120
|
+
.createEncryptedInput(contractAddress, signers.alice.address)
|
|
121
|
+
.add32(1)
|
|
122
|
+
.encrypt();
|
|
123
|
+
await contract.connect(signers.alice).vote(1, input1.handles[0], input1.inputProof);
|
|
124
|
+
|
|
125
|
+
// Vote counts are intentionally private
|
|
126
|
+
const [yesVotes, noVotes] = await contract.getVoteCounts(1);
|
|
127
|
+
|
|
128
|
+
// Verify handles are returned (non-zero bytes32)
|
|
129
|
+
expect(yesVotes).to.not.eq(ethers.ZeroHash);
|
|
130
|
+
expect(noVotes).to.not.eq(ethers.ZeroHash);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
{{TEST_EXTENSIONS}}
|
|
135
|
+
});
|