diff --git a/.prettierignore b/.prettierignore index 96ca527d..77f62d5e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -14,3 +14,6 @@ zkeys public/mockServiceWorker.js playwright-report/ test-results/ +packages/subgraph/templates/subgraph.template.yaml +packages/subgraph/subgraph.yaml + diff --git a/packages/contracts/contracts/interfaces/IRecipientRegistry.sol b/packages/contracts/contracts/interfaces/IRecipientRegistry.sol index c6b74988..8ad89cce 100644 --- a/packages/contracts/contracts/interfaces/IRecipientRegistry.sol +++ b/packages/contracts/contracts/interfaces/IRecipientRegistry.sol @@ -15,9 +15,9 @@ interface IRecipientRegistry { } /// @notice Events - event RecipientAdded(uint256 indexed index, bytes32 id, bytes32 indexed metadataUrl, address indexed recipient); - event RecipientRemoved(uint256 indexed index, bytes32 id, address indexed recipient); - event RecipientChanged(uint256 indexed index, bytes32 id, bytes32 indexed metadataUrl, address indexed newAddress); + event RecipientAdded(uint256 indexed index, bytes32 id, bytes32 indexed metadataUrl, address indexed payout); + event RecipientRemoved(uint256 indexed index, bytes32 id, address indexed payout); + event RecipientChanged(uint256 indexed index, bytes32 id, bytes32 indexed metadataUrl, address indexed newPayout); /// @notice Custom errors error MaxRecipientsReached(); diff --git a/packages/contracts/contracts/interfaces/IRegistryManager.sol b/packages/contracts/contracts/interfaces/IRegistryManager.sol index e4743d3c..31146f7a 100644 --- a/packages/contracts/contracts/interfaces/IRegistryManager.sol +++ b/packages/contracts/contracts/interfaces/IRegistryManager.sol @@ -38,25 +38,25 @@ interface IRegistryManager { event RequestSent( address indexed registry, RequestType indexed requestType, - address indexed recipient, + bytes32 indexed recipient, uint256 index, - bytes32 id, + address payout, bytes32 metadataUrl ); event RequestApproved( address indexed registry, RequestType indexed requestType, - address indexed recipient, + bytes32 indexed recipient, uint256 index, - bytes32 id, + address payout, bytes32 metadataUrl ); event RequestRejected( address indexed registry, RequestType indexed requestType, - address indexed recipient, + bytes32 indexed recipient, uint256 index, - bytes32 id, + address payout, bytes32 metadataUrl ); diff --git a/packages/contracts/contracts/registryManager/RegistryManager.sol b/packages/contracts/contracts/registryManager/RegistryManager.sol index cf057235..184c1328 100644 --- a/packages/contracts/contracts/registryManager/RegistryManager.sol +++ b/packages/contracts/contracts/registryManager/RegistryManager.sol @@ -66,9 +66,9 @@ contract RegistryManager is Ownable, IRegistryManager, ICommon { emit RequestSent( request.registry, request.requestType, - request.recipient.recipient, - request.index, request.recipient.id, + request.index, + request.recipient.recipient, request.recipient.metadataUrl ); } @@ -83,9 +83,9 @@ contract RegistryManager is Ownable, IRegistryManager, ICommon { emit RequestApproved( request.registry, request.requestType, - request.recipient.recipient, - request.index, request.recipient.id, + request.index, + request.recipient.recipient, request.recipient.metadataUrl ); @@ -106,9 +106,9 @@ contract RegistryManager is Ownable, IRegistryManager, ICommon { emit RequestRejected( request.registry, request.requestType, - request.recipient.recipient, - request.index, request.recipient.id, + request.index, + request.recipient.recipient, request.recipient.metadataUrl ); } diff --git a/packages/contracts/tests/EASRegistryManager.test.ts b/packages/contracts/tests/EASRegistryManager.test.ts index bf442c7b..87ebedc9 100644 --- a/packages/contracts/tests/EASRegistryManager.test.ts +++ b/packages/contracts/tests/EASRegistryManager.test.ts @@ -73,9 +73,9 @@ describe("EASRegistryManager", () => { .withArgs( addRequest.registry, addRequest.requestType, - addRequest.recipient.recipient, - addRequest.index, addRequest.recipient.id, + addRequest.index, + addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -96,9 +96,9 @@ describe("EASRegistryManager", () => { .withArgs( changeRequest.registry, changeRequest.requestType, - changeRequest.recipient.recipient, - changeRequest.index, changeRequest.recipient.id, + changeRequest.index, + changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -107,9 +107,9 @@ describe("EASRegistryManager", () => { .withArgs( changeRequest.registry, changeRequest.requestType, - changeRequest.recipient.recipient, - changeRequest.index, changeRequest.recipient.id, + changeRequest.index, + changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); diff --git a/packages/contracts/tests/RegistryManager.test.ts b/packages/contracts/tests/RegistryManager.test.ts index d171e1cb..86ff4a48 100644 --- a/packages/contracts/tests/RegistryManager.test.ts +++ b/packages/contracts/tests/RegistryManager.test.ts @@ -128,9 +128,9 @@ describe("RegistryManager", () => { .withArgs( addRequest.registry, addRequest.requestType, - addRequest.recipient.recipient, - addRequest.index, addRequest.recipient.id, + addRequest.index, + addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -175,9 +175,9 @@ describe("RegistryManager", () => { .withArgs( addRequest.registry, addRequest.requestType, - addRequest.recipient.recipient, - addRequest.index, addRequest.recipient.id, + addRequest.index, + addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -198,9 +198,9 @@ describe("RegistryManager", () => { .withArgs( changeRequest.registry, changeRequest.requestType, - changeRequest.recipient.recipient, - changeRequest.index, changeRequest.recipient.id, + changeRequest.index, + changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -209,9 +209,9 @@ describe("RegistryManager", () => { .withArgs( changeRequest.registry, changeRequest.requestType, - changeRequest.recipient.recipient, - changeRequest.index, changeRequest.recipient.id, + changeRequest.index, + changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -272,9 +272,9 @@ describe("RegistryManager", () => { .withArgs( addRequest.registry, addRequest.requestType, - addRequest.recipient.recipient, - addRequest.index, addRequest.recipient.id, + addRequest.index, + addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -283,9 +283,9 @@ describe("RegistryManager", () => { .withArgs( changeRequest.registry, changeRequest.requestType, - changeRequest.recipient.recipient, - changeRequest.index, changeRequest.recipient.id, + changeRequest.index, + changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -294,9 +294,9 @@ describe("RegistryManager", () => { .withArgs( addRequest.registry, addRequest.requestType, - addRequest.recipient.recipient, - addRequest.index, addRequest.recipient.id, + addRequest.index, + addRequest.recipient.recipient, addRequest.recipient.metadataUrl, ); @@ -305,9 +305,9 @@ describe("RegistryManager", () => { .withArgs( changeRequest.registry, changeRequest.requestType, - changeRequest.recipient.recipient, - changeRequest.index, changeRequest.recipient.id, + changeRequest.index, + changeRequest.recipient.recipient, changeRequest.recipient.metadataUrl, ); @@ -350,9 +350,9 @@ describe("RegistryManager", () => { .withArgs( removeRequest.registry, removeRequest.requestType, - removeRequest.recipient.recipient, - removeRequest.index, removeRequest.recipient.id, + removeRequest.index, + removeRequest.recipient.recipient, removeRequest.recipient.metadataUrl, ); @@ -363,9 +363,9 @@ describe("RegistryManager", () => { .withArgs( removeRequest.registry, removeRequest.requestType, - removeRequest.recipient.recipient, - removeRequest.index, removeRequest.recipient.id, + removeRequest.index, + removeRequest.recipient.recipient, removeRequest.recipient.metadataUrl, ); diff --git a/packages/subgraph/.eslintrc.js b/packages/subgraph/.eslintrc.js new file mode 100644 index 00000000..683364fc --- /dev/null +++ b/packages/subgraph/.eslintrc.js @@ -0,0 +1,44 @@ +const path = require("path"); + +module.exports = { + root: true, + extends: ["../../.eslintrc.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: path.resolve(__dirname, "./tsconfig.json"), + sourceType: "module", + typescript: true, + ecmaVersion: 2022, + experimentalDecorators: true, + requireConfigFile: false, + ecmaFeatures: { + classes: true, + impliedStrict: true, + }, + warnOnUnsupportedTypeScriptVersion: true, + }, + rules: { + "@typescript-eslint/no-shadow": [ + "error", + { + builtinGlobals: true, + allow: [ + "BigInt", + "location", + "event", + "history", + "name", + "status", + "Option", + "Request", + "test", + "expect", + "describe", + "afterEach", + "beforeEach", + "beforeAll", + ], + }, + ], + }, +}; diff --git a/packages/subgraph/.gitignore b/packages/subgraph/.gitignore new file mode 100644 index 00000000..477b9b79 --- /dev/null +++ b/packages/subgraph/.gitignore @@ -0,0 +1,9 @@ +node_modules +data +build +generated +subgraph.yaml +schema.graphql +tests/.bin/ +tests/.latest.json + diff --git a/packages/subgraph/README.md b/packages/subgraph/README.md new file mode 100644 index 00000000..af77d035 --- /dev/null +++ b/packages/subgraph/README.md @@ -0,0 +1,19 @@ +# maci-platform-subgraph + +1. Make sure you have `{network}.json` file in `config` folder, where network is a CLI name supported for subgraph network [https://thegraph.com/docs/en/developing/supported-networks/](https://thegraph.com/docs/en/developing/supported-networks/). + +2. Add network, maci contract address and maci contract deployed block. + +```json +{ + "network": "optimism-sepolia", + "maciContractAddress": "0xD18Ca45b6cC1f409380731C40551BD66932046c3", + "registryManagerContractAddress": "0xbE6e250bf8B5F689c65Cc79667589A9EBF6Fe8E3", + "registryManagerContractStartBlock": 13160483, + "maciContractStartBlock": 11052407 +} +``` + +3. Run `pnpm run build`. You can use env variables `NETWORK` and `VERSION` to switch config files. +4. Run `graph auth --studio {key}`. You can find the key in subgraph studio dashboard. +5. Run `pnpm run deploy` to deploy subgraph diff --git a/packages/subgraph/config/network.json b/packages/subgraph/config/network.json new file mode 100644 index 00000000..5874a4d3 --- /dev/null +++ b/packages/subgraph/config/network.json @@ -0,0 +1,7 @@ +{ + "network": "optimism-sepolia", + "maciContractAddress": "0xbE6e250bf8B5F689c65Cc79667589A9EBF6Fe8E3", + "registryManagerContractAddress": "0xbE6e250bf8B5F689c65Cc79667589A9EBF6Fe8E3", + "registryManagerContractStartBlock": 13160483, + "maciContractStartBlock": 13160483 +} diff --git a/packages/subgraph/docker-compose.yaml b/packages/subgraph/docker-compose.yaml new file mode 100644 index 00000000..97deaf05 --- /dev/null +++ b/packages/subgraph/docker-compose.yaml @@ -0,0 +1,40 @@ +version: "3" +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 + ipfs: "ipfs:5001" + ethereum: "localhost:http://host.docker.internal:8545" + GRAPH_LOG: info + ipfs: + image: ipfs/kubo:v0.14.0 + ports: + - "5001:5001" + volumes: + - ./data/ipfs:/data/ipfs + postgres: + image: postgres:14-alpine + ports: + - "5432:5432" + command: ["postgres", "-cshared_preload_libraries=pg_stat_statements", "-cmax_connections=200"] + environment: + POSTGRES_USER: graph-node + POSTGRES_PASSWORD: let-me-in + POSTGRES_DB: graph-node + PGDATA: "/var/lib/postgresql/data" + POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" + volumes: + - ./data/postgres:/var/lib/postgresql/data diff --git a/packages/subgraph/matchstick.yaml b/packages/subgraph/matchstick.yaml new file mode 100644 index 00000000..4f1961d6 --- /dev/null +++ b/packages/subgraph/matchstick.yaml @@ -0,0 +1,3 @@ +testsFolder: ./tests +libsFolder: ./node_modules +manifestPath: ./subgraph.yaml diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json new file mode 100644 index 00000000..b59da6eb --- /dev/null +++ b/packages/subgraph/package.json @@ -0,0 +1,39 @@ +{ + "name": "maci-subgraph", + "version": "2.2.1", + "description": "A subgraph to index data from MACI protocol to serve as data layer for frontend integration", + "private": false, + "files": [ + "build", + "schemas", + "config", + "templates", + "README.md" + ], + "scripts": { + "precodegen": "rm -rf ./generated && pnpm generate:schema && pnpm generate:yaml", + "codegen": "graph codegen", + "generate:yaml": "mustache ./config/${NETWORK:-network}.json ./templates/subgraph.template.yaml > subgraph.yaml", + "generate:schema": "cp ./schemas/schema.${VERSION:-v1}.graphql schema.graphql", + "prebuild": "pnpm codegen", + "build": "graph build", + "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ maci-subgraph", + "create-local": "graph create --node http://localhost:8020/ maci-subgraph", + "remove-local": "graph remove --node http://localhost:8020/ maci-subgraph", + "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 maci-subgraph --network localhost", + "deploy:mainnet": "graph deploy --node https://api.studio.thegraph.com/deploy/ maci-subgraph --network mainnet", + "test": "graph test", + "test:coverage": "graph test && graph test -c" + }, + "dependencies": { + "@graphprotocol/graph-cli": "^0.80.0", + "@graphprotocol/graph-ts": "^0.35.1", + "maci-platform-contracts": "workspace:^0.1.0" + }, + "devDependencies": { + "assemblyscript": "0.19.23", + "matchstick-as": "^0.6.0", + "mustache": "^4.2.0", + "wabt": "^1.0.36" + } +} diff --git a/packages/subgraph/schemas/schema.v1.graphql b/packages/subgraph/schemas/schema.v1.graphql new file mode 100644 index 00000000..0829bd7f --- /dev/null +++ b/packages/subgraph/schemas/schema.v1.graphql @@ -0,0 +1,110 @@ +type MACI @entity { + id: Bytes! # address + stateTreeDepth: BigInt! # uint8 + updatedAt: BigInt! + + "state" + numSignUps: BigInt! + numPoll: BigInt! + latestPoll: Bytes! + + "relations" + polls: [Poll!]! @derivedFrom(field: "maci") +} + +type User @entity(immutable: true) { + id: ID! # pubkey + createdAt: BigInt! # uint256 + "relations" + accounts: [Account!]! @derivedFrom(field: "owner") +} + +type Account @entity { + id: ID! # stateIndex + voiceCreditBalance: BigInt! # uint256 + createdAt: BigInt! # uint256 + "relations" + owner: User! +} + +enum RequestType { + Add + Change + Remove +} + +enum Status { + Pending + Approved + Rejected +} + +type Request @entity { + id: ID! + requestType: RequestType! + index: BigInt + status: Status! + "relations" + recipient: Recipient! + registryManager: RegistryManager! + registry: Registry! +} + +type RegistryManager @entity { + id: Bytes! # address + requests: [Request!]! @derivedFrom(field: "registryManager") +} + +type Recipient @entity { + id: Bytes! + metadataUrl: Bytes! + payout: Bytes! + index: BigInt! + deleted: Boolean! + initialized: Boolean! + "relations" + registry: Registry! +} + +type Registry @entity { + id: Bytes! # address + "relations" + poll: Poll + recipients: [Recipient!]! @derivedFrom(field: "registry") +} + +type Poll @entity { + id: Bytes! # poll address + pollId: BigInt! # uint256 + duration: BigInt! # uint256 + treeDepth: BigInt! # uint8 + maxMessages: BigInt! + maxVoteOption: BigInt! + messageProcessor: Bytes! # address + tally: Bytes! # address + createdAt: BigInt! + updatedAt: BigInt! + mode: BigInt! # uint8 + "merge state after ended" + stateRoot: BigInt # uint256 + numSignups: BigInt! # uint256 + numMessages: BigInt! # uint256 + "merge message tree after ended" + numSrQueueOps: BigInt # uint256 + messageRoot: BigInt + + "relations" + owner: Bytes! + maci: MACI! + registry: Registry + votes: [Vote!]! @derivedFrom(field: "poll") +} + +type Vote @entity(immutable: true) { + id: Bytes! + data: [BigInt!]! # uint256[10] + timestamp: BigInt! + + "relations" + poll: Poll! +} diff --git a/packages/subgraph/src/@types/global.d.ts b/packages/subgraph/src/@types/global.d.ts new file mode 100644 index 00000000..ba0f5286 --- /dev/null +++ b/packages/subgraph/src/@types/global.d.ts @@ -0,0 +1 @@ +declare const changetype: (...args: unknown[]) => T; diff --git a/packages/subgraph/src/maci.ts b/packages/subgraph/src/maci.ts new file mode 100644 index 00000000..e2d4815f --- /dev/null +++ b/packages/subgraph/src/maci.ts @@ -0,0 +1,68 @@ +/* eslint-disable no-underscore-dangle */ +import { Address, BigInt as GraphBN } from "@graphprotocol/graph-ts"; + +import { DeployPoll as DeployPollEvent, SignUp as SignUpEvent, MACI as MaciContract } from "../generated/MACI/MACI"; +import { Poll } from "../generated/schema"; +import { Poll as PollTemplate } from "../generated/templates"; +import { Poll as PollContract } from "../generated/templates/Poll/Poll"; + +import { ONE_BIG_INT, MESSAGE_TREE_ARITY } from "./utils/constants"; +import { createOrLoadMACI, createOrLoadUser, createOrLoadAccount } from "./utils/entity"; + +export function handleDeployPoll(event: DeployPollEvent): void { + const maci = createOrLoadMACI(event); + + const id = event.params._pollId; + + const maciContract = MaciContract.bind(Address.fromBytes(maci.id)); + const contracts = maciContract.getPoll(id); + const poll = new Poll(contracts.poll); + const contract = PollContract.bind(contracts.poll); + const treeDepths = contract.treeDepths(); + const durations = contract.getDeployTimeAndDuration(); + + poll.pollId = id; + poll.messageProcessor = contracts.messageProcessor; + poll.tally = contracts.tally; + poll.maxMessages = GraphBN.fromI32(MESSAGE_TREE_ARITY ** treeDepths.getMessageTreeDepth()); + poll.maxVoteOption = GraphBN.fromI32(MESSAGE_TREE_ARITY ** treeDepths.getVoteOptionTreeDepth()); + poll.treeDepth = GraphBN.fromI32(treeDepths.value0); + poll.duration = durations.value1; + poll.mode = GraphBN.fromI32(event.params._mode); + + poll.createdAt = event.block.timestamp; + poll.updatedAt = event.block.timestamp; + poll.owner = event.transaction.from; + + poll.numSignups = maci.numSignUps; + poll.numMessages = GraphBN.zero(); + poll.maci = maci.id; + poll.save(); + + maci.numPoll = maci.numPoll.plus(ONE_BIG_INT); + maci.latestPoll = poll.id; + maci.updatedAt = event.block.timestamp; + maci.save(); + + // Start indexing the poll; `event.params.pollAddr.poll` is the + // address of the new poll contract + PollTemplate.create(Address.fromBytes(poll.id)); +} + +export function handleSignUp(event: SignUpEvent): void { + const user = createOrLoadUser(event.params._userPubKeyX, event.params._userPubKeyY, event); + createOrLoadAccount(event.params._stateIndex, event, user.id, event.params._voiceCreditBalance); + + const maci = createOrLoadMACI(event); + maci.numSignUps = maci.numSignUps.plus(ONE_BIG_INT); + maci.updatedAt = event.block.timestamp; + maci.save(); + + const poll = Poll.load(maci.latestPoll); + + if (poll) { + poll.numSignups = poll.numSignups.plus(ONE_BIG_INT); + poll.updatedAt = event.block.timestamp; + poll.save(); + } +} diff --git a/packages/subgraph/src/poll.ts b/packages/subgraph/src/poll.ts new file mode 100644 index 00000000..d5a7c21d --- /dev/null +++ b/packages/subgraph/src/poll.ts @@ -0,0 +1,84 @@ +/* eslint-disable no-underscore-dangle */ + +import { Poll, Vote, MACI } from "../generated/schema"; +import { + MergeMaciState as MergeMaciStateEvent, + MergeMessageAq as MergeMessageAqEvent, + MergeMessageAqSubRoots as MergeMessageAqSubRootsEvent, + PublishMessage as PublishMessageEvent, + SetRegistry as SetRegistryEvent, +} from "../generated/templates/Poll/Poll"; + +import { ONE_BIG_INT } from "./utils/constants"; +import { createOrLoadRegistry } from "./utils/entity"; + +export function handleMergeMaciState(event: MergeMaciStateEvent): void { + const poll = Poll.load(event.address); + + if (poll) { + poll.stateRoot = event.params._stateRoot; + poll.numSignups = event.params._numSignups; + poll.updatedAt = event.block.timestamp; + poll.save(); + + const maci = MACI.load(poll.maci); + + if (maci) { + maci.numSignUps = event.params._numSignups; + maci.updatedAt = event.block.timestamp; + maci.save(); + } + } +} + +export function handleMergeMessageAq(event: MergeMessageAqEvent): void { + const poll = Poll.load(event.address); + + if (poll) { + poll.messageRoot = event.params._messageRoot; + poll.updatedAt = event.block.timestamp; + poll.save(); + } +} + +export function handleMergeMessageAqSubRoots(event: MergeMessageAqSubRootsEvent): void { + const poll = Poll.load(event.address); + + if (poll) { + poll.numSrQueueOps = event.params._numSrQueueOps; + poll.updatedAt = event.block.timestamp; + poll.save(); + } +} + +export function handlePublishMessage(event: PublishMessageEvent): void { + const vote = new Vote(event.transaction.hash.concatI32(event.logIndex.toI32())); + vote.data = event.params._message.data; + vote.poll = event.address; + vote.timestamp = event.block.timestamp; + vote.save(); + + const poll = Poll.load(event.address); + + if (poll) { + poll.numMessages = poll.numMessages.plus(ONE_BIG_INT); + poll.updatedAt = event.block.timestamp; + poll.save(); + } +} + +export function handleSetRegistry(event: SetRegistryEvent): void { + const poll = Poll.load(event.address); + + if (!poll) { + return; + } + + const registry = createOrLoadRegistry(event.params.registry); + registry.poll = poll.id; + registry.save(); + + poll.registry = event.params.registry; + poll.updatedAt = event.block.timestamp; + poll.save(); +} diff --git a/packages/subgraph/src/registry.ts b/packages/subgraph/src/registry.ts new file mode 100644 index 00000000..e392bed7 --- /dev/null +++ b/packages/subgraph/src/registry.ts @@ -0,0 +1,44 @@ +import { Recipient } from "../generated/schema"; +import { RecipientAdded, RecipientChanged, RecipientRemoved } from "../generated/templates/Registry/Registry"; + +import { createOrLoadRecipient, createOrLoadRegistry } from "./utils/entity"; + +export function handleAddRecipient(event: RecipientAdded): void { + createOrLoadRegistry(event.address); + const recipient = createOrLoadRecipient( + event.params.id, + event.params.metadataUrl, + event.params.index, + event.params.payout, + event.address, + ); + + recipient.initialized = true; + recipient.save(); +} + +export function handleChangeRecipient(event: RecipientChanged): void { + const recipient = Recipient.load(event.params.id); + + if (!recipient) { + return; + } + + recipient.metadataUrl = event.params.metadataUrl; + recipient.index = event.params.index; + recipient.initialized = true; + recipient.deleted = false; + recipient.payout = event.params.newPayout; + recipient.save(); +} + +export function handleRemoveRecipient(event: RecipientRemoved): void { + const recipient = Recipient.load(event.params.id); + + if (!recipient) { + return; + } + + recipient.deleted = true; + recipient.save(); +} diff --git a/packages/subgraph/src/registryManager.ts b/packages/subgraph/src/registryManager.ts new file mode 100644 index 00000000..9c37b2ab --- /dev/null +++ b/packages/subgraph/src/registryManager.ts @@ -0,0 +1,72 @@ +import { + RequestApproved, + RequestRejected, + RequestSent, + RegistryManager as RegistryManagerContract, +} from "../generated/RegistryManager/RegistryManager"; +import { Request } from "../generated/schema"; + +import { ONE_BIG_INT, RequestTypes } from "./utils/constants"; +import { createOrLoadRecipient, createOrLoadRegistryManager } from "./utils/entity"; + +export function handleRequestSent(event: RequestSent): void { + const registryManager = createOrLoadRegistryManager(event); + const contract = RegistryManagerContract.bind(event.address); + const id = contract.requestCount().minus(ONE_BIG_INT); + const request = new Request(id.toString()); + + switch (event.params.requestType) { + case RequestTypes.Add: { + request.requestType = "Add"; + break; + } + case RequestTypes.Change: { + request.requestType = "Change"; + break; + } + case RequestTypes.Remove: { + request.requestType = "Remove"; + break; + } + default: + break; + } + + const recipient = createOrLoadRecipient( + event.params.recipient, + event.params.metadataUrl, + event.params.index, + event.params.payout, + event.params.registry, + ); + + request.status = "Pending"; + request.index = event.params.index; + request.recipient = recipient.id; + request.registryManager = registryManager.id; + request.registry = event.params.registry; + + request.save(); +} + +export function handleRequestApproved(event: RequestApproved): void { + const request = Request.load(event.params.index.toString()); + + if (!request) { + return; + } + + request.status = "Approved"; + request.save(); +} + +export function handleRequestRejected(event: RequestRejected): void { + const request = Request.load(event.params.index.toString()); + + if (!request) { + return; + } + + request.status = "Rejected"; + request.save(); +} diff --git a/packages/subgraph/src/utils/constants.ts b/packages/subgraph/src/utils/constants.ts new file mode 100644 index 00000000..49f2dab8 --- /dev/null +++ b/packages/subgraph/src/utils/constants.ts @@ -0,0 +1,10 @@ +import { BigInt as GraphBN } from "@graphprotocol/graph-ts"; + +export const ONE_BIG_INT = GraphBN.fromU32(1); +export const MESSAGE_TREE_ARITY = 5; + +export enum RequestTypes { + Add, + Change, + Remove, +} diff --git a/packages/subgraph/src/utils/entity.ts b/packages/subgraph/src/utils/entity.ts new file mode 100644 index 00000000..fdd60e1f --- /dev/null +++ b/packages/subgraph/src/utils/entity.ts @@ -0,0 +1,100 @@ +/* eslint-disable no-underscore-dangle */ +import { BigInt as GraphBN, Bytes, ethereum, Address } from "@graphprotocol/graph-ts"; + +import { Account, MACI, Recipient, Registry, RegistryManager, User } from "../../generated/schema"; +import { Registry as RegistryTemplate } from "../../generated/templates"; + +export const createOrLoadMACI = (event: ethereum.Event, stateTreeDepth: GraphBN = GraphBN.fromI32(10)): MACI => { + let maci = MACI.load(event.address); + + if (!maci) { + maci = new MACI(event.address); + maci.stateTreeDepth = stateTreeDepth; + maci.updatedAt = event.block.timestamp; + maci.numPoll = GraphBN.zero(); + maci.numSignUps = GraphBN.zero(); + maci.latestPoll = Bytes.empty(); + maci.save(); + } + + return maci; +}; + +export const createOrLoadRegistryManager = (event: ethereum.Event): RegistryManager => { + let registryManager = RegistryManager.load(event.address); + + if (!registryManager) { + registryManager = new RegistryManager(event.address); + registryManager.save(); + } + + return registryManager; +}; + +export const createOrLoadUser = (publicKeyX: GraphBN, publicKeyY: GraphBN, event: ethereum.Event): User => { + const publicKey = `${publicKeyX.toString()} ${publicKeyY.toString()}`; + let user = User.load(publicKey); + + if (!user) { + user = new User(publicKey); + user.createdAt = event.block.timestamp; + user.save(); + } + + return user; +}; + +export const createOrLoadAccount = ( + stateIndex: GraphBN, + event: ethereum.Event, + owner: string, + voiceCreditBalance: GraphBN = GraphBN.zero(), +): Account => { + const id = stateIndex.toString(); + let account = Account.load(id); + + if (!account) { + account = new Account(id); + account.owner = owner; + account.voiceCreditBalance = voiceCreditBalance; + account.createdAt = event.block.timestamp; + account.save(); + } + + return account; +}; + +export const createOrLoadRegistry = (id: Address): Registry => { + let registry = Registry.load(id); + + if (!registry) { + registry = new Registry(id); + registry.save(); + RegistryTemplate.create(id); + } + + return registry; +}; + +export const createOrLoadRecipient = ( + id: Bytes, + metadataUrl: Bytes, + index: GraphBN, + payout: Address, + registry: Address, +): Recipient => { + let recipient = Recipient.load(id); + + if (!recipient) { + recipient = new Recipient(id); + recipient.metadataUrl = metadataUrl; + recipient.index = index; + recipient.payout = payout; + recipient.registry = registry; + recipient.initialized = false; + recipient.deleted = false; + recipient.save(); + } + + return recipient; +}; diff --git a/packages/subgraph/templates/subgraph.template.yaml b/packages/subgraph/templates/subgraph.template.yaml new file mode 100644 index 00000000..82de93b3 --- /dev/null +++ b/packages/subgraph/templates/subgraph.template.yaml @@ -0,0 +1,110 @@ +specVersion: 1.2.0 +description: Subgraph Indexer for MACI contract +repository: https://github.com/privacy-scaling-explorations/maci-platform +indexerHints: + prune: auto +schema: + file: ./schema.graphql +dataSources: + - kind: ethereum + name: MACI + network: {{ network }} + source: + abi: MACI + address: "{{ maciContractAddress }}" + startBlock: {{ maciContractStartBlock }} + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + entities: + - MACI + - Poll + abis: + - name: MACI + file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/maci/MACI.sol/MACI.json + - name: Poll + file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/maci/Poll.sol/Poll.json + eventHandlers: + - event: DeployPoll(uint256,indexed uint256,indexed uint256,uint8) + handler: handleDeployPoll + - event: SignUp(uint256,indexed uint256,indexed uint256,uint256,uint256) + handler: handleSignUp + file: ./src/maci.ts + - kind: ethereum + name: RegistryManager + network: {{ network }} + source: + abi: RegistryManager + address: "{{ registryManagerContractAddress }}" + startBlock: {{ registryManagerContractStartBlock }} + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + entities: + - RegistryManager + - BaseRegistry + abis: + - name: RegistryManager + file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/registryManager/RegistryManager.sol/RegistryManager.json + - name: BaseRegistry + file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/registry/BaseRegistry.sol/BaseRegistry.json + eventHandlers: + - event: RequestSent(indexed address,indexed uint8,indexed bytes32,uint256,address,bytes32) + handler: handleRequestSent + - event: RequestApproved(indexed address,indexed uint8,indexed bytes32,uint256,address,bytes32) + handler: handleRequestApproved + - event: RequestRejected(indexed address,indexed uint8,indexed bytes32,uint256,address,bytes32) + handler: handleRequestRejected + file: ./src/registryManager.ts +templates: + - kind: ethereum + name: Poll + network: {{ network }} + source: + abi: Poll + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + entities: + - Poll + abis: + - name: Poll + file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/maci/Poll.sol/Poll.json + eventHandlers: + - event: MergeMaciState(indexed uint256,indexed uint256) + handler: handleMergeMaciState + - event: MergeMessageAq(indexed uint256) + handler: handleMergeMessageAq + - event: MergeMessageAqSubRoots(indexed uint256) + handler: handleMergeMessageAqSubRoots + - event: PublishMessage((uint256[10]),(uint256,uint256)) + handler: handlePublishMessage + - event: SetRegistry(indexed address) + handler: handleSetRegistry + file: ./src/poll.ts + + - kind: ethereum + name: Registry + network: {{ network }} + source: + abi: Registry + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + entities: + - Registry + abis: + - name: Registry + file: ./node_modules/maci-platform-contracts/build/artifacts/contracts/registry/BaseRegistry.sol/BaseRegistry.json + eventHandlers: + - event: RecipientAdded(indexed uint256,bytes32,indexed bytes32,indexed address) + handler: handleAddRecipient + - event: RecipientChanged(indexed uint256,bytes32,indexed bytes32,indexed address) + handler: handleChangeRecipient + - event: RecipientRemoved(indexed uint256,bytes32,indexed address) + handler: handleRemoveRecipient + file: ./src/registry.ts diff --git a/packages/subgraph/tests/common.ts b/packages/subgraph/tests/common.ts new file mode 100644 index 00000000..af2ffad8 --- /dev/null +++ b/packages/subgraph/tests/common.ts @@ -0,0 +1,54 @@ +import { Address, ethereum, BigInt } from "@graphprotocol/graph-ts"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createMockedFunction } from "matchstick-as"; + +export const DEFAULT_POLL_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000001"); + +export const DEFAULT_MESSAGE_PROCESSOR_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000002"); + +export const DEFAULT_TALLY_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000003"); + +export const DEFAULT_REGISTRY_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000004"); + +export const DEFAULT_REGISTRY_MANAGER_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000005"); + +export const DEFAULT_PAYOUT_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000006"); + +export function mockPollContract(): void { + createMockedFunction(DEFAULT_POLL_ADDRESS, "treeDepths", "treeDepths():(uint8,uint8,uint8,uint8)").returns([ + ethereum.Value.fromI32(1), + ethereum.Value.fromI32(2), + ethereum.Value.fromI32(3), + ethereum.Value.fromI32(4), + ]); + + createMockedFunction( + DEFAULT_POLL_ADDRESS, + "getDeployTimeAndDuration", + "getDeployTimeAndDuration():(uint256,uint256)", + ).returns([ethereum.Value.fromI32(30), ethereum.Value.fromI32(40)]); +} + +export function mockMaciContract(): void { + createMockedFunction( + Address.fromString("0xA16081F360e3847006dB660bae1c6d1b2e17eC2A"), + "getPoll", + "getPoll(uint256):((address,address,address))", + ) + .withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1))]) + .returns([ + ethereum.Value.fromTuple( + changetype([ + ethereum.Value.fromAddress(DEFAULT_POLL_ADDRESS), + ethereum.Value.fromAddress(DEFAULT_MESSAGE_PROCESSOR_ADDRESS), + ethereum.Value.fromAddress(DEFAULT_TALLY_ADDRESS), + ]), + ), + ]); +} + +export function mockRegistryManager(): void { + createMockedFunction(DEFAULT_REGISTRY_MANAGER_ADDRESS, "requestCount", "requestCount():(uint256)").returns([ + ethereum.Value.fromI32(1), + ]); +} diff --git a/packages/subgraph/tests/maci/maci.test.ts b/packages/subgraph/tests/maci/maci.test.ts new file mode 100644 index 00000000..b84d3137 --- /dev/null +++ b/packages/subgraph/tests/maci/maci.test.ts @@ -0,0 +1,118 @@ +/* eslint-disable no-underscore-dangle */ +import { BigInt } from "@graphprotocol/graph-ts"; +import { test, describe, afterEach, clearStore, assert, beforeAll } from "matchstick-as"; + +import { Account, MACI, Poll, User } from "../../generated/schema"; +import { handleSignUp, handleDeployPoll } from "../../src/maci"; +import { DEFAULT_POLL_ADDRESS, mockMaciContract, mockPollContract } from "../common"; + +import { createSignUpEvent, createDeployPollEvent } from "./utils"; + +export { handleSignUp, handleDeployPoll }; + +describe("MACI", () => { + beforeAll(() => { + mockMaciContract(); + mockPollContract(); + }); + + afterEach(() => { + clearStore(); + }); + + test("should handle signup properly", () => { + const event = createSignUpEvent( + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + ); + + handleSignUp(event); + + const userId = `${event.params._userPubKeyX.toString()} ${event.params._userPubKeyY.toString()}`; + const maciAddress = event.address; + const user = User.load(userId)!; + const account = Account.load(event.params._stateIndex.toString())!; + const maci = MACI.load(maciAddress)!; + const poll = Poll.load(maci.latestPoll); + + assert.fieldEquals("User", user.id, "id", userId); + assert.fieldEquals("Account", account.id, "id", event.params._stateIndex.toString()); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numPoll", "0"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numSignUps", "1"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "latestPoll", "0x00000000"); + assert.assertTrue(maci.polls.load().length === 0); + assert.assertNull(poll); + }); + + test("should handle deploy poll properly (qv)", () => { + const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(0)); + + handleDeployPoll(event); + + const maciAddress = event.address; + const maci = MACI.load(maciAddress)!; + const poll = Poll.load(maci.latestPoll)!; + + assert.fieldEquals("Poll", poll.id.toHex(), "id", DEFAULT_POLL_ADDRESS.toHexString()); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numPoll", "1"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numSignUps", "0"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "latestPoll", poll.id.toHex()); + assert.fieldEquals("Poll", poll.id.toHexString(), "mode", "0"); + assert.assertTrue(maci.polls.load().length === 1); + }); + + test("should handle deploy poll properly (non-qv)", () => { + const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1)); + + handleDeployPoll(event); + + const maciAddress = event.address; + const maci = MACI.load(maciAddress)!; + const poll = Poll.load(maci.latestPoll)!; + + assert.fieldEquals("Poll", poll.id.toHex(), "id", DEFAULT_POLL_ADDRESS.toHexString()); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numPoll", "1"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numSignUps", "0"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "latestPoll", poll.id.toHex()); + assert.fieldEquals("Poll", poll.id.toHexString(), "mode", "1"); + assert.assertTrue(maci.polls.load().length === 1); + }); + + test("should handle signup with deployed poll properly", () => { + const deployPollEvent = createDeployPollEvent( + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(0), + ); + + const signUpEvent = createSignUpEvent( + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + BigInt.fromI32(1), + ); + + handleDeployPoll(deployPollEvent); + handleSignUp(signUpEvent); + + const userId = `${signUpEvent.params._userPubKeyX.toString()} ${signUpEvent.params._userPubKeyY.toString()}`; + const maciAddress = deployPollEvent.address; + const user = User.load(userId)!; + const account = Account.load(signUpEvent.params._stateIndex.toString())!; + const maci = MACI.load(maciAddress)!; + const poll = Poll.load(maci.latestPoll)!; + + assert.fieldEquals("User", user.id, "id", userId); + assert.fieldEquals("Account", account.id, "id", signUpEvent.params._stateIndex.toString()); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numPoll", "1"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "numSignUps", "1"); + assert.fieldEquals("MACI", maciAddress.toHexString(), "latestPoll", poll.id.toHex()); + assert.assertTrue(maci.polls.load().length === 1); + assert.assertNotNull(poll); + }); +}); diff --git a/packages/subgraph/tests/maci/utils.ts b/packages/subgraph/tests/maci/utils.ts new file mode 100644 index 00000000..76e4a79a --- /dev/null +++ b/packages/subgraph/tests/maci/utils.ts @@ -0,0 +1,45 @@ +import { BigInt as GraphBN, ethereum } from "@graphprotocol/graph-ts"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { newMockEvent } from "matchstick-as"; + +import { SignUp, DeployPoll } from "../../generated/MACI/MACI"; + +export function createSignUpEvent( + stateIndex: GraphBN, + userPubKeyX: GraphBN, + userPubKeyY: GraphBN, + voiceCreditBalance: GraphBN, + timestamp: GraphBN, +): SignUp { + const event = changetype(newMockEvent()); + + event.parameters.push(new ethereum.EventParam("_stateIndex", ethereum.Value.fromUnsignedBigInt(stateIndex))); + event.parameters.push(new ethereum.EventParam("_userPubKeyX", ethereum.Value.fromUnsignedBigInt(userPubKeyX))); + event.parameters.push(new ethereum.EventParam("_userPubKeyY", ethereum.Value.fromUnsignedBigInt(userPubKeyY))); + event.parameters.push( + new ethereum.EventParam("_voiceCreditBalance", ethereum.Value.fromUnsignedBigInt(voiceCreditBalance)), + ); + event.parameters.push(new ethereum.EventParam("_timestamp", ethereum.Value.fromUnsignedBigInt(timestamp))); + + return event; +} + +export function createDeployPollEvent( + pollId: GraphBN, + coordinatorPubKeyX: GraphBN, + coordinatorPubKeyY: GraphBN, + mode: GraphBN, +): DeployPoll { + const event = changetype(newMockEvent()); + + event.parameters.push(new ethereum.EventParam("_pollId", ethereum.Value.fromUnsignedBigInt(pollId))); + event.parameters.push( + new ethereum.EventParam("_coordinatorPubKeyX", ethereum.Value.fromUnsignedBigInt(coordinatorPubKeyX)), + ); + event.parameters.push( + new ethereum.EventParam("_coordinatorPubKeyY", ethereum.Value.fromUnsignedBigInt(coordinatorPubKeyY)), + ); + event.parameters.push(new ethereum.EventParam("mode", ethereum.Value.fromUnsignedBigInt(mode))); + + return event; +} diff --git a/packages/subgraph/tests/poll/poll.test.ts b/packages/subgraph/tests/poll/poll.test.ts new file mode 100644 index 00000000..d8ad7267 --- /dev/null +++ b/packages/subgraph/tests/poll/poll.test.ts @@ -0,0 +1,121 @@ +/* eslint-disable no-underscore-dangle */ +import { BigInt } from "@graphprotocol/graph-ts"; +import { test, describe, afterEach, clearStore, assert, beforeEach } from "matchstick-as"; + +import { MACI, Poll } from "../../generated/schema"; +import { handleDeployPoll } from "../../src/maci"; +import { + handleMergeMaciState, + handleMergeMessageAq, + handleMergeMessageAqSubRoots, + handlePublishMessage, + handleSetRegistry, +} from "../../src/poll"; +import { DEFAULT_POLL_ADDRESS, DEFAULT_REGISTRY_ADDRESS, mockMaciContract, mockPollContract } from "../common"; +import { createDeployPollEvent } from "../maci/utils"; + +import { + createMergeMaciStateEvent, + createMergeMessageAqEvent, + createMergeMessageAqSubRootsEvent, + createPublishMessageEvent, + createSetRegistryEvent, +} from "./utils"; + +export { + handleMergeMaciState, + handleMergeMessageAq, + handleMergeMessageAqSubRoots, + handlePublishMessage, + handleSetRegistry, +}; + +describe("Poll", () => { + beforeEach(() => { + mockMaciContract(); + mockPollContract(); + + // mock the deploy poll event with non qv mode set + const event = createDeployPollEvent(BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1), BigInt.fromI32(1)); + + handleDeployPoll(event); + }); + + afterEach(() => { + clearStore(); + }); + + test("should handle merge maci state properly", () => { + const event = createMergeMaciStateEvent(DEFAULT_POLL_ADDRESS, BigInt.fromI32(1), BigInt.fromI32(3)); + + handleMergeMaciState(event); + + const poll = Poll.load(event.address)!; + const maci = MACI.load(poll.maci)!; + + assert.fieldEquals("Poll", poll.id.toHex(), "stateRoot", "1"); + assert.fieldEquals("Poll", poll.id.toHex(), "numSignups", "3"); + assert.fieldEquals("MACI", maci.id.toHexString(), "numPoll", "1"); + assert.fieldEquals("MACI", maci.id.toHexString(), "numSignUps", "3"); + assert.fieldEquals("MACI", maci.id.toHexString(), "latestPoll", poll.id.toHex()); + assert.assertTrue(maci.polls.load().length === 1); + }); + + test("should handle merge message queue properly", () => { + const event = createMergeMessageAqEvent(DEFAULT_POLL_ADDRESS, BigInt.fromI32(1)); + + handleMergeMessageAq(event); + + const poll = Poll.load(DEFAULT_POLL_ADDRESS)!; + + assert.fieldEquals("Poll", poll.id.toHex(), "messageRoot", "1"); + }); + + test("should handle merge message queue subroots properly", () => { + const event = createMergeMessageAqSubRootsEvent(DEFAULT_POLL_ADDRESS, BigInt.fromI32(1)); + + handleMergeMessageAqSubRoots(event); + + const poll = Poll.load(event.address)!; + + assert.fieldEquals("Poll", poll.id.toHex(), "numSrQueueOps", "1"); + }); + + test("should handle publish message properly", () => { + const event = createPublishMessageEvent( + DEFAULT_POLL_ADDRESS, + [ + BigInt.fromI32(0), + BigInt.fromI32(1), + BigInt.fromI32(2), + BigInt.fromI32(3), + BigInt.fromI32(4), + BigInt.fromI32(5), + BigInt.fromI32(6), + BigInt.fromI32(7), + BigInt.fromI32(8), + BigInt.fromI32(9), + ], + BigInt.fromI32(2), + BigInt.fromI32(3), + ); + + handlePublishMessage(event); + + const poll = Poll.load(event.address)!; + + assert.entityCount("Vote", 1); + assert.fieldEquals("Poll", poll.id.toHex(), "numMessages", "1"); + }); + + test("should handle setting registry properly", () => { + const event = createSetRegistryEvent(DEFAULT_POLL_ADDRESS, DEFAULT_REGISTRY_ADDRESS); + + handleSetRegistry(event); + + const poll = Poll.load(event.address)!; + + assert.fieldEquals("Registry", DEFAULT_REGISTRY_ADDRESS.toHex(), "id", DEFAULT_REGISTRY_ADDRESS.toHex()); + assert.fieldEquals("Poll", poll.id.toHex(), "registry", DEFAULT_REGISTRY_ADDRESS.toHex()); + }); +}); diff --git a/packages/subgraph/tests/poll/utils.ts b/packages/subgraph/tests/poll/utils.ts new file mode 100644 index 00000000..283b11c9 --- /dev/null +++ b/packages/subgraph/tests/poll/utils.ts @@ -0,0 +1,75 @@ +import { Address, BigInt as GraphBN, ethereum } from "@graphprotocol/graph-ts"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { newMockEvent } from "matchstick-as"; + +import { + MergeMaciState, + MergeMessageAq, + MergeMessageAqSubRoots, + PublishMessage, + SetRegistry, +} from "../../generated/templates/Poll/Poll"; + +export function createMergeMaciStateEvent(address: Address, stateRoot: GraphBN, numSignups: GraphBN): MergeMaciState { + const event = changetype(newMockEvent()); + + event.parameters.push(new ethereum.EventParam("_stateRoot", ethereum.Value.fromUnsignedBigInt(stateRoot))); + event.parameters.push(new ethereum.EventParam("_numSignups", ethereum.Value.fromUnsignedBigInt(numSignups))); + event.address = address; + + return event; +} + +export function createMergeMessageAqEvent(address: Address, messageRoot: GraphBN): MergeMessageAq { + const event = changetype(newMockEvent()); + + event.parameters.push(new ethereum.EventParam("_messageRoot", ethereum.Value.fromUnsignedBigInt(messageRoot))); + event.address = address; + + return event; +} + +export function createMergeMessageAqSubRootsEvent(address: Address, numSrQueueOps: GraphBN): MergeMessageAqSubRoots { + const event = changetype(newMockEvent()); + + event.parameters.push(new ethereum.EventParam("_numSrQueueOps", ethereum.Value.fromUnsignedBigInt(numSrQueueOps))); + event.address = address; + + return event; +} + +export function createPublishMessageEvent( + address: Address, + data: GraphBN[], + encPubKeyX: GraphBN, + encPubKeyY: GraphBN, +): PublishMessage { + const event = changetype(newMockEvent()); + + event.parameters.push( + new ethereum.EventParam( + "_message", + ethereum.Value.fromTuple(changetype([ethereum.Value.fromUnsignedBigIntArray(data)])), + ), + ); + event.parameters.push( + new ethereum.EventParam( + "_encPubKey", + ethereum.Value.fromTuple( + changetype(ethereum.Value.fromUnsignedBigIntArray([encPubKeyX, encPubKeyY])), + ), + ), + ); + event.address = address; + + return event; +} + +export function createSetRegistryEvent(address: Address, registry: Address): SetRegistry { + const event = changetype(newMockEvent()); + + event.parameters.push(new ethereum.EventParam("registry", ethereum.Value.fromAddress(registry))); + event.address = address; + + return event; +} diff --git a/packages/subgraph/tests/registry/registry.test.ts b/packages/subgraph/tests/registry/registry.test.ts new file mode 100644 index 00000000..4a81e77a --- /dev/null +++ b/packages/subgraph/tests/registry/registry.test.ts @@ -0,0 +1,99 @@ +import { BigInt, Bytes } from "@graphprotocol/graph-ts"; +import { afterEach, assert, clearStore, describe, test } from "matchstick-as"; + +import { Recipient, Registry } from "../../generated/schema"; +import { handleAddRecipient, handleChangeRecipient, handleRemoveRecipient } from "../../src/registry"; +import { DEFAULT_PAYOUT_ADDRESS, DEFAULT_REGISTRY_ADDRESS } from "../common"; + +import { createRecipientAddEvent, createRecipientChangeEvent, createRecipientRemoveEvent } from "./utils"; + +export { handleAddRecipient, handleChangeRecipient, handleRemoveRecipient }; + +describe("Registry", () => { + afterEach(() => { + clearStore(); + }); + + test("should handle adding recipient properly", () => { + const event = createRecipientAddEvent( + DEFAULT_REGISTRY_ADDRESS, + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_PAYOUT_ADDRESS, + Bytes.fromUTF8("url"), + ); + + handleAddRecipient(event); + + const registry = Registry.load(event.address)!; + const recipient = Recipient.load(event.params.id)!; + + assert.fieldEquals("Registry", registry.id.toHex(), "id", DEFAULT_REGISTRY_ADDRESS.toHex()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "index", event.params.index.toString()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "deleted", "false"); + assert.fieldEquals("Recipient", recipient.id.toHex(), "initialized", "true"); + assert.fieldEquals("Recipient", recipient.id.toHex(), "metadataUrl", event.params.metadataUrl.toHex()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "payout", event.params.payout.toHex()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "registry", DEFAULT_REGISTRY_ADDRESS.toHex()); + }); + + test("should handle changing recipient properly", () => { + handleAddRecipient( + createRecipientAddEvent( + DEFAULT_REGISTRY_ADDRESS, + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_PAYOUT_ADDRESS, + Bytes.fromUTF8("url"), + ), + ); + + const event = createRecipientChangeEvent( + DEFAULT_REGISTRY_ADDRESS, + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_REGISTRY_ADDRESS, + Bytes.fromUTF8("url"), + ); + + handleChangeRecipient(event); + + const registry = Registry.load(event.address)!; + const recipient = Recipient.load(event.params.id)!; + + assert.fieldEquals("Registry", registry.id.toHex(), "id", DEFAULT_REGISTRY_ADDRESS.toHex()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "index", event.params.index.toString()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "deleted", "false"); + assert.fieldEquals("Recipient", recipient.id.toHex(), "initialized", "true"); + assert.fieldEquals("Recipient", recipient.id.toHex(), "metadataUrl", event.params.metadataUrl.toHex()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "payout", event.params.newPayout.toHex()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "registry", DEFAULT_REGISTRY_ADDRESS.toHex()); + }); + + test("should handle removing recipient properly", () => { + handleAddRecipient( + createRecipientAddEvent( + DEFAULT_REGISTRY_ADDRESS, + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_PAYOUT_ADDRESS, + Bytes.fromUTF8("url"), + ), + ); + + const event = createRecipientRemoveEvent( + DEFAULT_REGISTRY_ADDRESS, + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_REGISTRY_ADDRESS, + ); + + handleRemoveRecipient(event); + + const registry = Registry.load(event.address)!; + const recipient = Recipient.load(event.params.id)!; + + assert.fieldEquals("Registry", registry.id.toHex(), "id", DEFAULT_REGISTRY_ADDRESS.toHex()); + assert.fieldEquals("Recipient", recipient.id.toHex(), "deleted", "true"); + }); +}); diff --git a/packages/subgraph/tests/registry/utils.ts b/packages/subgraph/tests/registry/utils.ts new file mode 100644 index 00000000..8c7a4698 --- /dev/null +++ b/packages/subgraph/tests/registry/utils.ts @@ -0,0 +1,60 @@ +import { Address, BigInt as GraphBN, Bytes, ethereum } from "@graphprotocol/graph-ts"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { newMockEvent } from "matchstick-as"; + +import { RecipientAdded, RecipientChanged, RecipientRemoved } from "../../generated/templates/Registry/Registry"; + +export function createRecipientAddEvent( + address: Address, + recipient: Bytes, + index: GraphBN, + payout: Address, + metadataUrl: Bytes, +): RecipientAdded { + const event = newMockEvent(); + + event.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index))); + event.parameters.push(new ethereum.EventParam("id", ethereum.Value.fromBytes(recipient))); + event.parameters.push(new ethereum.EventParam("metadataUrl", ethereum.Value.fromBytes(metadataUrl))); + event.parameters.push(new ethereum.EventParam("payout", ethereum.Value.fromAddress(payout))); + + event.address = address; + + return changetype(event); +} + +export function createRecipientChangeEvent( + address: Address, + recipient: Bytes, + index: GraphBN, + newPayout: Address, + metadataUrl: Bytes, +): RecipientChanged { + const event = newMockEvent(); + + event.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index))); + event.parameters.push(new ethereum.EventParam("id", ethereum.Value.fromBytes(recipient))); + event.parameters.push(new ethereum.EventParam("metadataUrl", ethereum.Value.fromBytes(metadataUrl))); + event.parameters.push(new ethereum.EventParam("newPayout", ethereum.Value.fromAddress(newPayout))); + + event.address = address; + + return changetype(event); +} + +export function createRecipientRemoveEvent( + address: Address, + recipient: Bytes, + index: GraphBN, + newPayout: Address, +): RecipientRemoved { + const event = newMockEvent(); + + event.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index))); + event.parameters.push(new ethereum.EventParam("id", ethereum.Value.fromBytes(recipient))); + event.parameters.push(new ethereum.EventParam("payout", ethereum.Value.fromAddress(newPayout))); + + event.address = address; + + return changetype(event); +} diff --git a/packages/subgraph/tests/registryManager/registryManager.test.ts b/packages/subgraph/tests/registryManager/registryManager.test.ts new file mode 100644 index 00000000..5e85e904 --- /dev/null +++ b/packages/subgraph/tests/registryManager/registryManager.test.ts @@ -0,0 +1,121 @@ +import { BigInt, Bytes } from "@graphprotocol/graph-ts"; +import { afterEach, assert, beforeEach, clearStore, describe, test } from "matchstick-as"; + +import { RequestApproved, RequestRejected, RequestSent } from "../../generated/RegistryManager/RegistryManager"; +import { RegistryManager, Request } from "../../generated/schema"; +import { handleRequestApproved, handleRequestRejected, handleRequestSent } from "../../src/registryManager"; +import { RequestTypes } from "../../src/utils/constants"; +import { + DEFAULT_PAYOUT_ADDRESS, + DEFAULT_REGISTRY_ADDRESS, + DEFAULT_REGISTRY_MANAGER_ADDRESS, + mockRegistryManager, +} from "../common"; + +import { createRequestEvent } from "./utils"; + +export { handleRequestApproved, handleRequestRejected, handleRequestSent }; + +describe("RegistryManager", () => { + beforeEach(() => { + mockRegistryManager(); + }); + + afterEach(() => { + clearStore(); + }); + + test("should send and approve requests properly", () => { + [RequestTypes.Add, RequestTypes.Change, RequestTypes.Remove].forEach((requestType) => { + const sentEvent = createRequestEvent( + DEFAULT_REGISTRY_MANAGER_ADDRESS, + DEFAULT_REGISTRY_ADDRESS, + BigInt.fromI32(requestType), + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_PAYOUT_ADDRESS, + Bytes.fromUTF8("metadataUrl"), + ); + + handleRequestSent(sentEvent); + + const registryManager = RegistryManager.load(sentEvent.address)!; + const request = Request.load("0")!; + + assert.fieldEquals("RegistryManager", registryManager.id.toHex(), "id", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + assert.fieldEquals("Request", request.id, "requestType", ["Add", "Change", "Remove"][requestType].toString()); + assert.fieldEquals("Request", request.id, "index", sentEvent.params.index.toString()); + assert.fieldEquals("Request", request.id, "status", "Pending"); + assert.fieldEquals("Request", request.id, "recipient", sentEvent.params.recipient.toHex()); + assert.fieldEquals("Request", request.id, "registry", sentEvent.params.registry.toHex()); + assert.fieldEquals("Request", request.id, "registryManager", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + + const approveEvent = createRequestEvent( + DEFAULT_REGISTRY_MANAGER_ADDRESS, + DEFAULT_REGISTRY_ADDRESS, + BigInt.fromI32(requestType), + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_PAYOUT_ADDRESS, + Bytes.fromUTF8("metadataUrl"), + ); + + handleRequestApproved(approveEvent); + + assert.fieldEquals("RegistryManager", registryManager.id.toHex(), "id", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + assert.fieldEquals("Request", request.id, "requestType", ["Add", "Change", "Remove"][requestType].toString()); + assert.fieldEquals("Request", request.id, "index", approveEvent.params.index.toString()); + assert.fieldEquals("Request", request.id, "status", "Approved"); + assert.fieldEquals("Request", request.id, "recipient", approveEvent.params.recipient.toHex()); + assert.fieldEquals("Request", request.id, "registry", approveEvent.params.registry.toHex()); + assert.fieldEquals("Request", request.id, "registryManager", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + }); + }); + + test("should send and reject requests properly", () => { + [RequestTypes.Add, RequestTypes.Change, RequestTypes.Remove].forEach((requestType) => { + const sentEvent = createRequestEvent( + DEFAULT_REGISTRY_MANAGER_ADDRESS, + DEFAULT_REGISTRY_ADDRESS, + BigInt.fromI32(requestType), + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_PAYOUT_ADDRESS, + Bytes.fromUTF8("metadataUrl"), + ); + + handleRequestSent(sentEvent); + + const registryManager = RegistryManager.load(sentEvent.address)!; + const request = Request.load("0")!; + + assert.fieldEquals("RegistryManager", registryManager.id.toHex(), "id", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + assert.fieldEquals("Request", request.id, "requestType", ["Add", "Change", "Remove"][requestType].toString()); + assert.fieldEquals("Request", request.id, "index", sentEvent.params.index.toString()); + assert.fieldEquals("Request", request.id, "status", "Pending"); + assert.fieldEquals("Request", request.id, "recipient", sentEvent.params.recipient.toHex()); + assert.fieldEquals("Request", request.id, "registry", sentEvent.params.registry.toHex()); + assert.fieldEquals("Request", request.id, "registryManager", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + + const rejectEvent = createRequestEvent( + DEFAULT_REGISTRY_MANAGER_ADDRESS, + DEFAULT_REGISTRY_ADDRESS, + BigInt.fromI32(requestType), + Bytes.fromUTF8("id"), + BigInt.fromI32(0), + DEFAULT_PAYOUT_ADDRESS, + Bytes.fromUTF8("metadataUrl"), + ); + + handleRequestRejected(rejectEvent); + + assert.fieldEquals("RegistryManager", registryManager.id.toHex(), "id", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + assert.fieldEquals("Request", request.id, "requestType", ["Add", "Change", "Remove"][requestType].toString()); + assert.fieldEquals("Request", request.id, "index", rejectEvent.params.index.toString()); + assert.fieldEquals("Request", request.id, "status", "Rejected"); + assert.fieldEquals("Request", request.id, "recipient", rejectEvent.params.recipient.toHex()); + assert.fieldEquals("Request", request.id, "registry", rejectEvent.params.registry.toHex()); + assert.fieldEquals("Request", request.id, "registryManager", DEFAULT_REGISTRY_MANAGER_ADDRESS.toHex()); + }); + }); +}); diff --git a/packages/subgraph/tests/registryManager/utils.ts b/packages/subgraph/tests/registryManager/utils.ts new file mode 100644 index 00000000..9d525642 --- /dev/null +++ b/packages/subgraph/tests/registryManager/utils.ts @@ -0,0 +1,26 @@ +import { Address, BigInt as GraphBN, Bytes, ethereum } from "@graphprotocol/graph-ts"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { newMockEvent } from "matchstick-as"; + +export function createRequestEvent( + address: Address, + registry: Address, + requestType: GraphBN, + recipient: Bytes, + index: GraphBN, + payout: Address, + metadataUrl: Bytes, +): T { + const event = newMockEvent(); + + event.parameters.push(new ethereum.EventParam("registry", ethereum.Value.fromAddress(registry))); + event.parameters.push(new ethereum.EventParam("requestType", ethereum.Value.fromUnsignedBigInt(requestType))); + event.parameters.push(new ethereum.EventParam("recipient", ethereum.Value.fromBytes(recipient))); + event.parameters.push(new ethereum.EventParam("index", ethereum.Value.fromUnsignedBigInt(index))); + event.parameters.push(new ethereum.EventParam("payout", ethereum.Value.fromAddress(payout))); + event.parameters.push(new ethereum.EventParam("metadataUrl", ethereum.Value.fromBytes(metadataUrl))); + + event.address = address; + + return changetype(event); +} diff --git a/packages/subgraph/tsconfig.build.json b/packages/subgraph/tsconfig.build.json new file mode 100644 index 00000000..a688e95d --- /dev/null +++ b/packages/subgraph/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extend": "./node_modules/@graphprotocol/graph-ts/tsconfig.json", + "compilerOptions": { + "outDir": "./build", + "strictNullChecks": true, + "skipLibCheck": true, + "typeRoots": ["./src/@types/global.d.ts"] + }, + "include": ["./src", "./generated"] +} diff --git a/packages/subgraph/tsconfig.json b/packages/subgraph/tsconfig.json new file mode 100644 index 00000000..3fc29028 --- /dev/null +++ b/packages/subgraph/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extend": ["./node_modules/@graphprotocol/graph-ts/tsconfig.json"], + "compilerOptions": { + "outDir": "./build", + "strictNullChecks": true, + "skipLibCheck": true, + "typeRoots": ["./src/@types/global.d.ts"] + }, + "include": ["./src", "./tests", "./generated"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ad95886..7ff2ee49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -567,6 +567,31 @@ importers: specifier: ^5.3.3 version: 5.5.4 + packages/subgraph: + dependencies: + '@graphprotocol/graph-cli': + specifier: ^0.80.0 + version: 0.80.0(@types/node@22.2.0)(bufferutil@4.0.8)(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.5.4)(utf-8-validate@5.0.10) + '@graphprotocol/graph-ts': + specifier: ^0.35.1 + version: 0.35.1 + maci-platform-contracts: + specifier: workspace:^0.1.0 + version: link:../contracts + devDependencies: + assemblyscript: + specifier: 0.19.23 + version: 0.19.23 + matchstick-as: + specifier: ^0.6.0 + version: 0.6.0 + mustache: + specifier: ^4.2.0 + version: 4.2.0 + wabt: + specifier: ^1.0.36 + version: 1.0.36 + packages: '@adobe/css-tools@4.4.0': @@ -9376,6 +9401,9 @@ packages: marky@1.2.5: resolution: {integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==} + matchstick-as@0.6.0: + resolution: {integrity: sha512-E36fWsC1AbCkBFt05VsDDRoFvGSdcZg6oZJrtIe/YDBbuFh8SKbR5FcoqDhNWqSN+F7bN/iS2u8Md0SM+4pUpw==} + md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} @@ -12874,6 +12902,14 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + wabt@1.0.24: + resolution: {integrity: sha512-8l7sIOd3i5GWfTWciPL0+ff/FK/deVK2Q6FN+MPz4vfUcD78i2M/49XJTwF6aml91uIiuXJEsLKWMB2cw/mtKg==} + hasBin: true + + wabt@1.0.36: + resolution: {integrity: sha512-GAfEcFyvYRZ51xIZKeeCmIKytTz3ejCeEU9uevGNhEnqt9qXp3a8Q2O4ByZr6rKWcd8jV/Oj5cbDJFtmTYdchg==} + hasBin: true + wagmi@2.12.4: resolution: {integrity: sha512-qDyVISKHxqnX87LlkHwBMpfsp6yC7D7Er9BSI8IZlsNthQLN1I3Ih1+JzTBJg1DiLpazxmW8h7Yi4+qYhCgo6Q==} peerDependencies: @@ -15077,6 +15113,47 @@ snapshots: - typescript - utf-8-validate + '@graphprotocol/graph-cli@0.80.0(@types/node@22.2.0)(bufferutil@4.0.8)(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.5.4)(utf-8-validate@5.0.10)': + dependencies: + '@float-capital/float-subgraph-uncrashable': 0.0.0-internal-testing.5 + '@oclif/core': 2.8.6(@types/node@22.2.0)(typescript@5.5.4) + '@oclif/plugin-autocomplete': 2.3.10(@types/node@22.2.0)(typescript@5.5.4) + '@oclif/plugin-not-found': 2.4.3(@types/node@22.2.0)(typescript@5.5.4) + '@whatwg-node/fetch': 0.8.8 + assemblyscript: 0.19.23 + binary-install-raw: 0.0.13(debug@4.3.4) + chalk: 3.0.0 + chokidar: 3.5.3 + debug: 4.3.4(supports-color@8.1.1) + docker-compose: 0.23.19 + dockerode: 2.5.8 + fs-extra: 9.1.0 + glob: 9.3.5 + gluegun: 5.1.6(debug@4.3.4) + graphql: 15.5.0 + immutable: 4.2.1 + ipfs-http-client: 55.0.0(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13)) + jayson: 4.0.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + js-yaml: 3.14.1 + open: 8.4.2 + prettier: 3.0.3 + semver: 7.4.0 + sync-request: 6.1.0 + tmp-promise: 3.0.3 + web3-eth-abi: 1.7.0 + which: 2.0.2 + yaml: 1.10.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - encoding + - node-fetch + - supports-color + - typescript + - utf-8-validate + '@graphprotocol/graph-ts@0.35.1': dependencies: assemblyscript: 0.19.10 @@ -16602,7 +16679,7 @@ snapshots: - '@types/node' - typescript - '@oclif/core@2.8.6(@types/node@20.14.14)(typescript@5.5.4)': + '@oclif/core@2.16.0(@types/node@22.2.0)(typescript@5.5.4)': dependencies: '@types/cli-progress': 3.11.6 ansi-escapes: 4.3.2 @@ -16613,6 +16690,42 @@ snapshots: cli-progress: 3.12.0 debug: 4.3.4(supports-color@8.1.1) ejs: 3.1.10 + get-package-type: 0.1.0 + globby: 11.1.0 + hyperlinker: 1.0.0 + indent-string: 4.0.0 + is-wsl: 2.2.0 + js-yaml: 3.14.1 + natural-orderby: 2.0.3 + object-treeify: 1.1.33 + password-prompt: 1.1.3 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + supports-color: 8.1.1 + supports-hyperlinks: 2.3.0 + ts-node: 10.9.2(@types/node@22.2.0)(typescript@5.5.4) + tslib: 2.6.3 + widest-line: 3.1.0 + wordwrap: 1.0.0 + wrap-ansi: 7.0.0 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - typescript + + '@oclif/core@2.8.6(@types/node@20.14.14)(typescript@5.5.4)': + dependencies: + '@types/cli-progress': 3.11.6 + ansi-escapes: 4.3.2 + ansi-styles: 4.3.0 + cardinal: 2.1.1 + chalk: 4.1.2 + clean-stack: 3.0.1 + cli-progress: 3.12.0 + debug: 4.3.6(supports-color@8.1.1) + ejs: 3.1.10 fs-extra: 9.1.0 get-package-type: 0.1.0 globby: 11.1.0 @@ -16639,6 +16752,43 @@ snapshots: - '@types/node' - typescript + '@oclif/core@2.8.6(@types/node@22.2.0)(typescript@5.5.4)': + dependencies: + '@types/cli-progress': 3.11.6 + ansi-escapes: 4.3.2 + ansi-styles: 4.3.0 + cardinal: 2.1.1 + chalk: 4.1.2 + clean-stack: 3.0.1 + cli-progress: 3.12.0 + debug: 4.3.6(supports-color@8.1.1) + ejs: 3.1.10 + fs-extra: 9.1.0 + get-package-type: 0.1.0 + globby: 11.1.0 + hyperlinker: 1.0.0 + indent-string: 4.0.0 + is-wsl: 2.2.0 + js-yaml: 3.14.1 + natural-orderby: 2.0.3 + object-treeify: 1.1.33 + password-prompt: 1.1.3 + semver: 7.6.3 + string-width: 4.2.3 + strip-ansi: 6.0.1 + supports-color: 8.1.1 + supports-hyperlinks: 2.3.0 + ts-node: 10.9.2(@types/node@22.2.0)(typescript@5.5.4) + tslib: 2.6.3 + widest-line: 3.1.0 + wordwrap: 1.0.0 + wrap-ansi: 7.0.0 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - typescript + '@oclif/plugin-autocomplete@2.3.10(@types/node@20.14.14)(typescript@5.5.4)': dependencies: '@oclif/core': 2.16.0(@types/node@20.14.14)(typescript@5.5.4) @@ -16651,6 +16801,18 @@ snapshots: - supports-color - typescript + '@oclif/plugin-autocomplete@2.3.10(@types/node@22.2.0)(typescript@5.5.4)': + dependencies: + '@oclif/core': 2.16.0(@types/node@22.2.0)(typescript@5.5.4) + chalk: 4.1.2 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - supports-color + - typescript + '@oclif/plugin-not-found@2.4.3(@types/node@20.14.14)(typescript@5.5.4)': dependencies: '@oclif/core': 2.16.0(@types/node@20.14.14)(typescript@5.5.4) @@ -16662,6 +16824,17 @@ snapshots: - '@types/node' - typescript + '@oclif/plugin-not-found@2.4.3(@types/node@22.2.0)(typescript@5.5.4)': + dependencies: + '@oclif/core': 2.16.0(@types/node@22.2.0)(typescript@5.5.4) + chalk: 4.1.2 + fast-levenshtein: 3.0.0 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - typescript + '@octokit/auth-token@3.0.4': {} '@octokit/core@4.2.4(encoding@0.1.13)': @@ -21264,7 +21437,7 @@ snapshots: dns-over-http-resolver@1.2.3(node-fetch@2.7.0(encoding@0.1.13)): dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) native-fetch: 3.0.0(node-fetch@2.7.0(encoding@0.1.13)) receptacle: 1.3.2 transitivePeerDependencies: @@ -24005,7 +24178,7 @@ snapshots: any-signal: 2.1.2 blob-to-it: 1.0.4 browser-readablestream-to-it: 1.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) err-code: 3.0.1 ipfs-core-types: 0.9.0(node-fetch@2.7.0(encoding@0.1.13)) ipfs-unixfs: 6.0.9 @@ -24034,7 +24207,7 @@ snapshots: '@ipld/dag-pb': 2.1.18 abort-controller: 3.0.0 any-signal: 2.1.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.6(supports-color@8.1.1) err-code: 3.0.1 ipfs-core-types: 0.9.0(node-fetch@2.7.0(encoding@0.1.13)) ipfs-core-utils: 0.13.0(encoding@0.1.13)(node-fetch@2.7.0(encoding@0.1.13)) @@ -25889,6 +26062,10 @@ snapshots: marky@1.2.5: {} + matchstick-as@0.6.0: + dependencies: + wabt: 1.0.24 + md5.js@1.3.5: dependencies: hash-base: 3.1.0 @@ -30040,6 +30217,10 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + wabt@1.0.24: {} + + wabt@1.0.36: {} + wagmi@2.12.4(@tanstack/query-core@5.51.21)(@tanstack/react-query@5.51.21(react@18.2.0))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(immer@10.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.2.0)(utf-8-validate@5.0.10))(react@18.2.0)(rollup@4.20.0)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4): dependencies: '@tanstack/react-query': 5.51.21(react@18.2.0)