diff --git a/.eslintignore b/.eslintignore index 1d32fe2d..3cf5385d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -24,6 +24,10 @@ dist build /docs +# subgraph +subgraph.template.yaml +generated + # Docusaurus cache and generated files .docusaurus .cache-loader diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 7c4956b8..f1d3a513 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -43,12 +43,48 @@ jobs: - name: Build libraries run: yarn build:libraries + - name: Build subgraph + run: yarn build:subgraph + - name: Run Prettier run: yarn prettier - name: Run Eslint run: yarn lint + test-subgraph: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Node.js + uses: actions/setup-node@v1 + with: + node-version: 16.x + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + + - name: Restore yarn cache + uses: actions/cache@v3 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install dependencies + run: yarn + + - name: Build subgraph + run: yarn build:subgraph + + - name: Test subgraph + run: yarn test:subgraph + test: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 1a94ec74..9339ef1f 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -41,6 +41,9 @@ jobs: - name: Build libraries run: yarn build:libraries + - name: Build subgraph + run: yarn build:subgraph + - name: Run Prettier run: yarn prettier @@ -77,5 +80,8 @@ jobs: - name: Build libraries run: yarn build:libraries - - name: Test contracts and libraries + - name: Build subgraph + run: yarn build:subgraph + + - name: Test contracts, libraries and subgraph run: yarn test diff --git a/.prettierignore b/.prettierignore index 5b70d8a1..1b01a7f1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -27,6 +27,10 @@ dist build /docs +# subgraph +subgraph.template.yaml +generated + # github .github/ISSUE_TEMPLATE diff --git a/apps/subgraph/.gitignore b/apps/subgraph/.gitignore new file mode 100644 index 00000000..cc96ad5c --- /dev/null +++ b/apps/subgraph/.gitignore @@ -0,0 +1,5 @@ +# Generate output +generated + +# The Graph +subgraph.yaml diff --git a/apps/subgraph/README.md b/apps/subgraph/README.md new file mode 100644 index 00000000..0fa25361 --- /dev/null +++ b/apps/subgraph/README.md @@ -0,0 +1,137 @@ +

+ Semaphore Subgraph +

+ +

+ + + + + Github license + + + Linter eslint + + + Code style prettier + +

+ +
+

+ + 👥 Contributing + +   |   + + 🤝 Code of conduct + +   |   + + 🔎 Issues + +   |   + + 🗣️ Chat & Support + +

+
+ +| The Graph is an indexing protocol for querying networks like Ethereum and IPFS. Our subgraphs allow you to get data from the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/contracts/Semaphore.sol) smart contract. | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + +## Networks + +| Semaphore version | Sepolia | Goerli | Mumbai | Optimism Goerli | Arbitrum Goerli | Arbitrum One | +| ----------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| v2.0 | N/A | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum) | +| v2.5 | N/A | [semaphore-protocol/goerli](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli) | N/A | N/A | N/A | N/A | +| v2.6 | N/A | [semaphore-protocol/goerli-5259d3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-5259d3) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-86337c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-86337c) | +| v3.0 - v3.1 | N/A | [semaphore-protocol/goerli-89490c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-89490c) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-72dca3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-72dca3) | +| >= v3.2 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v3.6.1) | [semaphore-goerli](https://api.studio.thegraph.com/query/14377/semaphore-goerli/v3.6.1) | [semaphore-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-mumbai/v3.6.1) | [semaphore-optimism-goerli](https://api.studio.thegraph.com/query/14377/semaphore-optimism-goerli/v3.6.1) | [semaphore-arbitrum-goerli](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum-goerli/v3.6.1) | [semaphore-arbitrum](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.6.1) | + +## 🛠 Install + +Clone this repository: + +```bash +git clone https://github.com/semaphore-protocol/semaphore.git +``` + +and install the dependencies: + +```bash +cd semaphore/apps/subgraph && yarn +``` + +## Usage + +The subgraph definition consists of a few files: + +- `subgraph.template.yaml`: a YAML file containing the subgraph manifest, +- `schema.graphql`: a GraphQL schema that defines what data is stored for the subgraph, and how to query it via GraphQL, +- `src/semaphore.ts`: AssemblyScript code that translates from the event data to the entities defined in the schema. + +### Code generation + +Generate AssemblyScript types for the subgraph (required every time the schema changes): + +```bash +yarn codegen +``` + +It also generates a `subgraph.yaml` file for your specific network. + +### Testing + +After generating the types and `subgraph.yaml` file, test your subgraph: + +```bash +yarn test +``` + +### Deployment + +#### TheGraph Studio + +Set the authorization code that links your account on thegraph.com: + +```bash +yarn auth +``` + +Deploy the subgraph to the [TheGraph Studio](https://thegraph.com/studio/): + +```bash +yarn deploy +``` + +#### Local + +Start services required for TheGraph node by running: + +```bash +docker compose -f docker-compose-graph.yml up +``` + +Start a local Hardhat node and deploy the [Semaphore contract](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts): + +```bash +# CWD = /semaphore/packages/contracts +yarn start +yarn deploy:semaphore --network localhost +``` + +Create the `subgraph.yaml` file for your local network and create/deploy your subgraph: + +```bash +yarn codegen localhost +yarn create-local +yarn deploy-local +``` + +Once the subgraph is published it will start indexing. You can query the subgraph using the following GraphQL endpoint: + +``` +http://127.0.0.1:8000/subgraphs/name/sempahore/graphql +``` diff --git a/apps/subgraph/abis/Semaphore.json b/apps/subgraph/abis/Semaphore.json new file mode 100644 index 00000000..5a052415 --- /dev/null +++ b/apps/subgraph/abis/Semaphore.json @@ -0,0 +1,626 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "Semaphore", + "sourceName": "contracts/Semaphore.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "contract ISemaphoreVerifier", + "name": "_verifier", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "Semaphore__CallerIsNotTheGroupAdmin", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__GroupAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__GroupDoesNotExist", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__MerkleTreeDepthIsNotSupported", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__MerkleTreeRootIsExpired", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__MerkleTreeRootIsNotPartOfTheGroup", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__YouAreUsingTheSameNillifierTwice", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "oldAdmin", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "GroupAdminUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "zeroValue", + "type": "uint256" + } + ], + "name": "GroupCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldMerkleTreeDuration", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newMerkleTreeDuration", + "type": "uint256" + } + ], + "name": "GroupMerkleTreeDurationUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + } + ], + "name": "MemberAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + } + ], + "name": "MemberRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newIdentityCommitment", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + } + ], + "name": "MemberUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nullifierHash", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "externalNullifier", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "signal", + "type": "uint256" + } + ], + "name": "ProofVerified", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" + } + ], + "name": "addMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "identityCommitments", + "type": "uint256[]" + } + ], + "name": "addMembers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" + }, + { + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "merkleTreeDuration", + "type": "uint256" + } + ], + "name": "createGroup", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" + }, + { + "internalType": "address", + "name": "admin", + "type": "address" + } + ], + "name": "createGroup", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + } + ], + "name": "getMerkleTreeDepth", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + } + ], + "name": "getMerkleTreeRoot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + } + ], + "name": "getNumberOfMerkleTreeLeaves", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "groups", + "outputs": [ + { + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "merkleTreeDuration", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "proofSiblings", + "type": "uint256[]" + }, + { + "internalType": "uint8[]", + "name": "proofPathIndices", + "type": "uint8[]" + } + ], + "name": "removeMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "updateGroupAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newMerkleTreeDuration", + "type": "uint256" + } + ], + "name": "updateGroupMerkleTreeDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newIdentityCommitment", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "proofSiblings", + "type": "uint256[]" + }, + { + "internalType": "uint8[]", + "name": "proofPathIndices", + "type": "uint8[]" + } + ], + "name": "updateMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "verifier", + "outputs": [ + { + "internalType": "contract ISemaphoreVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "signal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nullifierHash", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "externalNullifier", + "type": "uint256" + }, + { + "internalType": "uint256[8]", + "name": "proof", + "type": "uint256[8]" + } + ], + "name": "verifyProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b506040516200214a3803806200214a833981810160405281019062000037919062000096565b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505062000124565b60008151905062000090816200010a565b92915050565b600060208284031215620000a957600080fd5b6000620000b9848285016200007f565b91505092915050565b6000620000cf82620000ea565b9050919050565b6000620000e382620000c2565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6200011581620000d6565b81146200012157600080fd5b50565b61201680620001346000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636389e1071161008c5780639c112141116100665780639c11214114610246578063dabc4d5114610262578063ec45622a14610292578063fcf0b6ec146102ae576100ea565b80636389e107146101c957806365e54f83146101f957806396324bd414610215576100ea565b80633bc778e3116100c85780633bc778e31461014557806343989f8514610161578063568ee8261461017d578063638480be14610199576100ea565b806304245371146100ef5780631783efc31461010b5780632b7ac3f314610127575b600080fd5b61010960048036038101906101049190611655565b6102ca565b005b610125600480360381019061012091906116ad565b610410565b005b61012f6104fc565b60405161013c9190611b24565b60405180910390f35b61015f600480360381019061015a91906118e6565b610522565b005b61017b6004803603810190610176919061179b565b6107cf565b005b61019760048036038101906101929190611619565b6108c3565b005b6101b360048036038101906101ae91906115f0565b610a22565b6040516101c09190611c5b565b60405180910390f35b6101e360048036038101906101de91906115f0565b610a41565b6040516101f09190611c5b565b60405180910390f35b610213600480360381019061020e9190611738565b610a60565b005b61022f600480360381019061022a91906115f0565b610b84565b60405161023d929190611afb565b60405180910390f35b610260600480360381019061025b91906116e9565b610bc8565b005b61027c600480360381019061027791906115f0565b610ced565b6040516102899190611c5b565b60405180910390f35b6102ac60048036038101906102a79190611836565b610d0c565b005b6102c860048036038101906102c391906116ad565b610e02565b005b826102d3610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461036d576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838390508110156103cf576103c4858585848181106103b8577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020135610f23565b806001019050610370565b5060006103db85610ced565b905042600260008781526020019081526020016000206002016000838152602001908152602001600020819055505050505050565b81610419610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146104b3576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104bd8383610f23565b60006104c884610ced565b9050426002600086815260200190815260200160002060020160008381526020019081526020016000208190555050505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600061052d87610a41565b9050600081141561056a576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061057588610ced565b9050808714610648576000600260008a815260200190815260200160002060020160008981526020019081526020016000205490506000600260008b81526020019081526020016000206001015490506000821415610600576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808261060c9190611dce565b421115610645576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b60026000898152602001908152602001600020600301600086815260200190815260200160002060009054906101000a900460ff16156106b4576040517f948d067400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639aca8f2a8887898888886040518763ffffffff1660e01b815260040161071996959493929190611d1b565b60006040518083038186803b15801561073157600080fd5b505afa158015610745573d6000803e3d6000fd5b505050506001600260008a8152602001908152602001600020600301600087815260200190815260200160002060006101000a81548160ff0219169083151502179055508387897f48950129900df26c2140187532df49c8af343c3daf74f1e99e562e1b2be07adc888a6040516107bd929190611c76565b60405180910390a45050505050505050565b856107d8610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610872576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61088087878787878761104a565b600061088b88610ced565b905042600260008a81526020019081526020016000206002016000838152602001908152602001600020819055505050505050505050565b816108cc610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610966576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816002600085815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff166109da610f1b565b73ffffffffffffffffffffffffffffffffffffffff16847f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a4505050565b6000806000838152602001908152602001600020600201549050919050565b6000806000838152602001908152602001600020600001549050919050565b826010811080610a705750602081115b15610aa7576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ab18585611172565b826002600087815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000878152602001908152602001600020600101819055508273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16867f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050505050565b60026020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010154905082565b816010811080610bd85750602081115b15610c0f576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c198484611172565b816002600086815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610e1060026000868152602001908152602001600020600101819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b6000806000838152602001908152602001600020600101549050919050565b86610d15610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610daf576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dbe888888888888886112a3565b6000610dc989610ced565b905042600260008b8152602001908152602001600020600201600083815260200190815260200160002081905550505050505050505050565b81610e0b610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610ea5576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060026000858152602001908152602001600020600101549050826002600086815260200190815260200160002060010181905550837f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58285604051610f0d929190611c76565b60405180910390a250505050565b600033905090565b6000610f2e83610a41565b1415610f66576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008083815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63168703fa9091836040518363ffffffff1660e01b8152600401610fb2929190611b3f565b60006040518083038186803b158015610fca57600080fd5b505af4158015610fde573d6000803e3d6000fd5b505050506000610fed83610ced565b905060006001610ffc85610a22565b6110069190611e7e565b9050837f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde982858560405161103c93929190611c9f565b60405180910390a250505050565b600061105587610a41565b141561108d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008087815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__630629596f909187878787876040518763ffffffff1660e01b81526004016110e196959493929190611b68565b60006040518083038186803b1580156110f957600080fd5b505af415801561110d573d6000803e3d6000fd5b50505050600061111c87610ced565b9050600061112a84846113d0565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87182898560405161116093929190611c9f565b60405180910390a25050505050505050565b600061117d83610a41565b146111b4576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006008836040516020016111c99190611ae0565b6040516020818303038152906040528051906020012060001c901c905060008084815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__631095fbb4909184846040518463ffffffff1660e01b815260040161123493929190611bbf565b60006040518083038186803b15801561124c57600080fd5b505af4158015611260573d6000803e3d6000fd5b50505050827f0d000126c26c1bbe400fd2332187f75d58b69306f9ec47b408686189d3a008338383604051611296929190611c76565b60405180910390a2505050565b60006112ae88610a41565b14156112e6576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008088815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63a547882790918888888888886040518863ffffffff1660e01b815260040161133c9796959493929190611bf6565b60006040518083038186803b15801561135457600080fd5b505af4158015611368573d6000803e3d6000fd5b50505050600061137788610ced565b9050600061138584846113d0565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1828a8a866040516113bd9493929190611cd6565b60405180910390a2505050505050505050565b6000806000905060008484905090505b60008160ff1611156114f457600082118061145b5750600085856001846114079190611eb2565b60ff16818110611440577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114559190611970565b60ff1614155b156114e85760028261146d9190611e24565b9150600185856001846114809190611eb2565b60ff168181106114b9577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114ce9190611970565b60ff1614156114e7576001826114e49190611dce565b91505b5b806001900390506113e0565b508091505092915050565b60008135905061150e81611f9b565b92915050565b60008190508260206008028201111561152c57600080fd5b92915050565b60008083601f84011261154457600080fd5b8235905067ffffffffffffffff81111561155d57600080fd5b60208301915083602082028301111561157557600080fd5b9250929050565b60008083601f84011261158e57600080fd5b8235905067ffffffffffffffff8111156115a757600080fd5b6020830191508360208202830111156115bf57600080fd5b9250929050565b6000813590506115d581611fb2565b92915050565b6000813590506115ea81611fc9565b92915050565b60006020828403121561160257600080fd5b6000611610848285016115c6565b91505092915050565b6000806040838503121561162c57600080fd5b600061163a858286016115c6565b925050602061164b858286016114ff565b9150509250929050565b60008060006040848603121561166a57600080fd5b6000611678868287016115c6565b935050602084013567ffffffffffffffff81111561169557600080fd5b6116a186828701611532565b92509250509250925092565b600080604083850312156116c057600080fd5b60006116ce858286016115c6565b92505060206116df858286016115c6565b9150509250929050565b6000806000606084860312156116fe57600080fd5b600061170c868287016115c6565b935050602061171d868287016115c6565b925050604061172e868287016114ff565b9150509250925092565b6000806000806080858703121561174e57600080fd5b600061175c878288016115c6565b945050602061176d878288016115c6565b935050604061177e878288016114ff565b925050606061178f878288016115c6565b91505092959194509250565b600080600080600080608087890312156117b457600080fd5b60006117c289828a016115c6565b96505060206117d389828a016115c6565b955050604087013567ffffffffffffffff8111156117f057600080fd5b6117fc89828a01611532565b9450945050606087013567ffffffffffffffff81111561181b57600080fd5b61182789828a0161157c565b92509250509295509295509295565b600080600080600080600060a0888a03121561185157600080fd5b600061185f8a828b016115c6565b97505060206118708a828b016115c6565b96505060406118818a828b016115c6565b955050606088013567ffffffffffffffff81111561189e57600080fd5b6118aa8a828b01611532565b9450945050608088013567ffffffffffffffff8111156118c957600080fd5b6118d58a828b0161157c565b925092505092959891949750929550565b6000806000806000806101a0878903121561190057600080fd5b600061190e89828a016115c6565b965050602061191f89828a016115c6565b955050604061193089828a016115c6565b945050606061194189828a016115c6565b935050608061195289828a016115c6565b92505060a061196389828a01611514565b9150509295509295509295565b60006020828403121561198257600080fd5b6000611990848285016115db565b91505092915050565b60006119a58383611ad1565b60208301905092915050565b6119ba81611ee6565b82525050565b6119cd6101008383611f53565b5050565b60006119dd8385611d95565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115611a0c57600080fd5b602083029250611a1d838584611f53565b82840190509392505050565b6000611a358385611da6565b9350611a4082611d7e565b8060005b85811015611a7957611a568284611db7565b611a608882611999565b9750611a6b83611d88565b925050600181019050611a44565b5085925050509392505050565b611a8f81611f2f565b82525050565b8082525050565b611aa581611f18565b82525050565b611ab481611f18565b82525050565b611acb611ac682611f18565b611f62565b82525050565b611ada81611f22565b82525050565b6000611aec8284611aba565b60208201915081905092915050565b6000604082019050611b1060008301856119b1565b611b1d6020830184611a9c565b9392505050565b6000602082019050611b396000830184611a86565b92915050565b6000604082019050611b546000830185611a95565b611b616020830184611aab565b9392505050565b6000608082019050611b7d6000830189611a95565b611b8a6020830188611aab565b8181036040830152611b9d8186886119d1565b90508181036060830152611bb2818486611a29565b9050979650505050505050565b6000606082019050611bd46000830186611a95565b611be16020830185611aab565b611bee6040830184611aab565b949350505050565b600060a082019050611c0b600083018a611a95565b611c186020830189611aab565b611c256040830188611aab565b8181036060830152611c388186886119d1565b90508181036080830152611c4d818486611a29565b905098975050505050505050565b6000602082019050611c706000830184611a9c565b92915050565b6000604082019050611c8b6000830185611a9c565b611c986020830184611a9c565b9392505050565b6000606082019050611cb46000830186611a9c565b611cc16020830185611a9c565b611cce6040830184611a9c565b949350505050565b6000608082019050611ceb6000830187611a9c565b611cf86020830186611a9c565b611d056040830185611a9c565b611d126060830184611a9c565b95945050505050565b60006101a082019050611d316000830189611a9c565b611d3e6020830188611a9c565b611d4b6040830187611a9c565b611d586060830186611a9c565b611d6560808301856119c0565b611d73610180830184611a9c565b979650505050505050565b6000819050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b6000611dc660208401846115db565b905092915050565b6000611dd982611f18565b9150611de483611f18565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611e1957611e18611f6c565b5b828201905092915050565b6000611e2f82611f18565b9150611e3a83611f18565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7357611e72611f6c565b5b828202905092915050565b6000611e8982611f18565b9150611e9483611f18565b925082821015611ea757611ea6611f6c565b5b828203905092915050565b6000611ebd82611f22565b9150611ec883611f22565b925082821015611edb57611eda611f6c565b5b828203905092915050565b6000611ef182611ef8565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b6000611f3a82611f41565b9050919050565b6000611f4c82611ef8565b9050919050565b82818337600083830152505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b611fa481611ee6565b8114611faf57600080fd5b50565b611fbb81611f18565b8114611fc657600080fd5b50565b611fd281611f22565b8114611fdd57600080fd5b5056fea2646970667358221220a6777edc645a16d8f81beacbb5ec133f19452c578127e38e7f40d5540508d08464736f6c63430008040033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636389e1071161008c5780639c112141116100665780639c11214114610246578063dabc4d5114610262578063ec45622a14610292578063fcf0b6ec146102ae576100ea565b80636389e107146101c957806365e54f83146101f957806396324bd414610215576100ea565b80633bc778e3116100c85780633bc778e31461014557806343989f8514610161578063568ee8261461017d578063638480be14610199576100ea565b806304245371146100ef5780631783efc31461010b5780632b7ac3f314610127575b600080fd5b61010960048036038101906101049190611655565b6102ca565b005b610125600480360381019061012091906116ad565b610410565b005b61012f6104fc565b60405161013c9190611b24565b60405180910390f35b61015f600480360381019061015a91906118e6565b610522565b005b61017b6004803603810190610176919061179b565b6107cf565b005b61019760048036038101906101929190611619565b6108c3565b005b6101b360048036038101906101ae91906115f0565b610a22565b6040516101c09190611c5b565b60405180910390f35b6101e360048036038101906101de91906115f0565b610a41565b6040516101f09190611c5b565b60405180910390f35b610213600480360381019061020e9190611738565b610a60565b005b61022f600480360381019061022a91906115f0565b610b84565b60405161023d929190611afb565b60405180910390f35b610260600480360381019061025b91906116e9565b610bc8565b005b61027c600480360381019061027791906115f0565b610ced565b6040516102899190611c5b565b60405180910390f35b6102ac60048036038101906102a79190611836565b610d0c565b005b6102c860048036038101906102c391906116ad565b610e02565b005b826102d3610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461036d576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838390508110156103cf576103c4858585848181106103b8577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020135610f23565b806001019050610370565b5060006103db85610ced565b905042600260008781526020019081526020016000206002016000838152602001908152602001600020819055505050505050565b81610419610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146104b3576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104bd8383610f23565b60006104c884610ced565b9050426002600086815260200190815260200160002060020160008381526020019081526020016000208190555050505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600061052d87610a41565b9050600081141561056a576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061057588610ced565b9050808714610648576000600260008a815260200190815260200160002060020160008981526020019081526020016000205490506000600260008b81526020019081526020016000206001015490506000821415610600576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808261060c9190611dce565b421115610645576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b60026000898152602001908152602001600020600301600086815260200190815260200160002060009054906101000a900460ff16156106b4576040517f948d067400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639aca8f2a8887898888886040518763ffffffff1660e01b815260040161071996959493929190611d1b565b60006040518083038186803b15801561073157600080fd5b505afa158015610745573d6000803e3d6000fd5b505050506001600260008a8152602001908152602001600020600301600087815260200190815260200160002060006101000a81548160ff0219169083151502179055508387897f48950129900df26c2140187532df49c8af343c3daf74f1e99e562e1b2be07adc888a6040516107bd929190611c76565b60405180910390a45050505050505050565b856107d8610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610872576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61088087878787878761104a565b600061088b88610ced565b905042600260008a81526020019081526020016000206002016000838152602001908152602001600020819055505050505050505050565b816108cc610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610966576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816002600085815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff166109da610f1b565b73ffffffffffffffffffffffffffffffffffffffff16847f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a4505050565b6000806000838152602001908152602001600020600201549050919050565b6000806000838152602001908152602001600020600001549050919050565b826010811080610a705750602081115b15610aa7576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ab18585611172565b826002600087815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000878152602001908152602001600020600101819055508273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16867f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050505050565b60026020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010154905082565b816010811080610bd85750602081115b15610c0f576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c198484611172565b816002600086815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610e1060026000868152602001908152602001600020600101819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b6000806000838152602001908152602001600020600101549050919050565b86610d15610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610daf576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dbe888888888888886112a3565b6000610dc989610ced565b905042600260008b8152602001908152602001600020600201600083815260200190815260200160002081905550505050505050505050565b81610e0b610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610ea5576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060026000858152602001908152602001600020600101549050826002600086815260200190815260200160002060010181905550837f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58285604051610f0d929190611c76565b60405180910390a250505050565b600033905090565b6000610f2e83610a41565b1415610f66576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008083815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63168703fa9091836040518363ffffffff1660e01b8152600401610fb2929190611b3f565b60006040518083038186803b158015610fca57600080fd5b505af4158015610fde573d6000803e3d6000fd5b505050506000610fed83610ced565b905060006001610ffc85610a22565b6110069190611e7e565b9050837f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde982858560405161103c93929190611c9f565b60405180910390a250505050565b600061105587610a41565b141561108d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008087815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__630629596f909187878787876040518763ffffffff1660e01b81526004016110e196959493929190611b68565b60006040518083038186803b1580156110f957600080fd5b505af415801561110d573d6000803e3d6000fd5b50505050600061111c87610ced565b9050600061112a84846113d0565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87182898560405161116093929190611c9f565b60405180910390a25050505050505050565b600061117d83610a41565b146111b4576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006008836040516020016111c99190611ae0565b6040516020818303038152906040528051906020012060001c901c905060008084815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__631095fbb4909184846040518463ffffffff1660e01b815260040161123493929190611bbf565b60006040518083038186803b15801561124c57600080fd5b505af4158015611260573d6000803e3d6000fd5b50505050827f0d000126c26c1bbe400fd2332187f75d58b69306f9ec47b408686189d3a008338383604051611296929190611c76565b60405180910390a2505050565b60006112ae88610a41565b14156112e6576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008088815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63a547882790918888888888886040518863ffffffff1660e01b815260040161133c9796959493929190611bf6565b60006040518083038186803b15801561135457600080fd5b505af4158015611368573d6000803e3d6000fd5b50505050600061137788610ced565b9050600061138584846113d0565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1828a8a866040516113bd9493929190611cd6565b60405180910390a2505050505050505050565b6000806000905060008484905090505b60008160ff1611156114f457600082118061145b5750600085856001846114079190611eb2565b60ff16818110611440577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114559190611970565b60ff1614155b156114e85760028261146d9190611e24565b9150600185856001846114809190611eb2565b60ff168181106114b9577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114ce9190611970565b60ff1614156114e7576001826114e49190611dce565b91505b5b806001900390506113e0565b508091505092915050565b60008135905061150e81611f9b565b92915050565b60008190508260206008028201111561152c57600080fd5b92915050565b60008083601f84011261154457600080fd5b8235905067ffffffffffffffff81111561155d57600080fd5b60208301915083602082028301111561157557600080fd5b9250929050565b60008083601f84011261158e57600080fd5b8235905067ffffffffffffffff8111156115a757600080fd5b6020830191508360208202830111156115bf57600080fd5b9250929050565b6000813590506115d581611fb2565b92915050565b6000813590506115ea81611fc9565b92915050565b60006020828403121561160257600080fd5b6000611610848285016115c6565b91505092915050565b6000806040838503121561162c57600080fd5b600061163a858286016115c6565b925050602061164b858286016114ff565b9150509250929050565b60008060006040848603121561166a57600080fd5b6000611678868287016115c6565b935050602084013567ffffffffffffffff81111561169557600080fd5b6116a186828701611532565b92509250509250925092565b600080604083850312156116c057600080fd5b60006116ce858286016115c6565b92505060206116df858286016115c6565b9150509250929050565b6000806000606084860312156116fe57600080fd5b600061170c868287016115c6565b935050602061171d868287016115c6565b925050604061172e868287016114ff565b9150509250925092565b6000806000806080858703121561174e57600080fd5b600061175c878288016115c6565b945050602061176d878288016115c6565b935050604061177e878288016114ff565b925050606061178f878288016115c6565b91505092959194509250565b600080600080600080608087890312156117b457600080fd5b60006117c289828a016115c6565b96505060206117d389828a016115c6565b955050604087013567ffffffffffffffff8111156117f057600080fd5b6117fc89828a01611532565b9450945050606087013567ffffffffffffffff81111561181b57600080fd5b61182789828a0161157c565b92509250509295509295509295565b600080600080600080600060a0888a03121561185157600080fd5b600061185f8a828b016115c6565b97505060206118708a828b016115c6565b96505060406118818a828b016115c6565b955050606088013567ffffffffffffffff81111561189e57600080fd5b6118aa8a828b01611532565b9450945050608088013567ffffffffffffffff8111156118c957600080fd5b6118d58a828b0161157c565b925092505092959891949750929550565b6000806000806000806101a0878903121561190057600080fd5b600061190e89828a016115c6565b965050602061191f89828a016115c6565b955050604061193089828a016115c6565b945050606061194189828a016115c6565b935050608061195289828a016115c6565b92505060a061196389828a01611514565b9150509295509295509295565b60006020828403121561198257600080fd5b6000611990848285016115db565b91505092915050565b60006119a58383611ad1565b60208301905092915050565b6119ba81611ee6565b82525050565b6119cd6101008383611f53565b5050565b60006119dd8385611d95565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115611a0c57600080fd5b602083029250611a1d838584611f53565b82840190509392505050565b6000611a358385611da6565b9350611a4082611d7e565b8060005b85811015611a7957611a568284611db7565b611a608882611999565b9750611a6b83611d88565b925050600181019050611a44565b5085925050509392505050565b611a8f81611f2f565b82525050565b8082525050565b611aa581611f18565b82525050565b611ab481611f18565b82525050565b611acb611ac682611f18565b611f62565b82525050565b611ada81611f22565b82525050565b6000611aec8284611aba565b60208201915081905092915050565b6000604082019050611b1060008301856119b1565b611b1d6020830184611a9c565b9392505050565b6000602082019050611b396000830184611a86565b92915050565b6000604082019050611b546000830185611a95565b611b616020830184611aab565b9392505050565b6000608082019050611b7d6000830189611a95565b611b8a6020830188611aab565b8181036040830152611b9d8186886119d1565b90508181036060830152611bb2818486611a29565b9050979650505050505050565b6000606082019050611bd46000830186611a95565b611be16020830185611aab565b611bee6040830184611aab565b949350505050565b600060a082019050611c0b600083018a611a95565b611c186020830189611aab565b611c256040830188611aab565b8181036060830152611c388186886119d1565b90508181036080830152611c4d818486611a29565b905098975050505050505050565b6000602082019050611c706000830184611a9c565b92915050565b6000604082019050611c8b6000830185611a9c565b611c986020830184611a9c565b9392505050565b6000606082019050611cb46000830186611a9c565b611cc16020830185611a9c565b611cce6040830184611a9c565b949350505050565b6000608082019050611ceb6000830187611a9c565b611cf86020830186611a9c565b611d056040830185611a9c565b611d126060830184611a9c565b95945050505050565b60006101a082019050611d316000830189611a9c565b611d3e6020830188611a9c565b611d4b6040830187611a9c565b611d586060830186611a9c565b611d6560808301856119c0565b611d73610180830184611a9c565b979650505050505050565b6000819050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b6000611dc660208401846115db565b905092915050565b6000611dd982611f18565b9150611de483611f18565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611e1957611e18611f6c565b5b828201905092915050565b6000611e2f82611f18565b9150611e3a83611f18565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7357611e72611f6c565b5b828202905092915050565b6000611e8982611f18565b9150611e9483611f18565b925082821015611ea757611ea6611f6c565b5b828203905092915050565b6000611ebd82611f22565b9150611ec883611f22565b925082821015611edb57611eda611f6c565b5b828203905092915050565b6000611ef182611ef8565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b6000611f3a82611f41565b9050919050565b6000611f4c82611ef8565b9050919050565b82818337600083830152505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b611fa481611ee6565b8114611faf57600080fd5b50565b611fbb81611f18565b8114611fc657600080fd5b50565b611fd281611f22565b8114611fdd57600080fd5b5056fea2646970667358221220a6777edc645a16d8f81beacbb5ec133f19452c578127e38e7f40d5540508d08464736f6c63430008040033", + "linkReferences": { + "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol": { + "IncrementalBinaryTree": [ + { + "length": 20, + "start": 4270 + }, + { + "length": 20, + "start": 4565 + }, + { + "length": 20, + "start": 4910 + }, + { + "length": 20, + "start": 5166 + } + ] + } + }, + "deployedLinkReferences": { + "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol": { + "IncrementalBinaryTree": [ + { + "length": 20, + "start": 3962 + }, + { + "length": 20, + "start": 4257 + }, + { + "length": 20, + "start": 4602 + }, + { + "length": 20, + "start": 4858 + } + ] + } + } +} diff --git a/apps/subgraph/docker-compose-graph.yml b/apps/subgraph/docker-compose-graph.yml new file mode 100644 index 00000000..332efbc5 --- /dev/null +++ b/apps/subgraph/docker-compose-graph.yml @@ -0,0 +1,60 @@ +version: "3.9" + +services: + graph-node: + image: graphprotocol/graph-node + ports: + - "8000:8000" + - "8001:8001" + - "8020:8020" + - "8030:8030" + - "8040:8040" + depends_on: + - ipfs + - postgres + environment: + postgres_host: postgres + postgres_user: graph-node + postgres_pass: let-me-in + postgres_db: graph-node + postgres_port: 5432 + ipfs: "ipfs:5001" + ethereum: "localhost:http://host.docker.internal:8545" # Should use the `localhost` as network name in subgraph.yml + # ethereum: 'goerli:https://goerli.infura.io/v3/YOUR-API-KEY' + GRAPH_LOG: info + GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1 + networks: + - the-graph + + ipfs: + image: ipfs/go-ipfs:v0.4.23 + ports: + - "5001:5001" + volumes: + - graph-ipfs:/data/ipfs + networks: + - the-graph + + postgres: + image: postgres + ports: + - "5433:5432" + command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"] + environment: + POSTGRES_USER: graph-node + POSTGRES_PASSWORD: let-me-in + POSTGRES_DB: graph-node + POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" + networks: + - the-graph + volumes: + - graph-postgres:/var/lib/postgresql/data + +networks: + the-graph: + internal: false + driver: bridge + +volumes: + graph-postgres: + graph-ipfs: diff --git a/apps/subgraph/matchstick.yaml b/apps/subgraph/matchstick.yaml new file mode 100644 index 00000000..8851578c --- /dev/null +++ b/apps/subgraph/matchstick.yaml @@ -0,0 +1 @@ +libsFolder: ../../node_modules diff --git a/apps/subgraph/networks.json b/apps/subgraph/networks.json new file mode 100644 index 00000000..2066652d --- /dev/null +++ b/apps/subgraph/networks.json @@ -0,0 +1,44 @@ +{ + "sepolia": { + "Semaphore": { + "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", + "startBlock": 3231111 + } + }, + "goerli": { + "Semaphore": { + "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", + "startBlock": 8777695 + } + }, + "mumbai": { + "Semaphore": { + "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", + "startBlock": 33995010 + } + }, + "optimism-goerli": { + "Semaphore": { + "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", + "startBlock": 7632846 + } + }, + "arbitrum-goerli": { + "Semaphore": { + "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", + "startBlock": 15174410 + } + }, + "arbitrum-one": { + "Semaphore": { + "address": "0xc60E0Ee1a2770d5F619858C641f14FC4a6401520", + "startBlock": 77278430 + } + }, + "localhost": { + "Semaphore": { + "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + "startBlock": 0 + } + } +} diff --git a/apps/subgraph/package.json b/apps/subgraph/package.json new file mode 100644 index 00000000..1cff22ed --- /dev/null +++ b/apps/subgraph/package.json @@ -0,0 +1,26 @@ +{ + "name": "semaphore-subgraph", + "type": "module", + "description": "Semaphore subgraph definition (The Graph).", + "license": "MIT", + "private": true, + "scripts": { + "codegen": "node scripts/generateSubgraph.js ${0} && graph codegen", + "build": "graph build", + "auth": "graph auth --studio", + "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ ${0}", + "create-local": "graph create --node http://localhost:8020/ semaphore", + "remove-local": "graph remove --node http://localhost:8020/ semaphore", + "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 semaphore", + "test": "graph test Semaphore -v 0.5.0" + }, + "dependencies": { + "@graphprotocol/graph-cli": "0.56.0", + "@graphprotocol/graph-ts": "^0.31.0" + }, + "devDependencies": { + "@types/mustache": "^4.2.2", + "matchstick-as": "0.5.0", + "mustache": "^4.2.0" + } +} diff --git a/apps/subgraph/schema.graphql b/apps/subgraph/schema.graphql new file mode 100644 index 00000000..f8d238b2 --- /dev/null +++ b/apps/subgraph/schema.graphql @@ -0,0 +1,35 @@ +type MerkleTree @entity { + id: ID! + depth: BigInt! + root: BigInt + zeroValue: BigInt! + numberOfLeaves: Int! + group: Group! +} + +type Group @entity { + id: ID! + merkleTree: MerkleTree! + timestamp: BigInt! + admin: Bytes + members: [Member!] @derivedFrom(field: "group") + verifiedProofs: [VerifiedProof!] @derivedFrom(field: "group") +} + +type Member @entity { + id: ID! + identityCommitment: BigInt! + timestamp: BigInt! + index: Int! + group: Group! +} + +type VerifiedProof @entity { + id: ID! + signal: BigInt! + merkleTreeRoot: BigInt! + nullifierHash: BigInt! + externalNullifier: BigInt! + timestamp: BigInt! + group: Group! +} diff --git a/apps/subgraph/scripts/generateSubgraph.js b/apps/subgraph/scripts/generateSubgraph.js new file mode 100644 index 00000000..8b867969 --- /dev/null +++ b/apps/subgraph/scripts/generateSubgraph.js @@ -0,0 +1,10 @@ +import Mustache from "mustache" +import { readFileSync, writeFileSync } from "fs" + +const network = process.argv.at(2) + +const template = readFileSync("./subgraph.template.yaml", "utf-8") +const networks = JSON.parse(readFileSync("./networks.json", "utf-8")) +const subgraph = Mustache.render(template, { network, ...networks[network].Semaphore }) + +writeFileSync("./subgraph.yaml", subgraph) diff --git a/apps/subgraph/src/semaphore.ts b/apps/subgraph/src/semaphore.ts new file mode 100644 index 00000000..1bc56436 --- /dev/null +++ b/apps/subgraph/src/semaphore.ts @@ -0,0 +1,191 @@ +import { ByteArray, log } from "@graphprotocol/graph-ts" +import { + GroupAdminUpdated, + GroupCreated, + MemberAdded, + MemberRemoved, + MemberUpdated, + ProofVerified +} from "../generated/Semaphore/Semaphore" +import { Member, Group, VerifiedProof, MerkleTree } from "../generated/schema" +import { concat, hash } from "./utils" + +/** + * Creates a new group. + * @param event Ethereum event emitted when a group is created. + */ +export function createGroup(event: GroupCreated): void { + log.debug(`GroupCreated event block: {}`, [event.block.number.toString()]) + + const group = new Group(event.params.groupId.toString()) + const merkleTree = new MerkleTree(event.params.groupId.toString()) + + log.info("Creating group '{}'", [group.id]) + + merkleTree.depth = event.params.merkleTreeDepth + merkleTree.zeroValue = event.params.zeroValue + merkleTree.numberOfLeaves = 0 + merkleTree.group = group.id + + group.timestamp = event.block.timestamp + group.merkleTree = merkleTree.id + + merkleTree.save() + group.save() + + log.info("Group '{}' has been created", [group.id]) +} + +/** + * Updates the admin of a group. + * @param event Ethereum event emitted when a group admin is updated. + */ +export function updateGroupAdmin(event: GroupAdminUpdated): void { + log.debug(`GroupAdminUpdated event block: {}`, [event.block.number.toString()]) + + const group = Group.load(event.params.groupId.toString()) + + if (group) { + log.info("Updating admin '{}' in the group '{}'", [event.params.newAdmin.toString(), group.id]) + + group.admin = event.params.newAdmin + + group.save() + + log.info("Admin '{}' of the group '{}' has been updated ", [group.admin!.toString(), group.id]) + } +} + +/** + * Adds a member to a group. + * @param event Ethereum event emitted when a member is added to a group. + */ +export function addMember(event: MemberAdded): void { + log.debug(`MemberAdded event block {}`, [event.block.number.toString()]) + + const merkleTree = MerkleTree.load(event.params.groupId.toString()) + + if (merkleTree) { + const memberId = hash( + concat(ByteArray.fromBigInt(event.params.index), ByteArray.fromBigInt(event.params.groupId)) + ) + const member = new Member(memberId) + + log.info("Adding member '{}' in the onchain group '{}'", [member.id, merkleTree.group]) + + member.group = merkleTree.group + member.identityCommitment = event.params.identityCommitment + member.timestamp = event.block.timestamp + member.index = merkleTree.numberOfLeaves + + member.save() + + merkleTree.root = event.params.merkleTreeRoot + merkleTree.numberOfLeaves += 1 + + merkleTree.save() + + log.info("Member '{}' of the onchain group '{}' has been added", [member.id, merkleTree.id]) + } +} + +/** + * Updates a member in a group. + * @param event Ethereum event emitted when a member is removed from a group. + */ +export function updateMember(event: MemberUpdated): void { + log.debug(`MemberUpdated event block {}`, [event.block.number.toString()]) + + const merkleTree = MerkleTree.load(event.params.groupId.toString()) + + if (merkleTree) { + const memberId = hash( + concat(ByteArray.fromBigInt(event.params.index), ByteArray.fromBigInt(event.params.groupId)) + ) + const member = Member.load(memberId) + + if (member) { + log.info("Updating member '{}' from the onchain group '{}'", [member.id, merkleTree.group]) + + member.identityCommitment = event.params.newIdentityCommitment + + member.save() + + merkleTree.root = event.params.merkleTreeRoot + + merkleTree.save() + + log.info("Member '{}' of the onchain group '{}' has been removed", [member.id, merkleTree.group]) + } + } +} + +/** + * Removes a member from a group. + * @param event Ethereum event emitted when a member is removed from a group. + */ +export function removeMember(event: MemberRemoved): void { + log.debug(`MemberRemoved event block {}`, [event.block.number.toString()]) + + const merkleTree = MerkleTree.load(event.params.groupId.toString()) + + if (merkleTree) { + const memberId = hash( + concat(ByteArray.fromBigInt(event.params.index), ByteArray.fromBigInt(event.params.groupId)) + ) + const member = Member.load(memberId) + + if (member) { + log.info("Removing member '{}' from the onchain group '{}'", [member.id, merkleTree.group]) + + member.identityCommitment = merkleTree.zeroValue + + member.save() + + merkleTree.root = event.params.merkleTreeRoot + + merkleTree.save() + + log.info("Member '{}' of the onchain group '{}' has been removed", [member.id, merkleTree.group]) + } + } +} + +/** + * Adds a verified proof in a group. + * @param event Ethereum event emitted when a proof has been verified. + */ +export function addVerifiedProof(event: ProofVerified): void { + log.debug(`ProofVerified event block {}`, [event.block.number.toString()]) + + const group = Group.load(event.params.groupId.toString()) + + if (group) { + const verifiedProofId = hash( + concat(ByteArray.fromBigInt(event.params.nullifierHash), ByteArray.fromBigInt(event.params.groupId)) + ) + + const verifiedProof = new VerifiedProof(verifiedProofId) + + log.info("Adding verified proof with signal '{}' in the onchain group '{}'", [ + event.params.signal.toHexString(), + group.id + ]) + + verifiedProof.group = group.id + verifiedProof.signal = event.params.signal + verifiedProof.merkleTreeRoot = event.params.merkleTreeRoot + verifiedProof.externalNullifier = event.params.externalNullifier + verifiedProof.nullifierHash = event.params.nullifierHash + verifiedProof.timestamp = event.block.timestamp + + verifiedProof.save() + + group.save() + + log.info("Verified proof with signal '{}' in the onchain group '{}' has been added", [ + event.params.signal.toHexString(), + group.id + ]) + } +} diff --git a/apps/subgraph/src/utils.ts b/apps/subgraph/src/utils.ts new file mode 100644 index 00000000..0c3549fd --- /dev/null +++ b/apps/subgraph/src/utils.ts @@ -0,0 +1,25 @@ +import { ByteArray, crypto } from "@graphprotocol/graph-ts" + +/** + * Concatenates two byte arrays. + * @param a First byte array. + * @param b Second byte array. + * @returns Final concatenated byte array. + */ +export function concat(a: ByteArray, b: ByteArray): ByteArray { + const c = new ByteArray(a.length + b.length) + + c.set(a) + c.set(b, a.length) + + return c +} + +/** + * Creates a Keccak256 hash. + * @param message Message to hash. + * @returns Hexadecimal string of the Keccak256 hash. + */ +export function hash(message: ByteArray): string { + return crypto.keccak256(message).toHexString() +} diff --git a/apps/subgraph/subgraph.template.yaml b/apps/subgraph/subgraph.template.yaml new file mode 100644 index 00000000..434a461a --- /dev/null +++ b/apps/subgraph/subgraph.template.yaml @@ -0,0 +1,35 @@ +specVersion: 0.0.5 +schema: + file: ./schema.graphql +dataSources: + - kind: ethereum + name: Semaphore + network: {{ network }} + source: + address: "{{ address }}" + abi: Semaphore + startBlock: {{ startBlock }} + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Group + - Member + abis: + - name: Semaphore + file: ./abis/Semaphore.json + eventHandlers: + - event: GroupCreated(indexed uint256,uint256,uint256) + handler: createGroup + - event: GroupAdminUpdated(indexed uint256,indexed address,indexed address) + handler: updateGroupAdmin + - event: MemberAdded(indexed uint256,uint256,uint256,uint256) + handler: addMember + - event: MemberUpdated(indexed uint256,uint256,uint256,uint256,uint256) + handler: updateMember + - event: MemberRemoved(indexed uint256,uint256,uint256,uint256) + handler: removeMember + - event: ProofVerified(indexed uint256,indexed uint256,uint256,indexed uint256,uint256) + handler: addVerifiedProof + file: ./src/semaphore.ts diff --git a/apps/subgraph/tests/.bin/semaphore.wasm b/apps/subgraph/tests/.bin/semaphore.wasm new file mode 100644 index 00000000..30bf3dc8 Binary files /dev/null and b/apps/subgraph/tests/.bin/semaphore.wasm differ diff --git a/apps/subgraph/tests/semaphore-utils.ts b/apps/subgraph/tests/semaphore-utils.ts new file mode 100644 index 00000000..38b963f1 --- /dev/null +++ b/apps/subgraph/tests/semaphore-utils.ts @@ -0,0 +1,160 @@ +import { newMockEvent } from "matchstick-as" +import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts" +import { + GroupAdminUpdated, + GroupCreated, + GroupMerkleTreeDurationUpdated, + MemberAdded, + MemberRemoved, + MemberUpdated, + ProofVerified +} from "../generated/Semaphore/Semaphore" + +export function createGroupCreatedEvent(groupId: BigInt, merkleTreeDepth: BigInt, zeroValue: BigInt): GroupCreated { + const groupCreatedEvent = changetype(newMockEvent()) + + groupCreatedEvent.parameters = [] + + groupCreatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) + groupCreatedEvent.parameters.push( + new ethereum.EventParam("merkleTreeDepth", ethereum.Value.fromUnsignedBigInt(merkleTreeDepth)) + ) + groupCreatedEvent.parameters.push( + new ethereum.EventParam("zeroValue", ethereum.Value.fromUnsignedBigInt(zeroValue)) + ) + + return groupCreatedEvent +} + +export function createGroupAdminUpdatedEvent(groupId: BigInt, oldAdmin: Address, newAdmin: Address): GroupAdminUpdated { + const groupAdminUpdatedEvent = changetype(newMockEvent()) + + groupAdminUpdatedEvent.parameters = [] + + groupAdminUpdatedEvent.parameters.push( + new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)) + ) + groupAdminUpdatedEvent.parameters.push(new ethereum.EventParam("oldAdmin", ethereum.Value.fromAddress(oldAdmin))) + groupAdminUpdatedEvent.parameters.push(new ethereum.EventParam("newAdmin", ethereum.Value.fromAddress(newAdmin))) + + return groupAdminUpdatedEvent +} + +export function createGroupMerkleTreeDurationUpdatedEvent( + groupId: BigInt, + oldMerkleTreeDuration: BigInt, + newMerkleTreeDuration: BigInt +): GroupMerkleTreeDurationUpdated { + const groupMerkleTreeDurationUpdatedEvent = changetype(newMockEvent()) + + groupMerkleTreeDurationUpdatedEvent.parameters = [] + + groupMerkleTreeDurationUpdatedEvent.parameters.push( + new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)) + ) + groupMerkleTreeDurationUpdatedEvent.parameters.push( + new ethereum.EventParam("oldMerkleTreeDuration", ethereum.Value.fromUnsignedBigInt(oldMerkleTreeDuration)) + ) + groupMerkleTreeDurationUpdatedEvent.parameters.push( + new ethereum.EventParam("newMerkleTreeDuration", ethereum.Value.fromUnsignedBigInt(newMerkleTreeDuration)) + ) + + return groupMerkleTreeDurationUpdatedEvent +} + +export function createMemberAddedEvent( + groupId: BigInt, + index: BigInt, + identityCommitment: BigInt, + merkleTreeRoot: BigInt +): MemberAdded { + const memberAddedEvent = changetype(newMockEvent()) + + memberAddedEvent.parameters = [] + + memberAddedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) + memberAddedEvent.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index))) + memberAddedEvent.parameters.push( + new ethereum.EventParam("identityCommitment", ethereum.Value.fromUnsignedBigInt(identityCommitment)) + ) + memberAddedEvent.parameters.push( + new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot)) + ) + + return memberAddedEvent +} + +export function createMemberRemovedEvent( + groupId: BigInt, + index: BigInt, + identityCommitment: BigInt, + merkleTreeRoot: BigInt +): MemberRemoved { + const memberRemovedEvent = changetype(newMockEvent()) + + memberRemovedEvent.parameters = [] + + memberRemovedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) + memberRemovedEvent.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index))) + memberRemovedEvent.parameters.push( + new ethereum.EventParam("identityCommitment", ethereum.Value.fromUnsignedBigInt(identityCommitment)) + ) + memberRemovedEvent.parameters.push( + new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot)) + ) + + return memberRemovedEvent +} + +export function createMemberUpdatedEvent( + groupId: BigInt, + index: BigInt, + identityCommitment: BigInt, + newIdentityCommitment: BigInt, + merkleTreeRoot: BigInt +): MemberUpdated { + const memberUpdatedEvent = changetype(newMockEvent()) + + memberUpdatedEvent.parameters = [] + + memberUpdatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) + memberUpdatedEvent.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index))) + memberUpdatedEvent.parameters.push( + new ethereum.EventParam("identityCommitment", ethereum.Value.fromUnsignedBigInt(identityCommitment)) + ) + memberUpdatedEvent.parameters.push( + new ethereum.EventParam("newIdentityCommitment", ethereum.Value.fromUnsignedBigInt(newIdentityCommitment)) + ) + memberUpdatedEvent.parameters.push( + new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot)) + ) + + return memberUpdatedEvent +} + +export function createProofVerifiedEvent( + groupId: BigInt, + merkleTreeRoot: BigInt, + externalNullifier: BigInt, + nullifierHash: BigInt, + signal: BigInt +): ProofVerified { + const proofVerifiedEvent = changetype(newMockEvent()) + + proofVerifiedEvent.parameters = [] + + proofVerifiedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) + proofVerifiedEvent.parameters.push( + new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot)) + ) + proofVerifiedEvent.parameters.push( + new ethereum.EventParam("nullifierHash", ethereum.Value.fromUnsignedBigInt(nullifierHash)) + ) + proofVerifiedEvent.parameters.push( + new ethereum.EventParam("externalNullifier", ethereum.Value.fromUnsignedBigInt(externalNullifier)) + ) + + proofVerifiedEvent.parameters.push(new ethereum.EventParam("signal", ethereum.Value.fromUnsignedBigInt(signal))) + + return proofVerifiedEvent +} diff --git a/apps/subgraph/tests/semaphore.test.ts b/apps/subgraph/tests/semaphore.test.ts new file mode 100644 index 00000000..64e6d2ef --- /dev/null +++ b/apps/subgraph/tests/semaphore.test.ts @@ -0,0 +1,156 @@ +import { Address, BigInt, ByteArray } from "@graphprotocol/graph-ts" +import { afterAll, assert, clearStore, describe, test } from "matchstick-as/assembly/index" +import { + addMember, + addVerifiedProof, + createGroup, + removeMember, + updateGroupAdmin, + updateMember +} from "../src/semaphore" +import { concat, hash } from "../src/utils" +import { + createGroupAdminUpdatedEvent, + createGroupCreatedEvent, + createMemberAddedEvent, + createMemberRemovedEvent, + createMemberUpdatedEvent, + createProofVerifiedEvent +} from "./semaphore-utils" + +// https://thegraph.com/docs/en/developer/matchstick +describe("Semaphore subgraph", () => { + afterAll(() => { + clearStore() + }) + + describe("# createGroup", () => { + test("Should have created a group", () => { + const groupId = BigInt.fromI32(234) + const merkleTreeDepth = BigInt.fromI32(20) + const zeroValue = BigInt.fromI32(0) + const oldAdmin = Address.fromString("0x0000000000000000000000000000000000000000") + const newAdmin = Address.fromString("0x0000000000000000000000000000000000000001") + + const event1 = createGroupCreatedEvent(groupId, merkleTreeDepth, zeroValue) + const event2 = createGroupAdminUpdatedEvent(groupId, oldAdmin, newAdmin) + + createGroup(event1) + updateGroupAdmin(event2) + + assert.entityCount("Group", 1) + assert.entityCount("MerkleTree", 1) + + assert.fieldEquals("Group", groupId.toString(), "admin", "0x0000000000000000000000000000000000000001") + assert.fieldEquals("Group", groupId.toString(), "merkleTree", groupId.toString()) + + assert.fieldEquals("MerkleTree", groupId.toString(), "depth", "20") + assert.fieldEquals("MerkleTree", groupId.toString(), "zeroValue", "0") + assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "0") + assert.fieldEquals("MerkleTree", groupId.toString(), "group", groupId.toString()) + }) + }) + + describe("# updateGroupAdmin", () => { + test("Should have updated a group admin", () => { + const groupId = BigInt.fromI32(234) + const oldAdmin = Address.fromString("0x0000000000000000000000000000000000000001") + const newAdmin = Address.fromString("0x0000000000000000000000000000000000000002") + + const event = createGroupAdminUpdatedEvent(groupId, oldAdmin, newAdmin) + + updateGroupAdmin(event) + + assert.fieldEquals("Group", groupId.toString(), "admin", "0x0000000000000000000000000000000000000002") + }) + }) + + describe("# addMember", () => { + test("Should have added a group member", () => { + const groupId = BigInt.fromI32(234) + const index = BigInt.fromI32(0) + const identityCommitment = BigInt.fromI32(123) + const merkleTreeRoot = BigInt.fromI32(999) + const id = hash(concat(ByteArray.fromBigInt(index), ByteArray.fromBigInt(groupId))) + + const event = createMemberAddedEvent(groupId, index, identityCommitment, merkleTreeRoot) + + addMember(event) + + assert.entityCount("Member", 1) + + assert.fieldEquals("Member", id, "index", "0") + assert.fieldEquals("Member", id, "identityCommitment", "123") + assert.fieldEquals("Member", id, "group", groupId.toString()) + + assert.fieldEquals("MerkleTree", groupId.toString(), "root", "999") + assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "1") + }) + }) + + describe("# updateMember", () => { + test("Should have added a group member", () => { + const groupId = BigInt.fromI32(234) + const index = BigInt.fromI32(0) + const identityCommitment = BigInt.fromI32(123) + const newIdentityCommitment = BigInt.fromI32(124) + const merkleTreeRoot = BigInt.fromI32(1000) + const id = hash(concat(ByteArray.fromBigInt(index), ByteArray.fromBigInt(groupId))) + + const event = createMemberUpdatedEvent( + groupId, + index, + identityCommitment, + newIdentityCommitment, + merkleTreeRoot + ) + + updateMember(event) + + assert.fieldEquals("Member", id, "identityCommitment", "124") + + assert.fieldEquals("MerkleTree", groupId.toString(), "root", "1000") + }) + }) + + describe("# removeMember", () => { + test("Should have removed a group member", () => { + const groupId = BigInt.fromI32(234) + const index = BigInt.fromI32(0) + const identityCommitment = BigInt.fromI32(123) + const merkleTreeRoot = BigInt.fromI32(1001) + const id = hash(concat(ByteArray.fromBigInt(index), ByteArray.fromBigInt(groupId))) + + const event = createMemberRemovedEvent(groupId, index, identityCommitment, merkleTreeRoot) + + removeMember(event) + + assert.fieldEquals("Member", id, "identityCommitment", "0") + + assert.fieldEquals("MerkleTree", groupId.toString(), "root", "1001") + }) + }) + + describe("# addVerifiedProof", () => { + test("Should have added a proof", () => { + const groupId = BigInt.fromI32(234) + const merkleTreeRoot = BigInt.fromI32(1001) + const externalNullifier = BigInt.fromI32(1) + const nullifierHash = BigInt.fromI32(666) + const signal = BigInt.fromI32(2) + const id = hash(concat(ByteArray.fromBigInt(nullifierHash), ByteArray.fromBigInt(groupId))) + + const event = createProofVerifiedEvent(groupId, merkleTreeRoot, externalNullifier, nullifierHash, signal) + + addVerifiedProof(event) + + assert.entityCount("VerifiedProof", 1) + + assert.fieldEquals("VerifiedProof", id, "merkleTreeRoot", "1001") + assert.fieldEquals("VerifiedProof", id, "externalNullifier", "1") + assert.fieldEquals("VerifiedProof", id, "nullifierHash", "666") + assert.fieldEquals("VerifiedProof", id, "signal", "2") + assert.fieldEquals("VerifiedProof", id, "group", groupId.toString()) + }) + }) +}) diff --git a/apps/subgraph/tsconfig.json b/apps/subgraph/tsconfig.json new file mode 100644 index 00000000..1c33f88a --- /dev/null +++ b/apps/subgraph/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", + "include": ["src", "scripts", "tests"] +} diff --git a/package.json b/package.json index 3dd2b642..7f5851ba 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,15 @@ "private": true, "scripts": { "build:libraries": "yarn workspaces foreach -t --no-private run build", - "compile:contracts": "yarn workspace contracts compile", + "build:subgraph": "yarn workspace semaphore-subgraph codegen goerli && yarn workspace semaphore-subgraph build", + "compile:contracts": "yarn workspace semaphore-contracts compile", "download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts", "remove:template-files": "ts-node scripts/remove-template-files.ts", - "test": "yarn test:libraries && yarn test:contracts", + "test": "yarn test:libraries && yarn test:contracts && yarn test:subgraph", "test:libraries": "jest --coverage", - "test:contracts": "yarn workspace contracts test:coverage", - "lint": "eslint . --ext .js,.ts,.tsx && yarn workspace contracts lint", + "test:subgraph": "yarn workspace semaphore-subgraph test", + "test:contracts": "yarn workspace semaphore-contracts test:coverage", + "lint": "eslint . --ext .js,.ts,.tsx && yarn workspace semaphore-contracts lint", "prettier": "prettier -c .", "prettier:write": "prettier -w .", "docs": "typedoc --cname js.semaphore.pse.dev --githubPages true", diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 783d8027..9eeb023c 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,5 +1,5 @@ { - "name": "contracts", + "name": "semaphore-contracts", "private": true, "scripts": { "start": "hardhat node", diff --git a/yarn.lock.REMOVED.git-id b/yarn.lock.REMOVED.git-id index 97f0b603..50a16600 100644 --- a/yarn.lock.REMOVED.git-id +++ b/yarn.lock.REMOVED.git-id @@ -1 +1 @@ -27e13e856d828fdf7ba52e07404ed16ca3a0b6f3 \ No newline at end of file +90c3d691290740e8dfd7b75dd1a43eca7d45a826 \ No newline at end of file