diff --git a/packages/subgraph/CHANGELOG.md b/packages/subgraph/CHANGELOG.md new file mode 100644 index 00000000..ee9f278f --- /dev/null +++ b/packages/subgraph/CHANGELOG.md @@ -0,0 +1,12 @@ +# Aragon OSx Subgraph + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.0.0 + +### Added + +- Copied files from [aragon/osx commit 19f4c4](https://github.com/aragon/osx/commit/19f4c42b5ae86b10b3a9cbbcaca6af504b69b0d2) diff --git a/packages/subgraph/manifest/data/arbitrum.json b/packages/subgraph/manifest/data/arbitrum.json index 0428ea2c..fcb806bb 100644 --- a/packages/subgraph/manifest/data/arbitrum.json +++ b/packages/subgraph/manifest/data/arbitrum.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "arbitrum-one", + "PluginRepo": { + "address": "0x7553E6Fb020c5740768cF289e603770AA09b7aE2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", "startBlock": 145462184 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/arbitrumGoerli.json b/packages/subgraph/manifest/data/arbitrumGoerli.json deleted file mode 100644 index ab3a4f37..00000000 --- a/packages/subgraph/manifest/data/arbitrumGoerli.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "arbitrum-goerli", - "dataSources": { - "PluginSetupProcessors": [ - { - "name": "PluginSetupProcessor", - "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", - "startBlock": 51930460 - } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } - } -} diff --git a/packages/subgraph/manifest/data/arbitrumSepolia.json b/packages/subgraph/manifest/data/arbitrumSepolia.json index a05a2973..86a08943 100644 --- a/packages/subgraph/manifest/data/arbitrumSepolia.json +++ b/packages/subgraph/manifest/data/arbitrumSepolia.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "arbitrum-sepolia", + "PluginRepo": { + "address": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", "startBlock": 2827173 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/baseGoerli.json b/packages/subgraph/manifest/data/baseGoerli.json deleted file mode 100644 index 63f49b34..00000000 --- a/packages/subgraph/manifest/data/baseGoerli.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "base-testnet", - "dataSources": { - "PluginSetupProcessors": [ - { - "name": "PluginSetupProcessor", - "address": "0x6095b5004c59301f8Bb98768Bd395d0bc637C893", - "startBlock": 7890981 - } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } - } -} diff --git a/packages/subgraph/manifest/data/baseMainnet.json b/packages/subgraph/manifest/data/baseMainnet.json index 833132c5..c3a8835d 100644 --- a/packages/subgraph/manifest/data/baseMainnet.json +++ b/packages/subgraph/manifest/data/baseMainnet.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "base", + "PluginRepo": { + "address": "0xcDC4b0BC63AEfFf3a7826A19D101406C6322A585" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x91a851E9Ed7F2c6d41b15F76e4a88f5A37067cC9", "startBlock": 2094737 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/baseSepolia.json b/packages/subgraph/manifest/data/baseSepolia.json index 474f7c3a..e1f711cd 100644 --- a/packages/subgraph/manifest/data/baseSepolia.json +++ b/packages/subgraph/manifest/data/baseSepolia.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "base-sepolia", + "PluginRepo": { + "address": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", "startBlock": 3654401 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/goerli.json b/packages/subgraph/manifest/data/goerli.json deleted file mode 100644 index 19a0dbb6..00000000 --- a/packages/subgraph/manifest/data/goerli.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "goerli", - "dataSources": { - "PluginSetupProcessors": [ - { - "name": "PluginSetupProcessor", - "address": "0xE8B5d8D66a02CD1b9Bd32a4064D7ABa45F51305e", - "startBlock": 8548226 - } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } - } -} diff --git a/packages/subgraph/manifest/data/localhost.json b/packages/subgraph/manifest/data/localhost.json index 0f778a14..8f071725 100644 --- a/packages/subgraph/manifest/data/localhost.json +++ b/packages/subgraph/manifest/data/localhost.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "rinkeby", + "network": "sepolia", + "PluginRepo": { + "address": null + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x2B8C4DD137104d1E869105cd0106e7D9EF955BfE", "startBlock": 7727664 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/mainnet.json b/packages/subgraph/manifest/data/mainnet.json index 81ffcdeb..529b8c55 100644 --- a/packages/subgraph/manifest/data/mainnet.json +++ b/packages/subgraph/manifest/data/mainnet.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "mainnet", + "PluginRepo": { + "address": "0x8c278e37D0817210E18A7958524b7D0a1fAA6F7b" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xE978942c691e43f65c1B7c7F8f1dc8cDF061B13f", "startBlock": 16721812 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/mumbai.json b/packages/subgraph/manifest/data/mumbai.json index a6e30681..592f9ba3 100644 --- a/packages/subgraph/manifest/data/mumbai.json +++ b/packages/subgraph/manifest/data/mumbai.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "mumbai", + "PluginRepo": { + "address": "0x2c4690b8be39adAd4F15A69340d5035aC6E53eEF" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x9227b311C5cecB416707F1C8B7Ca1b52649AabEc", "startBlock": 33514164 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/polygon.json b/packages/subgraph/manifest/data/polygon.json index c831362b..3bf8dc75 100644 --- a/packages/subgraph/manifest/data/polygon.json +++ b/packages/subgraph/manifest/data/polygon.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "matic", + "PluginRepo": { + "address": "0x5A5035E7E8aeff220540F383a9cf8c35929bcF31" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x879D9dfe3F36d7684BeC1a2bB4Aa8E8871A7245B", "startBlock": 40817440 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/sepolia.json b/packages/subgraph/manifest/data/sepolia.json index 87c67210..8f321ac4 100644 --- a/packages/subgraph/manifest/data/sepolia.json +++ b/packages/subgraph/manifest/data/sepolia.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "sepolia", + "PluginRepo": { + "address": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", "startBlock": 4415294 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/subgraph.placeholder.yaml b/packages/subgraph/manifest/subgraph.placeholder.yaml index 2de5289f..931951b1 100644 --- a/packages/subgraph/manifest/subgraph.placeholder.yaml +++ b/packages/subgraph/manifest/subgraph.placeholder.yaml @@ -19,9 +19,7 @@ dataSources: apiVersion: 0.0.5 language: wasm/assemblyscript entities: - - PluginInstallation - - PluginPreparation - - PluginPermission + - MultisigPlugin abis: - name: PluginSetupProcessor file: ./imported/PluginSetupProcessor.json @@ -32,7 +30,7 @@ dataSources: {{/dataSources.PluginSetupProcessors}} # templates templates: - # Plugin (package) + # Plugin - name: Plugin kind: ethereum/contract network: {{network}} @@ -43,12 +41,25 @@ templates: apiVersion: 0.0.5 language: wasm/assemblyscript entities: - - Dao + - Action + - MultisigPlugin + - MultisigApprover + - MultisigProposal + - MultisigProposalApprover abis: - name: Plugin - file: $PLUGIN_MODULE/contracts/artifacts/src/MyPlugin.sol/MyPlugin.json + file: $PLUGIN_MODULE/contracts/artifacts/src/Multisig.sol/Multisig.json eventHandlers: - - event: NumberStored(uint256) - handler: handleNumberStored + - event: MembersAdded(address[]) + handler: handleMembersAdded + - event: MembersRemoved(address[]) + handler: handleMembersRemoved + - event: Approved(indexed uint256,indexed address) + handler: handleApproved + - event: ProposalCreated(indexed uint256,indexed address,uint64,uint64,bytes,(address,uint256,bytes)[],uint256) + handler: handleProposalCreated + - event: ProposalExecuted(indexed uint256) + handler: handleProposalExecuted + - event: MultisigSettingsUpdated(bool,indexed uint16) + handler: handleMultisigSettingsUpdated file: ./src/plugin/plugin.ts - diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index bb175125..81f412cc 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -1,10 +1,86 @@ +interface IPlugin { + id: ID! # PluginEntityID + daoAddress: Bytes! + pluginAddress: Bytes! +} + # Types -type DaoPlugin @entity { - "OSX related data" - id: ID! # psp installationId - dao: Bytes! +type MultisigPlugin implements IPlugin @entity { + id: ID! # PluginEntityID + daoAddress: Bytes! pluginAddress: Bytes! - "Set plugin specific related data below:" - number: BigInt + # Plugin specific data" + proposalCount: BigInt + proposals: [MultisigProposal!]! @derivedFrom(field: "plugin") + members: [MultisigApprover!]! @derivedFrom(field: "plugin") + + minApprovals: Int + onlyListed: Boolean +} + +interface IProposal { + id: ID! # ProposalEntityId + daoAddress: Bytes! + creator: Bytes! + metadata: String + actions: [Action!]! @derivedFrom(field: "proposal") + allowFailureMap: BigInt! + failureMap: BigInt + executed: Boolean! + createdAt: BigInt! + startDate: BigInt! + endDate: BigInt! + executionTxHash: Bytes +} + +type Action @entity { + id: ID! # ActionEntityId + to: Bytes! + value: BigInt! + data: Bytes! + daoAddress: Bytes! + proposal: IProposal! + execResult: Bytes +} + +type MultisigApprover @entity { + id: ID! # plugin_address + member_address + address: String # address as string to facilitate filtering by address on the UI + proposals: [MultisigProposalApprover!]! @derivedFrom(field: "approver") + plugin: MultisigPlugin! +} + +type MultisigProposalApprover @entity(immutable: true) { + "ApproverProposal for Many-to-Many" + id: ID! # approver + proposal + approver: MultisigApprover! + proposal: MultisigProposal! + createdAt: BigInt! +} + +type MultisigProposal implements IProposal @entity { + id: ID! # plugin + proposalId + daoAddress: Bytes! + actions: [Action!]! @derivedFrom(field: "proposal") + allowFailureMap: BigInt! + failureMap: BigInt + plugin: MultisigPlugin! + pluginProposalId: BigInt! + creator: Bytes! + metadata: String + createdAt: BigInt! + startDate: BigInt! + endDate: BigInt! + creationBlockNumber: BigInt! + snapshotBlock: BigInt! + minApprovals: Int! + approvals: Int + approvalReached: Boolean! + isSignaling: Boolean! + executed: Boolean! + executionDate: BigInt + executionBlockNumber: BigInt + executionTxHash: Bytes + approvers: [MultisigProposalApprover!]! @derivedFrom(field: "proposal") } diff --git a/packages/subgraph/scripts/deploy-subgraph.sh b/packages/subgraph/scripts/deploy-subgraph.sh index 69c94067..3f7a9db3 100755 --- a/packages/subgraph/scripts/deploy-subgraph.sh +++ b/packages/subgraph/scripts/deploy-subgraph.sh @@ -41,7 +41,7 @@ echo '> Subgraph version: '$SUBGRAPH_VERSION # check if the repo address is null or zero address FILE=manifest/data/$SUBGRAPH_NETWORK_NAME'.json' -address=$(jq -r '.dataSources.Plugin.address' "$FILE") +address=$(jq -r '.PluginRepo.address' "$FILE") if [ "$address" = "null" ] || [ "$address" = "0x0000000000000000000000000000000000000000" ]; then diff --git a/packages/subgraph/scripts/import-plugin-repo.ts b/packages/subgraph/scripts/import-plugin-repo.ts index 59bad356..b24590fe 100644 --- a/packages/subgraph/scripts/import-plugin-repo.ts +++ b/packages/subgraph/scripts/import-plugin-repo.ts @@ -27,7 +27,7 @@ function extractAndWriteAddressToTS(): void { const networkManifestPath = path.join(manifestsPath, `${network}.json`); let networkRepoAddress = JSON.parse( fs.readFileSync(networkManifestPath, 'utf8') - ).dataSources.Plugin.address; + ).PluginRepo.address; // check if address is null and throw warning and continue with zero address if (!networkRepoAddress) { diff --git a/packages/subgraph/src/osx/pluginSetupProcessor.ts b/packages/subgraph/src/osx/pluginSetupProcessor.ts index ac0aabdf..07aa8319 100644 --- a/packages/subgraph/src/osx/pluginSetupProcessor.ts +++ b/packages/subgraph/src/osx/pluginSetupProcessor.ts @@ -1,9 +1,9 @@ import {InstallationPrepared} from '../../generated/PluginSetupProcessor/PluginSetupProcessor'; -import {DaoPlugin} from '../../generated/schema'; +import {MultisigPlugin} from '../../generated/schema'; import {Plugin as PluginTemplate} from '../../generated/templates'; import {PLUGIN_REPO_ADDRESS} from '../../imported/repo-address'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, DataSourceContext, log} from '@graphprotocol/graph-ts'; +import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; export function handleInstallationPrepared(event: InstallationPrepared): void { const pluginRepo = event.params.pluginSetupRepo; @@ -16,35 +16,27 @@ export function handleInstallationPrepared(event: InstallationPrepared): void { return; } - const dao = event.params.dao; - const plugin = event.params.plugin; - - // Generate a unique ID for the plugin installation. - const installationId = generatePluginInstallationEntityId(dao, plugin); - // Log an error and exit if unable to generate the installation ID. - if (!installationId) { - log.error('Failed to generate installationId', [ - dao.toHexString(), - plugin.toHexString(), - ]); - return; - } + const daoAddress = event.params.dao; + const pluginAddress = event.params.plugin; + const pluginId = generatePluginEntityId(pluginAddress); + // Load or create a new entry for the this plugin using the generated installation ID. - let pluginEntity = DaoPlugin.load(installationId!); + let pluginEntity = MultisigPlugin.load(pluginId); if (!pluginEntity) { - pluginEntity = new DaoPlugin(installationId!); + pluginEntity = new MultisigPlugin(pluginId); } // Set the DAO and plugin address for the plugin entity. - pluginEntity.dao = dao; - pluginEntity.pluginAddress = plugin; + pluginEntity.daoAddress = daoAddress; + pluginEntity.pluginAddress = pluginAddress; + pluginEntity.proposalCount = BigInt.zero(); // Initialize a context for the plugin data source to enable indexing from the moment of preparation. const context = new DataSourceContext(); // Include the DAO address in the context for future reference. - context.setString('daoAddress', dao.toHexString()); + context.setString('daoAddress', daoAddress.toHexString()); // Deploy a template for the plugin to facilitate individual contract indexing. - PluginTemplate.createWithContext(plugin, context); + PluginTemplate.createWithContext(pluginAddress, context); pluginEntity.save(); } diff --git a/packages/subgraph/src/plugin/id.ts b/packages/subgraph/src/plugin/id.ts new file mode 100644 index 00000000..6f22a779 --- /dev/null +++ b/packages/subgraph/src/plugin/id.ts @@ -0,0 +1,19 @@ +import {generateEntityIdFromAddress} from '@aragon/osx-commons-subgraph'; +import {Address} from '@graphprotocol/graph-ts'; + +export function generateMemberEntityId( + pluginAddress: Address, + memberAddress: Address +): string { + return [ + generateEntityIdFromAddress(pluginAddress), + generateEntityIdFromAddress(memberAddress), + ].join('_'); +} + +export function generateVoterEntityId( + memberEntityId: string, + proposalId: string +): string { + return [memberEntityId, proposalId].join('_'); +} diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index aa004d71..776bee4f 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -1,24 +1,206 @@ -import {DaoPlugin} from '../../generated/schema'; -import {NumberStored} from '../../generated/templates/Plugin/Plugin'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, dataSource} from '@graphprotocol/graph-ts'; +import { + Action, + MultisigPlugin, + MultisigProposal, + MultisigApprover, + MultisigProposalApprover, +} from '../../generated/schema'; +import { + ProposalCreated, + ProposalExecuted, + MembersAdded, + MembersRemoved, + Plugin, + Approved, + MultisigSettingsUpdated, +} from '../../generated/templates/Plugin/Plugin'; +import {generateMemberEntityId, generateVoterEntityId} from './id'; +import { + generateActionEntityId, + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, dataSource, store} from '@graphprotocol/graph-ts'; -export function handleNumberStored(event: NumberStored): void { +export function handleProposalCreated(event: ProposalCreated): void { + const pluginProposalId = event.params.proposalId; const pluginAddress = event.address; + const proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId + ); + const pluginEntityId = generatePluginEntityId(pluginAddress); + + const proposalEntity = new MultisigProposal(proposalEntityId); const context = dataSource.context(); - const daoId = context.getString('daoAddress'); + const daoAddr = Address.fromHexString(context.getString('daoAddress')); - const installationId = generatePluginInstallationEntityId( - Address.fromString(daoId), - pluginAddress - ); + proposalEntity.daoAddress = daoAddr; + proposalEntity.plugin = pluginEntityId; + proposalEntity.pluginProposalId = pluginProposalId; + proposalEntity.creator = event.params.creator; + proposalEntity.metadata = event.params.metadata.toString(); + proposalEntity.createdAt = event.block.timestamp; + proposalEntity.creationBlockNumber = event.block.number; + proposalEntity.startDate = event.params.startDate; + proposalEntity.endDate = event.params.endDate; + proposalEntity.allowFailureMap = event.params.allowFailureMap; + + const contract = Plugin.bind(pluginAddress); + + const proposal = contract.try_getProposal(pluginProposalId); + if (!proposal.reverted) { + proposalEntity.executed = proposal.value.value0; + proposalEntity.approvals = proposal.value.value1; + + // ProposalParameters + const parameters = proposal.value.value2; + proposalEntity.minApprovals = parameters.minApprovals; + proposalEntity.snapshotBlock = parameters.snapshotBlock; + proposalEntity.approvalReached = false; + + // Actions + const actions = proposal.value.value3; + for (let index = 0; index < actions.length; index++) { + const action = actions[index]; + + const actionId = generateActionEntityId(proposalEntityId, index); + + const actionEntity = new Action(actionId); + actionEntity.to = action.to; + actionEntity.value = action.value; + actionEntity.data = action.data; + actionEntity.daoAddress = daoAddr; + actionEntity.proposal = proposalEntityId; + actionEntity.save(); + } + proposalEntity.isSignaling = actions.length == 0; + } + + proposalEntity.save(); - if (installationId) { - const pluginEntity = DaoPlugin.load(installationId); - if (pluginEntity) { - pluginEntity.number = event.params.number; + // update vote length + const pluginEntity = MultisigPlugin.load(pluginEntityId); + if (pluginEntity) { + const voteLength = contract.try_proposalCount(); + if (!voteLength.reverted) { + pluginEntity.proposalCount = voteLength.value; pluginEntity.save(); } } } + +export function handleApproved(event: Approved): void { + const memberAddress = event.params.approver; + const pluginAddress = event.address; + const memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); + const pluginProposalId = event.params.proposalId; + const proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); + const approverProposalId = generateVoterEntityId( + memberEntityId, + proposalEntityId + ); + + let approverProposalEntity = + MultisigProposalApprover.load(approverProposalId); + if (!approverProposalEntity) { + approverProposalEntity = new MultisigProposalApprover(approverProposalId); + approverProposalEntity.approver = memberEntityId; + approverProposalEntity.proposal = proposalEntityId; + } + approverProposalEntity.createdAt = event.block.timestamp; + approverProposalEntity.save(); + + // update count + const proposalEntity = MultisigProposal.load(proposalEntityId); + if (proposalEntity) { + const contract = Plugin.bind(pluginAddress); + const proposal = contract.try_getProposal(pluginProposalId); + + if (!proposal.reverted) { + const approvals = proposal.value.value1; + proposalEntity.approvals = approvals; + + // calculate if proposal is executable + const minApprovalsStruct = proposal.value.value2; + + if ( + approvals >= minApprovalsStruct.minApprovals && + !proposalEntity.approvalReached + ) { + proposalEntity.approvalReached = true; + } + + proposalEntity.save(); + } + } +} + +export function handleProposalExecuted(event: ProposalExecuted): void { + const pluginProposalId = event.params.proposalId; + const proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); + + const proposalEntity = MultisigProposal.load(proposalEntityId); + if (proposalEntity) { + proposalEntity.approvalReached = false; + proposalEntity.executed = true; + proposalEntity.executionDate = event.block.timestamp; + proposalEntity.executionBlockNumber = event.block.number; + proposalEntity.executionTxHash = event.transaction.hash; + proposalEntity.save(); + } +} + +export function handleMembersAdded(event: MembersAdded): void { + const memberAddresses = event.params.members; + for (let index = 0; index < memberAddresses.length; index++) { + const pluginEntityId = generatePluginEntityId(event.address); + const memberEntityId = generateMemberEntityId( + event.address, + memberAddresses[index] + ); + + let approverEntity = MultisigApprover.load(memberEntityId); + if (!approverEntity) { + approverEntity = new MultisigApprover(memberEntityId); + approverEntity.address = memberAddresses[index].toHexString(); + approverEntity.plugin = pluginEntityId; + approverEntity.save(); + } + } +} + +export function handleMembersRemoved(event: MembersRemoved): void { + const memberAddresses = event.params.members; + for (let index = 0; index < memberAddresses.length; index++) { + const memberEntityId = generateMemberEntityId( + event.address, + memberAddresses[index] + ); + + const approverEntity = MultisigApprover.load(memberEntityId); + if (approverEntity) { + store.remove('MultisigApprover', memberEntityId); + } + } +} + +export function handleMultisigSettingsUpdated( + event: MultisigSettingsUpdated +): void { + const pluginEntity = MultisigPlugin.load( + generatePluginEntityId(event.address) + ); + if (pluginEntity) { + pluginEntity.onlyListed = event.params.onlyListed; + pluginEntity.minApprovals = event.params.minApprovals; + pluginEntity.save(); + } +} diff --git a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts index 920203aa..4a69b394 100644 --- a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts +++ b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts @@ -12,7 +12,7 @@ import { PLUGIN_SETUP_ID, } from '../utils/constants'; import {createInstallationPreparedEvent} from '../utils/events'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; +import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; import {assert, afterEach, clearStore, test, describe} from 'matchstick-as'; @@ -27,12 +27,11 @@ describe('OSx', () => { // Create event const daoAddress = DAO_ADDRESS; const pluginAddress = CONTRACT_ADDRESS; - const installationId = generatePluginInstallationEntityId( - Address.fromString(daoAddress), + const pluginEntityId = generatePluginEntityId( Address.fromString(pluginAddress) ); - if (!installationId) { - throw new Error('Failed to get installationId'); + if (!pluginEntityId) { + throw new Error('Failed to get pluginEntityId'); } const setupId = PLUGIN_SETUP_ID; const versionTuple = new ethereum.Tuple(); @@ -76,10 +75,13 @@ describe('OSx', () => { handleInstallationPrepared(event1); - assert.notInStore('DaoPlugin', installationId!); - assert.entityCount('DaoPlugin', 0); + assert.notInStore('MultisigPlugin', pluginEntityId!); + assert.entityCount('MultisigPlugin', 0); const thisPluginRepoAddress = PLUGIN_REPO_ADDRESS; + const pluginId = generatePluginEntityId( + Address.fromString(pluginAddress) + ); const event2 = createInstallationPreparedEvent( ADDRESS_THREE, @@ -95,8 +97,8 @@ describe('OSx', () => { handleInstallationPrepared(event2); - assert.entityCount('DaoPlugin', 1); - assert.fieldEquals('DaoPlugin', installationId!, 'id', installationId!); + assert.entityCount('MultisigPlugin', 1); + assert.fieldEquals('MultisigPlugin', pluginId, 'id', pluginId); }); }); }); diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 14db4cc4..25ab5e7d 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -1,18 +1,69 @@ -import {DaoPlugin} from '../../generated/schema'; -import {handleNumberStored} from '../../src/plugin/plugin'; -import {CONTRACT_ADDRESS, DAO_ADDRESS} from '../utils/constants'; -import {createNewNumberStoredEvent} from '../utils/events'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, DataSourceContext} from '@graphprotocol/graph-ts'; +import {MultisigApprover} from '../../generated/schema'; +import { + generateMemberEntityId, + generateVoterEntityId, +} from '../../src/plugin/id'; +import { + handleMembersAdded, + handleApproved, + handleProposalExecuted, + handleMembersRemoved, + handleProposalCreated, + handleMultisigSettingsUpdated, +} from '../../src/plugin/plugin'; +import { + ADDRESS_ONE, + ADDRESS_TWO, + ADDRESS_THREE, + CONTRACT_ADDRESS, + DAO_ADDRESS, +} from '../utils/constants'; +import { + createNewMembersAddedEvent, + createNewApprovedEvent, + createNewProposalExecutedEvent, + createNewMembersRemovedEvent, + createNewProposalCreatedEvent, + getProposalCountCall, + createMultisigProposalEntityState, + createGetProposalCall, + createNewMultisigSettingsUpdatedEvent, + createMultisigPluginState, + PLUGIN_PROPOSAL_ID, + SNAPSHOT_BLOCK, + ONE, + TWO, + START_DATE, + END_DATE, + ALLOW_FAILURE_MAP, +} from '../utils/events'; +import { + generatePluginEntityId, + generateProposalEntityId, + createDummyAction, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; import { - assert, afterEach, + assert, beforeEach, clearStore, - test, - describe, dataSourceMock, -} from 'matchstick-as'; + describe, + test, +} from 'matchstick-as/assembly/index'; + +let actions = [createDummyAction(ADDRESS_THREE, '0', '0x00000000')]; + +const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginEntityId = generatePluginEntityId(pluginAddress); +const pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID); +const proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId +); + +export const METADATA = 'Some String Data ...'; describe('Plugin', () => { beforeEach(function () { @@ -25,36 +76,459 @@ describe('Plugin', () => { clearStore(); }); - describe('NumberStored Event', () => { - test('it should store the correct number emitted from the event', () => { - const daoAddress = Address.fromString(DAO_ADDRESS); - const pluginAddress = Address.fromString(CONTRACT_ADDRESS); + describe('handleProposalCreated', () => { + test('handles the event', () => { + // create state + createMultisigPluginState(); + + // create calls + getProposalCountCall(CONTRACT_ADDRESS, '1'); + createGetProposalCall( + CONTRACT_ADDRESS, + PLUGIN_PROPOSAL_ID, + false, + + // ProposalParameters + START_DATE, + END_DATE, + ONE, + SNAPSHOT_BLOCK, + + // approvals + ONE, + + actions, + + ALLOW_FAILURE_MAP + ); + + // create event + let event = createNewProposalCreatedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_ONE, + START_DATE, + END_DATE, + METADATA, + actions, + ALLOW_FAILURE_MAP, + CONTRACT_ADDRESS + ); + + // handle event + handleProposalCreated(event); + + // checks + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'id', + proposalEntityId + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'daoAddress', + DAO_ADDRESS + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'plugin', + pluginEntityId + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'pluginProposalId', + PLUGIN_PROPOSAL_ID + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'creator', + ADDRESS_ONE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'startDate', + START_DATE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'endDate', + END_DATE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'metadata', + METADATA + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'createdAt', + event.block.timestamp.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'creationBlockNumber', + event.block.number.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'snapshotBlock', + SNAPSHOT_BLOCK + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'minApprovals', + ONE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'approvals', + ONE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executed', + 'false' + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'allowFailureMap', + ALLOW_FAILURE_MAP + ); + + // check MultisigPlugin + assert.fieldEquals( + 'MultisigPlugin', + Address.fromString(CONTRACT_ADDRESS).toHexString(), + 'proposalCount', + '1' + ); + }); + }); + + describe('handleApproved', () => { + test('handles the event', () => { + // create state + let proposal = createMultisigProposalEntityState( + proposalEntityId, + DAO_ADDRESS, + CONTRACT_ADDRESS, + ADDRESS_ONE + ); + + // create calls + createGetProposalCall( + CONTRACT_ADDRESS, + PLUGIN_PROPOSAL_ID, + false, + + // ProposalParameters + START_DATE, + END_DATE, + TWO, // minApprovals + SNAPSHOT_BLOCK, + + // approvals + ONE, + + actions, + ALLOW_FAILURE_MAP + ); + + // create event + let event = createNewApprovedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_ONE, + CONTRACT_ADDRESS + ); + + handleApproved(event); + + // checks + const memberAddress = Address.fromString(ADDRESS_ONE); + + const memberEntityId = generateMemberEntityId( + pluginAddress, + memberAddress + ); + + const voterEntityId = generateVoterEntityId(memberEntityId, proposal.id); + // check proposalVoter + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'id', + voterEntityId + ); + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'approver', + memberEntityId + ); + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'proposal', + proposal.id + ); + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'createdAt', + event.block.timestamp.toString() + ); + + // check proposal + assert.fieldEquals('MultisigProposal', proposal.id, 'approvals', ONE); + assert.fieldEquals( + 'MultisigProposal', + proposal.id, + 'approvalReached', + 'false' + ); + + // create 2nd approve, to test approvals + // create calls + createGetProposalCall( + CONTRACT_ADDRESS, + PLUGIN_PROPOSAL_ID, + false, + + // ProposalParameters + START_DATE, + END_DATE, + TWO, // minApprovals + SNAPSHOT_BLOCK, + + // approvals + TWO, + + actions, + ALLOW_FAILURE_MAP + ); + + // create event + let event2 = createNewApprovedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_TWO, + CONTRACT_ADDRESS + ); + + handleApproved(event2); + + // Check + assert.fieldEquals('MultisigProposal', proposal.id, 'approvals', TWO); + assert.fieldEquals( + 'MultisigProposal', + proposal.id, + 'approvalReached', + 'true' + ); + }); + }); + + describe('handleProposalExecuted', () => { + test('handles the event', () => { + // create state + createMultisigProposalEntityState( + proposalEntityId, + DAO_ADDRESS, + CONTRACT_ADDRESS, + ADDRESS_ONE + ); + + // create event + let event = createNewProposalExecutedEvent('0', CONTRACT_ADDRESS); + + // handle event + handleProposalExecuted(event); - const installationId = generatePluginInstallationEntityId( - daoAddress, - pluginAddress + // checks + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'id', + proposalEntityId ); - if (!installationId) { - throw new Error('Failed to get installationId'); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executed', + 'true' + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executionDate', + event.block.timestamp.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executionBlockNumber', + event.block.number.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executionTxHash', + event.transaction.hash.toHexString() + ); + }); + }); + + describe('handleMembersAdded', () => { + test('handles the event', () => { + let userArray = [ + Address.fromString(ADDRESS_ONE), + Address.fromString(ADDRESS_TWO), + ]; + + // create event + let event = createNewMembersAddedEvent(userArray, CONTRACT_ADDRESS); + + // handle event + handleMembersAdded(event); + + // checks + let memberId = + Address.fromString(CONTRACT_ADDRESS).toHexString() + + '_' + + userArray[0].toHexString(); + + assert.fieldEquals('MultisigApprover', memberId, 'id', memberId); + assert.fieldEquals( + 'MultisigApprover', + memberId, + 'address', + userArray[0].toHexString() + ); + assert.fieldEquals( + 'MultisigApprover', + memberId, + 'plugin', + Address.fromString(CONTRACT_ADDRESS).toHexString() + ); + }); + }); + + describe('handleMembersRemoved', () => { + test('handles the event', () => { + // create state + let memberAddresses = [ + Address.fromString(ADDRESS_ONE), + Address.fromString(ADDRESS_TWO), + ]; + + for (let index = 0; index < memberAddresses.length; index++) { + const user = memberAddresses[index].toHexString(); + const pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); + let memberId = pluginId + '_' + user; + let userEntity = new MultisigApprover(memberId); + userEntity.plugin = Address.fromString(CONTRACT_ADDRESS).toHexString(); + userEntity.save(); } - // Create state - let daoPlugin = new DaoPlugin(installationId!); - daoPlugin.dao = daoAddress; - daoPlugin.pluginAddress = pluginAddress; - daoPlugin.save(); - const number = '5'; + // checks + let memberId1 = + Address.fromString(CONTRACT_ADDRESS).toHexString() + + '_' + + memberAddresses[0].toHexString(); + let memberId2 = + Address.fromString(CONTRACT_ADDRESS).toHexString() + + '_' + + memberAddresses[1].toHexString(); + + assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); + assert.fieldEquals('MultisigApprover', memberId2, 'id', memberId2); + + // create event + let event = createNewMembersRemovedEvent( + [memberAddresses[1]], + CONTRACT_ADDRESS + ); + + // handle event + handleMembersRemoved(event); - const event = createNewNumberStoredEvent( - number, - pluginAddress.toHexString() + // checks + assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); + assert.notInStore('MultisigApprover', memberId2); + }); + }); + + describe('handleMultisigSettingsUpdated', () => { + test('handles the event', () => { + // create state + let entityID = createMultisigPluginState().id; + + // create event + let onlyListed = true; + let minApproval = '5'; + + let event = createNewMultisigSettingsUpdatedEvent( + onlyListed, + minApproval, + CONTRACT_ADDRESS + ); + + // handle event + handleMultisigSettingsUpdated(event); + + // checks + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'onlyListed', + `${onlyListed}` + ); + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'minApprovals', + minApproval ); - handleNumberStored(event); + // create event + onlyListed = false; + minApproval = '4'; + + event = createNewMultisigSettingsUpdatedEvent( + onlyListed, + minApproval, + CONTRACT_ADDRESS + ); - assert.fieldEquals('DaoPlugin', installationId!, 'id', installationId!); - assert.fieldEquals('DaoPlugin', installationId!, 'number', number); - assert.entityCount('DaoPlugin', 1); + // handle event + handleMultisigSettingsUpdated(event); + + // checks + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'onlyListed', + `${onlyListed}` + ); + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'minApprovals', + minApproval + ); }); }); }); diff --git a/packages/subgraph/tests/utils/events/plugin.ts b/packages/subgraph/tests/utils/events/plugin.ts index 58df89f7..620798df 100644 --- a/packages/subgraph/tests/utils/events/plugin.ts +++ b/packages/subgraph/tests/utils/events/plugin.ts @@ -1,22 +1,332 @@ -import {NumberStored} from '../../../generated/templates/Plugin/Plugin'; -import {Address, BigInt, ethereum} from '@graphprotocol/graph-ts'; -import {newMockEvent} from 'matchstick-as'; +import {MultisigPlugin, MultisigProposal} from '../../../generated/schema'; +import { + ProposalCreated, + Approved, + ProposalExecuted, + MembersAdded, + MembersRemoved, + MultisigSettingsUpdated, +} from '../../../generated/templates/Plugin/Plugin'; +import {ADDRESS_ONE, DAO_ADDRESS, CONTRACT_ADDRESS} from '../constants'; +import { + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {createMockedFunction, newMockEvent} from 'matchstick-as'; -export function createNewNumberStoredEvent( - number: string, +// test data + +export const ZERO = '0'; +export const ONE = '1'; +export const TWO = '2'; +export const THREE = '3'; + +export const PLUGIN_PROPOSAL_ID = ZERO; +export const MIN_PROPOSER_VOTING_POWER = ZERO; +export const START_DATE = '1644851000'; +export const END_DATE = '1644852000'; +export const CREATED_AT = ONE; +export const SNAPSHOT_BLOCK = '100'; +export const ALLOW_FAILURE_MAP = '1'; + +// events + +export function createNewProposalCreatedEvent( + proposalId: string, + creator: string, + startDate: string, + endDate: string, + metadata: string, + actions: ethereum.Tuple[], + allowFailureMap: string, + contractAddress: string +): ProposalCreated { + let createProposalCreatedEvent = changetype(newMockEvent()); + + createProposalCreatedEvent.address = Address.fromString(contractAddress); + createProposalCreatedEvent.parameters = []; + + let proposalIdParam = new ethereum.EventParam( + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) + ); + let creatorParam = new ethereum.EventParam( + 'creator', + ethereum.Value.fromAddress(Address.fromString(creator)) + ); + let startDateParam = new ethereum.EventParam( + 'startDate', + ethereum.Value.fromSignedBigInt(BigInt.fromString(startDate)) + ); + let endDateParam = new ethereum.EventParam( + 'endDate', + ethereum.Value.fromSignedBigInt(BigInt.fromString(endDate)) + ); + let metadataParam = new ethereum.EventParam( + 'metadata', + ethereum.Value.fromBytes(Bytes.fromUTF8(metadata)) + ); + let actionsParam = new ethereum.EventParam( + 'actions', + ethereum.Value.fromTupleArray(actions) + ); + let allowFailureMapParam = new ethereum.EventParam( + 'allowFailureMap', + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)) + ); + + createProposalCreatedEvent.parameters.push(proposalIdParam); + createProposalCreatedEvent.parameters.push(creatorParam); + createProposalCreatedEvent.parameters.push(startDateParam); + createProposalCreatedEvent.parameters.push(endDateParam); + createProposalCreatedEvent.parameters.push(metadataParam); + createProposalCreatedEvent.parameters.push(actionsParam); + createProposalCreatedEvent.parameters.push(allowFailureMapParam); + + return createProposalCreatedEvent; +} + +export function createNewApprovedEvent( + proposalId: string, + approver: string, contractAddress: string -): NumberStored { - let createNumberStoredEvent = changetype(newMockEvent()); +): Approved { + let createApprovedEvent = changetype(newMockEvent()); - createNumberStoredEvent.address = Address.fromString(contractAddress); - createNumberStoredEvent.parameters = []; + createApprovedEvent.address = Address.fromString(contractAddress); + createApprovedEvent.parameters = []; let proposalIdParam = new ethereum.EventParam( - 'number', - ethereum.Value.fromSignedBigInt(BigInt.fromString(number)) + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) + ); + let approverParam = new ethereum.EventParam( + 'approver', + ethereum.Value.fromAddress(Address.fromString(approver)) ); - createNumberStoredEvent.parameters.push(proposalIdParam); + createApprovedEvent.parameters.push(proposalIdParam); + createApprovedEvent.parameters.push(approverParam); + + return createApprovedEvent; +} + +export function createNewProposalExecutedEvent( + proposalId: string, + contractAddress: string +): ProposalExecuted { + let createProposalExecutedEvent = changetype( + newMockEvent() + ); + + createProposalExecutedEvent.address = Address.fromString(contractAddress); + createProposalExecutedEvent.parameters = []; + + let proposalIdParam = new ethereum.EventParam( + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) + ); + + createProposalExecutedEvent.parameters.push(proposalIdParam); + + return createProposalExecutedEvent; +} + +export function createNewMembersAddedEvent( + addresses: Address[], + contractAddress: string +): MembersAdded { + let newMembersAddedEvent = changetype(newMockEvent()); + + newMembersAddedEvent.address = Address.fromString(contractAddress); + newMembersAddedEvent.parameters = []; + + let usersParam = new ethereum.EventParam( + 'users', + ethereum.Value.fromAddressArray(addresses) + ); + + newMembersAddedEvent.parameters.push(usersParam); + + return newMembersAddedEvent; +} + +export function createNewMembersRemovedEvent( + addresses: Address[], + contractAddress: string +): MembersRemoved { + let newMembersRemovedEvent = changetype(newMockEvent()); + + newMembersRemovedEvent.address = Address.fromString(contractAddress); + newMembersRemovedEvent.parameters = []; + + let usersParam = new ethereum.EventParam( + 'users', + ethereum.Value.fromAddressArray(addresses) + ); + + newMembersRemovedEvent.parameters.push(usersParam); + + return newMembersRemovedEvent; +} + +export function createNewMultisigSettingsUpdatedEvent( + onlyListed: boolean, + minApprovals: string, + contractAddress: string +): MultisigSettingsUpdated { + let newProposalSettingsUpdatedEvent = changetype( + newMockEvent() + ); + + newProposalSettingsUpdatedEvent.address = Address.fromString(contractAddress); + newProposalSettingsUpdatedEvent.parameters = []; + + let onlyListedParam = new ethereum.EventParam( + 'onlyListed', + ethereum.Value.fromBoolean(onlyListed) + ); + + let minApprovalsParam = new ethereum.EventParam( + 'minApprovals', + ethereum.Value.fromSignedBigInt(BigInt.fromString(minApprovals)) + ); + + newProposalSettingsUpdatedEvent.parameters.push(onlyListedParam); + newProposalSettingsUpdatedEvent.parameters.push(minApprovalsParam); + + return newProposalSettingsUpdatedEvent; +} + +// calls + +export function getProposalCountCall( + contractAddress: string, + returns: string +): void { + createMockedFunction( + Address.fromString(contractAddress), + 'proposalCount', + 'proposalCount():(uint256)' + ) + .withArgs([]) + .returns([ethereum.Value.fromSignedBigInt(BigInt.fromString(returns))]); +} + +export function createGetProposalCall( + contractAddress: string, + proposalId: string, + executed: boolean, + + startDate: string, + endDate: string, + minApprovals: string, + snapshotBlock: string, + + approvals: string, + + actions: ethereum.Tuple[], + allowFailureMap: string +): void { + let parameters = new ethereum.Tuple(); + + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(minApprovals)) + ); + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(snapshotBlock)) + ); + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(startDate)) + ); + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(endDate)) + ); + + createMockedFunction( + Address.fromString(contractAddress), + 'getProposal', + 'getProposal(uint256):(bool,uint16,(uint16,uint64,uint64,uint64),(address,uint256,bytes)[],uint256)' + ) + .withArgs([ + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(proposalId)), + ]) + .returns([ + ethereum.Value.fromBoolean(executed), + + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(approvals)), + + // ProposalParameters + ethereum.Value.fromTuple(parameters), + + ethereum.Value.fromTupleArray(actions), + + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)), + ]); +} + +// state + +export function createMultisigPluginState( + entityID: string = generatePluginEntityId( + Address.fromString(CONTRACT_ADDRESS) + ), + dao: string = DAO_ADDRESS, + pluginAddress: string = CONTRACT_ADDRESS, + proposalCount: string = ZERO, + minApprovals: string = THREE, + onlyListed: boolean = false +): MultisigPlugin { + let multisigPlugin = new MultisigPlugin(entityID); + multisigPlugin.daoAddress = Bytes.fromHexString(dao); + multisigPlugin.pluginAddress = Bytes.fromHexString(pluginAddress); + multisigPlugin.proposalCount = BigInt.fromString(proposalCount); + multisigPlugin.minApprovals = parseInt(minApprovals) as i32; + multisigPlugin.onlyListed = onlyListed; + multisigPlugin.save(); + + return multisigPlugin; +} + +export function createMultisigProposalEntityState( + entityID: string = generateProposalEntityId( + Address.fromString(CONTRACT_ADDRESS), + BigInt.fromString(PLUGIN_PROPOSAL_ID) + ), + dao: string = DAO_ADDRESS, + plugin: string = CONTRACT_ADDRESS, + creator: string = ADDRESS_ONE, + pluginProposalId: string = PLUGIN_PROPOSAL_ID, + minApprovals: string = TWO, + startDate: string = START_DATE, + endDate: string = END_DATE, + executable: boolean = false, + executed: boolean = false, + allowFailureMap: string = ALLOW_FAILURE_MAP, + + snapshotBlock: string = SNAPSHOT_BLOCK, + + createdAt: string = CREATED_AT, + creationBlockNumber: BigInt = new BigInt(0) +): MultisigProposal { + let multisigProposal = new MultisigProposal(entityID); + multisigProposal.daoAddress = Address.fromHexString(dao); + multisigProposal.plugin = Address.fromString(plugin).toHexString(); + multisigProposal.pluginProposalId = BigInt.fromString(pluginProposalId); + multisigProposal.creator = Address.fromString(creator); + multisigProposal.startDate = BigInt.fromString(startDate); + multisigProposal.endDate = BigInt.fromString(endDate); + multisigProposal.approvalReached = executable; + multisigProposal.isSignaling = false; + multisigProposal.executed = executed; + multisigProposal.snapshotBlock = BigInt.fromString(snapshotBlock); + multisigProposal.minApprovals = BigInt.fromString(minApprovals).toI32(); + multisigProposal.allowFailureMap = BigInt.fromString(allowFailureMap); + multisigProposal.createdAt = BigInt.fromString(createdAt); + multisigProposal.creationBlockNumber = creationBlockNumber; + + multisigProposal.save(); - return createNumberStoredEvent; + return multisigProposal; } diff --git a/packages/subgraph/tsconfig.json b/packages/subgraph/tsconfig.json index 192bbfb5..c3f13e2e 100644 --- a/packages/subgraph/tsconfig.json +++ b/packages/subgraph/tsconfig.json @@ -24,6 +24,7 @@ "tests/**/*", "utils/**/*", "plugin-settings.ts", - "commons/**/*" + "commons/**/*", + "types/**/*" ] } diff --git a/packages/subgraph/types/ASM.d.ts b/packages/subgraph/types/ASM.d.ts new file mode 100644 index 00000000..279fcb44 --- /dev/null +++ b/packages/subgraph/types/ASM.d.ts @@ -0,0 +1,14 @@ +/// AssemblyScript types to remove false errors from the compiler +interface ethereum { + event: import('@graphprotocol/graph-ts/chain/ethereum').ethereum.Event; + value: import('@graphprotocol/graph-ts/chain/ethereum').ethereum.Value; +} + +// type casting through generics is a bit tricky so just add overloads here as you need them +declare function changetype(input: ethereum['event']): T & ethereum['event']; +declare function changetype( + input: ethereum['value'][] +): T & ethereum['value']; + +declare type i32 = number; +declare type bool = boolean;