@semaphore-protocol/cli-template-contracts-hardhat 3.6.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/README.md +54 -0
- package/contracts/Feedback.sol +30 -0
- package/hardhat.config.ts +77 -0
- package/package.json +79 -0
- package/scripts/download-snark-artifacts.ts +24 -0
- package/tasks/deploy.ts +31 -0
- package/test/Feedback.ts +69 -0
- package/tsconfig.json +15 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Semaphore Hardhat template
|
|
2
|
+
|
|
3
|
+
This project demonstrates a basic Semaphore use case. It comes with a sample contract, a test for that contract and a sample task that deploys that contract.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### Compile
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn compile
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Testing
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You can also generate a test coverage report:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
yarn test:coverage
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or a test gas report:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
yarn test:report-gas
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Deploy
|
|
32
|
+
|
|
33
|
+
1. Copy the `.env.example` file as `.env`.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cp .env.example .env
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. Add your environment variables.
|
|
40
|
+
|
|
41
|
+
> **Note**
|
|
42
|
+
> You should at least set a valid Ethereum URL (e.g. Infura) and a private key with some ethers.
|
|
43
|
+
|
|
44
|
+
3. And deploy your contract.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
yarn deploy --semaphore <semaphore-address> --group <group-id> --network goerli
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> **Note**
|
|
51
|
+
> Check the Semaphore contract addresses [here](https://semaphore.appliedzkp.org/docs/deployed-contracts#semaphore).
|
|
52
|
+
|
|
53
|
+
> **Warning**
|
|
54
|
+
> The group id is a number!
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.4;
|
|
3
|
+
|
|
4
|
+
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
|
|
5
|
+
|
|
6
|
+
contract Feedback {
|
|
7
|
+
ISemaphore public semaphore;
|
|
8
|
+
|
|
9
|
+
uint256 public groupId;
|
|
10
|
+
|
|
11
|
+
constructor(address semaphoreAddress, uint256 _groupId) {
|
|
12
|
+
semaphore = ISemaphore(semaphoreAddress);
|
|
13
|
+
groupId = _groupId;
|
|
14
|
+
|
|
15
|
+
semaphore.createGroup(groupId, 20, address(this));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function joinGroup(uint256 identityCommitment) external {
|
|
19
|
+
semaphore.addMember(groupId, identityCommitment);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sendFeedback(
|
|
23
|
+
uint256 feedback,
|
|
24
|
+
uint256 merkleTreeRoot,
|
|
25
|
+
uint256 nullifierHash,
|
|
26
|
+
uint256[8] calldata proof
|
|
27
|
+
) external {
|
|
28
|
+
semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import "@nomiclabs/hardhat-ethers"
|
|
2
|
+
import "@nomicfoundation/hardhat-chai-matchers"
|
|
3
|
+
import "@semaphore-protocol/hardhat"
|
|
4
|
+
import "@typechain/hardhat"
|
|
5
|
+
import { config as dotenvConfig } from "dotenv"
|
|
6
|
+
import "hardhat-gas-reporter"
|
|
7
|
+
import { HardhatUserConfig } from "hardhat/config"
|
|
8
|
+
import { NetworksUserConfig } from "hardhat/types"
|
|
9
|
+
import "solidity-coverage"
|
|
10
|
+
import { config } from "./package.json"
|
|
11
|
+
import "./tasks/deploy"
|
|
12
|
+
|
|
13
|
+
dotenvConfig()
|
|
14
|
+
|
|
15
|
+
function getNetworks(): NetworksUserConfig {
|
|
16
|
+
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
|
|
17
|
+
return {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
|
|
21
|
+
const infuraApiKey = process.env.INFURA_API_KEY
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
goerli: {
|
|
25
|
+
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
|
|
26
|
+
chainId: 5,
|
|
27
|
+
accounts
|
|
28
|
+
},
|
|
29
|
+
sepolia: {
|
|
30
|
+
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
|
31
|
+
chainId: 11155111,
|
|
32
|
+
accounts
|
|
33
|
+
},
|
|
34
|
+
mumbai: {
|
|
35
|
+
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
|
36
|
+
chainId: 80001,
|
|
37
|
+
accounts
|
|
38
|
+
},
|
|
39
|
+
"optimism-goerli": {
|
|
40
|
+
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
|
41
|
+
chainId: 420,
|
|
42
|
+
accounts
|
|
43
|
+
},
|
|
44
|
+
arbitrum: {
|
|
45
|
+
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
|
|
46
|
+
chainId: 42161,
|
|
47
|
+
accounts
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const hardhatConfig: HardhatUserConfig = {
|
|
53
|
+
solidity: config.solidity,
|
|
54
|
+
paths: {
|
|
55
|
+
sources: config.paths.contracts,
|
|
56
|
+
tests: config.paths.tests,
|
|
57
|
+
cache: config.paths.cache,
|
|
58
|
+
artifacts: config.paths.build.contracts
|
|
59
|
+
},
|
|
60
|
+
networks: {
|
|
61
|
+
hardhat: {
|
|
62
|
+
chainId: 1337
|
|
63
|
+
},
|
|
64
|
+
...getNetworks()
|
|
65
|
+
},
|
|
66
|
+
gasReporter: {
|
|
67
|
+
currency: "USD",
|
|
68
|
+
enabled: process.env.REPORT_GAS === "true",
|
|
69
|
+
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
|
70
|
+
},
|
|
71
|
+
typechain: {
|
|
72
|
+
outDir: config.paths.build.typechain,
|
|
73
|
+
target: "ethers-v5"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default hardhatConfig
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@semaphore-protocol/cli-template-contracts-hardhat",
|
|
3
|
+
"version": "3.6.0-1",
|
|
4
|
+
"description": "Semaphore Hardhat template.",
|
|
5
|
+
"license": "Unlicense",
|
|
6
|
+
"files": [
|
|
7
|
+
".gitignore",
|
|
8
|
+
".env.example",
|
|
9
|
+
"contracts/",
|
|
10
|
+
"scripts/",
|
|
11
|
+
"tasks/",
|
|
12
|
+
"test/",
|
|
13
|
+
"hardhat.config.ts",
|
|
14
|
+
"tsconfig.json",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
|
|
23
|
+
"compile": "hardhat compile",
|
|
24
|
+
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
|
|
25
|
+
"deploy": "yarn compile && hardhat deploy",
|
|
26
|
+
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
|
|
27
|
+
"test:report-gas": "REPORT_GAS=true hardhat test",
|
|
28
|
+
"test:coverage": "hardhat coverage",
|
|
29
|
+
"typechain": "hardhat typechain"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@ethersproject/abi": "^5.4.7",
|
|
33
|
+
"@ethersproject/providers": "^5.4.7",
|
|
34
|
+
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
|
|
35
|
+
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
|
|
36
|
+
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
|
37
|
+
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
|
38
|
+
"@nomiclabs/hardhat-etherscan": "^3.0.0",
|
|
39
|
+
"@semaphore-protocol/group": "3.6.0-1",
|
|
40
|
+
"@semaphore-protocol/hardhat": "3.6.0-1",
|
|
41
|
+
"@semaphore-protocol/identity": "3.6.0-1",
|
|
42
|
+
"@semaphore-protocol/proof": "3.6.0-1",
|
|
43
|
+
"@typechain/ethers-v5": "^10.1.0",
|
|
44
|
+
"@typechain/hardhat": "^6.1.2",
|
|
45
|
+
"@types/chai": "^4.2.0",
|
|
46
|
+
"@types/download": "^8.0.1",
|
|
47
|
+
"@types/mocha": "^9.1.0",
|
|
48
|
+
"@types/node": ">=12.0.0",
|
|
49
|
+
"chai": "^4.2.0",
|
|
50
|
+
"dotenv": "^16.0.3",
|
|
51
|
+
"download": "^8.0.0",
|
|
52
|
+
"ethers": "^5.4.7",
|
|
53
|
+
"hardhat": "^2.11.0",
|
|
54
|
+
"hardhat-gas-reporter": "^1.0.8",
|
|
55
|
+
"solidity-coverage": "^0.8.1",
|
|
56
|
+
"ts-node": ">=8.0.0",
|
|
57
|
+
"typechain": "^8.1.0",
|
|
58
|
+
"typescript": ">=4.5.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@semaphore-protocol/contracts": "3.6.0-1"
|
|
62
|
+
},
|
|
63
|
+
"config": {
|
|
64
|
+
"solidity": {
|
|
65
|
+
"version": "0.8.4"
|
|
66
|
+
},
|
|
67
|
+
"paths": {
|
|
68
|
+
"contracts": "./contracts",
|
|
69
|
+
"tests": "./test",
|
|
70
|
+
"cache": "./cache",
|
|
71
|
+
"build": {
|
|
72
|
+
"snark-artifacts": "./build/snark-artifacts",
|
|
73
|
+
"contracts": "./build/contracts",
|
|
74
|
+
"typechain": "./build/typechain"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"stableVersion": "3.5.0"
|
|
79
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import download from "download"
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import { config } from "../package.json"
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const snarkArtifactsPath = config.paths.build["snark-artifacts"]
|
|
7
|
+
const url = `http://www.trusted-setup-pse.org/semaphore/${20}`
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(snarkArtifactsPath)) {
|
|
10
|
+
fs.mkdirSync(snarkArtifactsPath, { recursive: true })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
|
|
14
|
+
await download(`${url}/semaphore.wasm`, snarkArtifactsPath)
|
|
15
|
+
await download(`${url}/semaphore.zkey`, snarkArtifactsPath)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
main()
|
|
20
|
+
.then(() => process.exit(0))
|
|
21
|
+
.catch((error) => {
|
|
22
|
+
console.error(error)
|
|
23
|
+
process.exit(1)
|
|
24
|
+
})
|
package/tasks/deploy.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { task, types } from "hardhat/config"
|
|
2
|
+
|
|
3
|
+
task("deploy", "Deploy a Feedback contract")
|
|
4
|
+
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.string)
|
|
5
|
+
.addOptionalParam("group", "Group id", "42", types.string)
|
|
6
|
+
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
|
7
|
+
.setAction(async ({ logs, semaphore: semaphoreAddress, group: groupId }, { ethers, run }) => {
|
|
8
|
+
if (!semaphoreAddress) {
|
|
9
|
+
const { semaphore } = await run("deploy:semaphore", {
|
|
10
|
+
logs
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
semaphoreAddress = semaphore.address
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!groupId) {
|
|
17
|
+
groupId = process.env.GROUP_ID
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const FeedbackFactory = await ethers.getContractFactory("Feedback")
|
|
21
|
+
|
|
22
|
+
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
|
|
23
|
+
|
|
24
|
+
await feedbackContract.deployed()
|
|
25
|
+
|
|
26
|
+
if (logs) {
|
|
27
|
+
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return feedbackContract
|
|
31
|
+
})
|
package/test/Feedback.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Group } from "@semaphore-protocol/group"
|
|
2
|
+
import { Identity } from "@semaphore-protocol/identity"
|
|
3
|
+
import { generateProof } from "@semaphore-protocol/proof"
|
|
4
|
+
import { expect } from "chai"
|
|
5
|
+
import { formatBytes32String } from "ethers/lib/utils"
|
|
6
|
+
import { run } from "hardhat"
|
|
7
|
+
// @ts-ignore: typechain folder will be generated after contracts compilation
|
|
8
|
+
import { Feedback } from "../build/typechain"
|
|
9
|
+
import { config } from "../package.json"
|
|
10
|
+
|
|
11
|
+
describe("Feedback", () => {
|
|
12
|
+
let feedbackContract: Feedback
|
|
13
|
+
let semaphoreContract: string
|
|
14
|
+
|
|
15
|
+
const groupId = "42"
|
|
16
|
+
const group = new Group(groupId)
|
|
17
|
+
const users: Identity[] = []
|
|
18
|
+
|
|
19
|
+
before(async () => {
|
|
20
|
+
const { semaphore } = await run("deploy:semaphore", {
|
|
21
|
+
logs: false
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
|
|
25
|
+
semaphoreContract = semaphore
|
|
26
|
+
|
|
27
|
+
users.push(new Identity())
|
|
28
|
+
users.push(new Identity())
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe("# joinGroup", () => {
|
|
32
|
+
it("Should allow users to join the group", async () => {
|
|
33
|
+
for await (const [i, user] of users.entries()) {
|
|
34
|
+
const transaction = feedbackContract.joinGroup(user.commitment)
|
|
35
|
+
|
|
36
|
+
group.addMember(user.commitment)
|
|
37
|
+
|
|
38
|
+
await expect(transaction)
|
|
39
|
+
.to.emit(semaphoreContract, "MemberAdded")
|
|
40
|
+
.withArgs(groupId, i, user.commitment, group.root)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe("# sendFeedback", () => {
|
|
46
|
+
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
|
|
47
|
+
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
|
|
48
|
+
|
|
49
|
+
it("Should allow users to send feedback anonymously", async () => {
|
|
50
|
+
const feedback = formatBytes32String("Hello World")
|
|
51
|
+
|
|
52
|
+
const fullProof = await generateProof(users[1], group, groupId, feedback, {
|
|
53
|
+
wasmFilePath,
|
|
54
|
+
zkeyFilePath
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const transaction = feedbackContract.sendFeedback(
|
|
58
|
+
feedback,
|
|
59
|
+
fullProof.merkleTreeRoot,
|
|
60
|
+
fullProof.nullifierHash,
|
|
61
|
+
fullProof.proof
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
await expect(transaction)
|
|
65
|
+
.to.emit(semaphoreContract, "ProofVerified")
|
|
66
|
+
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"moduleResolution": "Node",
|
|
4
|
+
"noImplicitAny": true,
|
|
5
|
+
"resolveJsonModule": true,
|
|
6
|
+
"target": "ES2018",
|
|
7
|
+
"module": "CommonJS",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"outDir": "dist",
|
|
11
|
+
"typeRoots": ["node_modules/@types", "types"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
|
|
14
|
+
"files": ["./hardhat.config.ts"]
|
|
15
|
+
}
|