From 72938d796a652de502678ac2ef6276b82b9f0975 Mon Sep 17 00:00:00 2001 From: josemarinas <36479864+josemarinas@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:16:11 +0100 Subject: [PATCH 1/2] Feature: Add is member function (#299) * add is member function * update changelog * fix lint --- modules/client-common/src/schemas.ts | 6 ++ modules/client-common/src/types.ts | 5 ++ modules/client/CHANGELOG.md | 8 ++ .../02-multisig-client/15-is-member.ts | 31 +++++++ .../03-tokenVoting-client/22-is-member.ts | 31 +++++++ .../15-is-member.ts | 31 +++++++ .../internal/client/methods.ts | 28 ++++++ .../internal/graphql-queries/members.ts | 10 +++ .../addresslistVoting/internal/interfaces.ts | 2 + .../src/multisig/internal/client/methods.ts | 30 ++++++- .../internal/graphql-queries/members.ts | 10 +++ .../src/multisig/internal/interfaces.ts | 2 + .../tokenVoting/internal/client/methods.ts | 27 ++++++ .../internal/graphql-queries/members.ts | 10 +++ .../src/tokenVoting/internal/interfaces.ts | 2 + .../addresslistVoting-client/methods.test.ts | 84 +++++++++++++++++- .../multisig-client/methods.test.ts | 86 ++++++++++++++++++- .../tokenVoting-client/methods.test.ts | 82 +++++++++++++++++- yarn.lock | 7 ++ 19 files changed, 487 insertions(+), 5 deletions(-) create mode 100644 modules/client/examples/02-multisig-client/15-is-member.ts create mode 100644 modules/client/examples/03-tokenVoting-client/22-is-member.ts create mode 100644 modules/client/examples/04-addresslistVoting-client/15-is-member.ts diff --git a/modules/client-common/src/schemas.ts b/modules/client-common/src/schemas.ts index 8b50d009d..589202a7b 100644 --- a/modules/client-common/src/schemas.ts +++ b/modules/client-common/src/schemas.ts @@ -98,3 +98,9 @@ export const ApplyUninstallationSchema = object({ export const ApplyInstallationSchema = ApplyUninstallationSchema.concat(object({ helpers: array(AddressOrEnsSchema).required(), })); + +export const IsMemberSchema = object({ + address: AddressOrEnsSchema.required(), + pluginAddress: AddressOrEnsSchema.required(), + blockNumber: number().notRequired(), +}); diff --git a/modules/client-common/src/types.ts b/modules/client-common/src/types.ts index 0e086afd9..b47ba6764 100644 --- a/modules/client-common/src/types.ts +++ b/modules/client-common/src/types.ts @@ -278,3 +278,8 @@ export enum SupportedVersion { V1_3_0 = "1.3.0", LATEST = "1.3.0", } +export type IsMemberParams = { + address: string; + pluginAddress: string; + blockNumber?: number; +}; diff --git a/modules/client/CHANGELOG.md b/modules/client/CHANGELOG.md index 0a4767c09..a39c9d8ff 100644 --- a/modules/client/CHANGELOG.md +++ b/modules/client/CHANGELOG.md @@ -18,6 +18,14 @@ TEMPLATE: --> ## [UPCOMING] + +### Added + +- `isMember` function to `TokenVotingClient` +- `isMember` function to `AddresslistVotingClient` +- `isMember` function to `Client` + +## [1.18.2] ### Fixed - Plugin preparations query diff --git a/modules/client/examples/02-multisig-client/15-is-member.ts b/modules/client/examples/02-multisig-client/15-is-member.ts new file mode 100644 index 000000000..5c58ef293 --- /dev/null +++ b/modules/client/examples/02-multisig-client/15-is-member.ts @@ -0,0 +1,31 @@ +/* MARKDOWN +--- +title: Is Member +--- + +### Check if an address is a member of Multisig DAO in a specific block and plugin + +The is member function receives the plugin address and the address to check as parameters and returns a boolean value. + +*/ + +import { MultisigClient } from "@aragon/sdk-client"; +import { context } from "../index"; + +// Instantiate the multisig client from the Aragon OSx SDK context. +const client: MultisigClient = new MultisigClient(context); + +const isMember = await client.methods.isMember({ + pluginAddress: "0x2345678901234567890123456789012345678901", + address: "0x1234567890123456789012345678901234567890", + blockNumber: 12345678, +}); + +console.log(isMember); + +/* MARKDOWN + Returns: + ```tsx + true + ``` + */ diff --git a/modules/client/examples/03-tokenVoting-client/22-is-member.ts b/modules/client/examples/03-tokenVoting-client/22-is-member.ts new file mode 100644 index 000000000..bfa1b3132 --- /dev/null +++ b/modules/client/examples/03-tokenVoting-client/22-is-member.ts @@ -0,0 +1,31 @@ +/* MARKDOWN +--- +title: Is Member +--- + +### Check if an address is a member of TokenVoting DAO in a specific block and plugin + +The is member function receives the plugin address and the address to check as parameters and returns a boolean value. + +*/ + +import { TokenVotingClient } from "@aragon/sdk-client"; +import { context } from "../index"; + +// Instantiate the token voting client from the Aragon OSx SDK context. +const client: TokenVotingClient = new TokenVotingClient(context); + +const isMember = await client.methods.isMember({ + pluginAddress: "0x2345678901234567890123456789012345678901", + address: "0x1234567890123456789012345678901234567890", + blockNumber: 12345678, +}); + +console.log(isMember); + +/* MARKDOWN + Returns: + ```tsx + true + ``` + */ diff --git a/modules/client/examples/04-addresslistVoting-client/15-is-member.ts b/modules/client/examples/04-addresslistVoting-client/15-is-member.ts new file mode 100644 index 000000000..bfd72fb02 --- /dev/null +++ b/modules/client/examples/04-addresslistVoting-client/15-is-member.ts @@ -0,0 +1,31 @@ +/* MARKDOWN +--- +title: Is Member +--- + +### Check if an address is a member of an Addresslist DAO in a specific block and plugin + +The is member function receives the plugin address and the address to check as parameters and returns a boolean value. + +*/ + +import { AddresslistVotingClient } from "@aragon/sdk-client"; +import { context } from "../index"; + +// Instantiate the addresslist voting client from the Aragon OSx SDK context. +const client: AddresslistVotingClient = new AddresslistVotingClient(context); + +const isMember = await client.methods.isMember({ + pluginAddress: "0x2345678901234567890123456789012345678901", + address: "0x1234567890123456789012345678901234567890", + blockNumber: 12345678, +}); + +console.log(isMember); + +/* MARKDOWN + Returns: + ```tsx + true + ``` + */ diff --git a/modules/client/src/addresslistVoting/internal/client/methods.ts b/modules/client/src/addresslistVoting/internal/client/methods.ts index 453b6c1f1..51da1d897 100644 --- a/modules/client/src/addresslistVoting/internal/client/methods.ts +++ b/modules/client/src/addresslistVoting/internal/client/methods.ts @@ -20,6 +20,7 @@ import { votingSettingsToContract, } from "../../../client-common"; import { + QueryAddresslistVotingIsMember, QueryAddresslistVotingMembers, QueryAddresslistVotingProposal, QueryAddresslistVotingProposals, @@ -55,6 +56,8 @@ import { InvalidCidError, InvalidProposalIdError, IpfsPinError, + IsMemberParams, + IsMemberSchema, isProposalId, MULTI_FETCH_TIMEOUT, NoProviderError, @@ -578,4 +581,29 @@ export class AddresslistVotingClientMethods extends ClientCore votingMode: addresslistVotingPlugin.votingMode, }; } + + /** + * Checks if a given address is a member of the AddresslistVoting contract. + * @param params - The parameters for the isMember method. + * @param params.pluginAddress - The address of the plugin. + * @param params.address - The address to check. + * @param params.blockNumber - The block number for specifying a specific block. + * @returns A boolean indicating whether the address is a member or not. + */ + public async isMember(params: IsMemberParams): Promise { + IsMemberSchema.strict().validateSync(params); + const query = QueryAddresslistVotingIsMember; + const name = "AddresslistVoting isMember"; + type T = { addresslistVotingVoter: { id: string } }; + const { addresslistVotingVoter } = await this.graphql.request({ + query, + params: { + id: + `${params.pluginAddress.toLowerCase()}_${params.address.toLowerCase()}`, + blockHeight: params.blockNumber ? { number: params.blockNumber } : null, + }, + name, + }); + return !!addresslistVotingVoter; + } } diff --git a/modules/client/src/addresslistVoting/internal/graphql-queries/members.ts b/modules/client/src/addresslistVoting/internal/graphql-queries/members.ts index d8f6b7f9b..7ddc142d1 100644 --- a/modules/client/src/addresslistVoting/internal/graphql-queries/members.ts +++ b/modules/client/src/addresslistVoting/internal/graphql-queries/members.ts @@ -13,3 +13,13 @@ query AddresslistVotingMembers($where: AddresslistVotingVoter_filter!, $block: B address } }`; + +export const QueryAddresslistVotingIsMember = gql` +query AddresslistVotingIsMember($id: ID!, $block: Block_height) { + addresslistVotingVoter( + id: $id + block: $block + ) { + id + } +}`; diff --git a/modules/client/src/addresslistVoting/internal/interfaces.ts b/modules/client/src/addresslistVoting/internal/interfaces.ts index 17bba6d15..4551b2075 100644 --- a/modules/client/src/addresslistVoting/internal/interfaces.ts +++ b/modules/client/src/addresslistVoting/internal/interfaces.ts @@ -4,6 +4,7 @@ import { DaoAction, GasFeeEstimation, InterfaceParams, + IsMemberParams, PrepareInstallationStepValue, PrepareUpdateStepValue, ProposalMetadata, @@ -57,6 +58,7 @@ export interface IAddresslistVotingClientMethods { pluginAddress: string, blockNumber?: number, ) => Promise; + isMember: (params: IsMemberParams) => Promise; } export interface IAddresslistVotingClientEncoding { diff --git a/modules/client/src/multisig/internal/client/methods.ts b/modules/client/src/multisig/internal/client/methods.ts index 7938bbe17..ef10f2a09 100644 --- a/modules/client/src/multisig/internal/client/methods.ts +++ b/modules/client/src/multisig/internal/client/methods.ts @@ -29,6 +29,7 @@ import { } from "../../../client-common"; import { Multisig__factory } from "@aragon/osx-ethers"; import { + QueryMultisigIsMember, QueryMultisigMembers, QueryMultisigProposal, QueryMultisigProposals, @@ -53,6 +54,8 @@ import { InvalidCidError, InvalidProposalIdError, IpfsPinError, + IsMemberParams, + IsMemberSchema, isProposalId, MULTI_FETCH_TIMEOUT, NoProviderError, @@ -389,7 +392,7 @@ export class MultisigClientMethods extends ClientCore skip = 0, direction = SortDirection.ASC, sortBy = MembersSortBy.ADDRESS, - }: MembersQueryParams): Promise{ + }: MembersQueryParams): Promise { // TODO // update this with yup validation if (!isAddress(pluginAddress)) { @@ -565,4 +568,29 @@ export class MultisigClientMethods extends ClientCore ), ); } + + /** + * Checks if a given address is a member of the tokenVoting contract. + * @param params - The parameters for the isMember method. + * @param params.pluginAddress - The address of the plugin. + * @param params.address - The address to check. + * @param params.blockNumber - The block number for specifying a specific block. + * @returns A boolean indicating whether the address is a member or not. + */ + public async isMember(params: IsMemberParams): Promise { + IsMemberSchema.strict().validateSync(params); + const query = QueryMultisigIsMember; + const name = "multisig isMember"; + type T = { multisigApprover: { id: string } }; + const { multisigApprover } = await this.graphql.request({ + query, + params: { + id: + `${params.pluginAddress.toLowerCase()}_${params.address.toLowerCase()}`, + blockHeight: params.blockNumber ? { number: params.blockNumber } : null, + }, + name, + }); + return !!multisigApprover; + } } diff --git a/modules/client/src/multisig/internal/graphql-queries/members.ts b/modules/client/src/multisig/internal/graphql-queries/members.ts index 5f7ef0be7..8203358a5 100644 --- a/modules/client/src/multisig/internal/graphql-queries/members.ts +++ b/modules/client/src/multisig/internal/graphql-queries/members.ts @@ -14,3 +14,13 @@ query MultisigMembers($where: MultisigApprover_filter!, $block: Block_height, $l } } `; + +export const QueryMultisigIsMember = gql` +query MultisigIsMember($id: ID!, $block: Block_height) { + multisigApprover( + id: $id + block: $block + ) { + id + } +}`; diff --git a/modules/client/src/multisig/internal/interfaces.ts b/modules/client/src/multisig/internal/interfaces.ts index d2f546690..1b19011bd 100644 --- a/modules/client/src/multisig/internal/interfaces.ts +++ b/modules/client/src/multisig/internal/interfaces.ts @@ -4,6 +4,7 @@ import { DaoAction, GasFeeEstimation, InterfaceParams, + IsMemberParams, PrepareInstallationStepValue, PrepareUpdateStepValue, ProposalMetadata, @@ -58,6 +59,7 @@ export interface IMultisigClientMethods { getProposals: ( params: ProposalQueryParams, ) => Promise; + isMember: (params: IsMemberParams) => Promise; } export interface IMultisigClientEncoding { diff --git a/modules/client/src/tokenVoting/internal/client/methods.ts b/modules/client/src/tokenVoting/internal/client/methods.ts index 9289ac0a4..f96f02940 100644 --- a/modules/client/src/tokenVoting/internal/client/methods.ts +++ b/modules/client/src/tokenVoting/internal/client/methods.ts @@ -47,6 +47,7 @@ import { TokenVotingTokenCompatibility, } from "../types"; import { + QueryTokenVotingIsMember, QueryTokenVotingMembers, QueryTokenVotingPlugin, QueryTokenVotingProposal, @@ -112,6 +113,8 @@ import { import { abi as ERC165_ABI } from "@openzeppelin/contracts/build/contracts/ERC165.json"; import { Contract } from "@ethersproject/contracts"; import { AddressZero } from "@ethersproject/constants"; +import { IsMemberSchema } from "@aragon/sdk-client-common"; +import { IsMemberParams } from "@aragon/sdk-client-common"; /** * Methods module the SDK TokenVoting Client @@ -832,4 +835,28 @@ export class TokenVotingClientMethods extends ClientCore return TokenVotingTokenCompatibility.NEEDS_WRAPPING; } } + /** + * Checks if a given address is a member of the tokenVoting contract. + * @param params - The parameters for the isMember method. + * @param params.pluginAddress - The address of the plugin. + * @param params.address - The address to check. + * @param params.blockNumber - The block number for specifying a specific block. + * @returns A boolean indicating whether the address is a member or not. + */ + public async isMember(params: IsMemberParams): Promise { + IsMemberSchema.strict().validateSync(params); + const query = QueryTokenVotingIsMember; + const name = "TokenVoting isMember"; + type T = { tokenVotingMember: { id: string } }; + const { tokenVotingMember } = await this.graphql.request({ + query, + params: { + id: + `${params.pluginAddress.toLowerCase()}_${params.address.toLowerCase()}`, + blockHeight: params.blockNumber ? { number: params.blockNumber } : null, + }, + name, + }); + return !!tokenVotingMember; + } } diff --git a/modules/client/src/tokenVoting/internal/graphql-queries/members.ts b/modules/client/src/tokenVoting/internal/graphql-queries/members.ts index 371bd5f69..224be72bb 100644 --- a/modules/client/src/tokenVoting/internal/graphql-queries/members.ts +++ b/modules/client/src/tokenVoting/internal/graphql-queries/members.ts @@ -23,3 +23,13 @@ query TokenVotingMembers($where: TokenVotingMember_filter!, $block: Block_height } } `; + +export const QueryTokenVotingIsMember = gql` +query TokenVotingIsMember($id: ID!, $block: Block_height) { + tokenVotingMember( + id: $id + block: $block + ) { + id + } +}`; diff --git a/modules/client/src/tokenVoting/internal/interfaces.ts b/modules/client/src/tokenVoting/internal/interfaces.ts index 9e0aa70c7..66955931d 100644 --- a/modules/client/src/tokenVoting/internal/interfaces.ts +++ b/modules/client/src/tokenVoting/internal/interfaces.ts @@ -3,6 +3,7 @@ import { DaoAction, GasFeeEstimation, InterfaceParams, + IsMemberParams, PrepareInstallationStepValue, PrepareUpdateStepValue, ProposalMetadata, @@ -91,6 +92,7 @@ export interface ITokenVotingClientMethods { isTokenVotingCompatibleToken: ( tokenAddress: string, ) => Promise; + isMember: (params: IsMemberParams) => Promise; } export interface ITokenVotingClientEncoding { diff --git a/modules/client/test/integration/addresslistVoting-client/methods.test.ts b/modules/client/test/integration/addresslistVoting-client/methods.test.ts index 99fe47f69..e67969f5b 100644 --- a/modules/client/test/integration/addresslistVoting-client/methods.test.ts +++ b/modules/client/test/integration/addresslistVoting-client/methods.test.ts @@ -22,6 +22,7 @@ import { } from "../../../src"; import { ADDRESS_ONE, + ADDRESS_TWO, contextParamsLocalChain, SUBGRAPH_ACTIONS, SUBGRAPH_PLUGIN_INSTALLATION, @@ -42,6 +43,7 @@ import { import { buildAddressListVotingDAO } from "../../helpers/build-daos"; import { JsonRpcProvider } from "@ethersproject/providers"; import { + QueryAddresslistVotingIsMember, QueryAddresslistVotingMembers, QueryAddresslistVotingProposal, QueryAddresslistVotingProposals, @@ -53,13 +55,13 @@ import { } from "../../../src/addresslistVoting/internal/types"; import { Context, + getExtendedProposalId, InvalidAddressOrEnsError, PrepareInstallationStep, PrepareUpdateStep, ProposalMetadata, ProposalStatus, SortDirection, - getExtendedProposalId, } from "@aragon/sdk-client-common"; import { PluginRepo__factory } from "@aragon/osx-ethers"; import { createAddresslistVotingPluginBuild } from "../../helpers/create-plugin-build"; @@ -684,6 +686,86 @@ describe("Client Address List", () => { }); }); + describe("isMember", () => { + it("Should check if an address is a member of a DAO and return true", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new AddresslistVotingClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + addresslistVotingVoter: { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + }, + }); + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + }); + expect(isMember).toBe(true); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryAddresslistVotingIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: null, + }, + ); + }); + it("Should check if an address is a member in a specified block number of a DAO and return true", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new AddresslistVotingClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + addresslistVotingVoter: { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + }, + }); + const blockNumber = 123456; + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + blockNumber, + }); + expect(isMember).toBe(true); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryAddresslistVotingIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: { + number: blockNumber, + }, + }, + ); + }); + it("Should check if an address is a member of a DAO and return false", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new AddresslistVotingClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + addresslistVotingVoter: null, + }); + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + }); + expect(isMember).toBe(false); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryAddresslistVotingIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: null, + }, + ); + }); + }); + describe("Data retrieval", () => { it("Should get the list of members that can vote in a proposal", async () => { const ctx = new Context(contextParamsLocalChain); diff --git a/modules/client/test/integration/multisig-client/methods.test.ts b/modules/client/test/integration/multisig-client/methods.test.ts index 66271e5f6..1f5c8dd22 100644 --- a/modules/client/test/integration/multisig-client/methods.test.ts +++ b/modules/client/test/integration/multisig-client/methods.test.ts @@ -40,17 +40,20 @@ import { QueryMultisigProposals, QueryMultisigVotingSettings, } from "../../../src/multisig/internal/graphql-queries"; -import { QueryMultisigMembers } from "../../../src/multisig/internal/graphql-queries/members"; +import { + QueryMultisigIsMember, + QueryMultisigMembers, +} from "../../../src/multisig/internal/graphql-queries/members"; import { SubgraphMultisigProposal } from "../../../src/multisig/internal/types"; import { Context, + getExtendedProposalId, InvalidAddressOrEnsError, PrepareInstallationStep, PrepareUpdateStep, ProposalMetadata, ProposalStatus, SortDirection, - getExtendedProposalId, } from "@aragon/sdk-client-common"; import { PluginRepo__factory } from "@aragon/osx-ethers"; import { createMultisigPluginBuild } from "../../helpers/create-plugin-build"; @@ -435,6 +438,85 @@ describe("Client Multisig", () => { } }); }); + describe("isMember", () => { + it("Should check if an address is a member of a DAO and return true", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new MultisigClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + multisigApprover: { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + }, + }); + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + }); + expect(isMember).toBe(true); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryMultisigIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: null, + }, + ); + }); + it("Should check if an address is a member in a specified block number of a DAO and return true", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new MultisigClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + multisigApprover: { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + }, + }); + const blockNumber = 123456; + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + blockNumber, + }); + expect(isMember).toBe(true); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryMultisigIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: { + number: blockNumber, + }, + }, + ); + }); + it("Should check if an address is a member of a DAO and return false", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new MultisigClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + multisigApprover: null, + }); + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + }); + expect(isMember).toBe(false); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryMultisigIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: null, + }, + ); + }); + }); describe("Data retrieval", () => { it("Should get the voting settings of the plugin", async () => { diff --git a/modules/client/test/integration/tokenVoting-client/methods.test.ts b/modules/client/test/integration/tokenVoting-client/methods.test.ts index fd506cd5d..2852438b6 100644 --- a/modules/client/test/integration/tokenVoting-client/methods.test.ts +++ b/modules/client/test/integration/tokenVoting-client/methods.test.ts @@ -56,6 +56,7 @@ import { } from "../../helpers/block-times"; import { JsonRpcProvider } from "@ethersproject/providers"; import { + QueryTokenVotingIsMember, QueryTokenVotingMembers, QueryTokenVotingPlugin, QueryTokenVotingProposal, @@ -81,6 +82,7 @@ import { import { BigNumber } from "@ethersproject/bignumber"; import { Context, + getExtendedProposalId, InvalidAddressError, InvalidAddressOrEnsError, NotAContractError, @@ -90,7 +92,6 @@ import { ProposalStatus, SortDirection, TokenType, - getExtendedProposalId, } from "@aragon/sdk-client-common"; import { createTokenVotingPluginBuild } from "../../helpers/create-plugin-build"; @@ -879,6 +880,85 @@ describe("Token Voting Client", () => { delegatee = await client.methods.getDelegatee(tokenAddress); expect(delegatee).toBe(null); }); + describe("isMember", () => { + it("Should check if an address is a member of a DAO and return true", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new TokenVotingClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + tokenVotingMember: { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + }, + }); + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + }); + expect(isMember).toBe(true); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryTokenVotingIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: null, + }, + ); + }); + it("Should check if an address is a member in a specified block number of a DAO and return true", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new TokenVotingClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + tokenVotingMember: { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + }, + }); + const blockNumber = 123456; + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + blockNumber, + }); + expect(isMember).toBe(true); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryTokenVotingIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: { + number: blockNumber, + }, + }, + ); + }); + it("Should check if an address is a member of a DAO and return false", async () => { + const ctx = new Context(contextParamsLocalChain); + const client = new TokenVotingClient(ctx); + + const mockedClient = mockedGraphqlRequest.getMockedInstance( + client.graphql.getClient(), + ); + mockedClient.request.mockResolvedValueOnce({ + tokenVotingMember: null, + }); + const isMember = await client.methods.isMember({ + pluginAddress: ADDRESS_ONE, + address: ADDRESS_TWO, + }); + expect(isMember).toBe(false); + expect(mockedClient.request).toHaveBeenLastCalledWith( + QueryTokenVotingIsMember, + { + id: `${ADDRESS_ONE}_${ADDRESS_TWO}`, + blockHeight: null, + }, + ); + }); + }); describe("Data retrieval", () => { it("Should get the list of members that can vote in a proposal", async () => { const ctx = new Context(contextParamsLocalChain); diff --git a/yarn.lock b/yarn.lock index 5511e8c21..d407c5334 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,6 +24,13 @@ dependencies: ethers "^5.6.2" +"@aragon/osx-ethers@^1.3.0-rc0.4": + version "1.3.0-rc0.4" + resolved "https://registry.yarnpkg.com/@aragon/osx-ethers/-/osx-ethers-1.3.0-rc0.4.tgz#878af071e454ef068801104deae8439f0f8f1720" + integrity sha512-FDuF6LC1OLnjFK4C8+P4Wf0sORrrUQ/JtUAxL5ABVtBD8JpyyhtdWGDiv/yWIooLyC2l8aqbDLPuiYWhw1DjEQ== + dependencies: + ethers "^5.6.2" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.5.5": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" From 7cd47bd67f29610fa80a9e1ec83dd0c3193fb5bd Mon Sep 17 00:00:00 2001 From: josemarinas <36479864+josemarinas@users.noreply.github.com> Date: Mon, 6 Nov 2023 10:16:30 +0100 Subject: [PATCH 2/2] Feature: Add actions to proposal queries (#300) * add actions to proposal queries * update changelog and package.json * update changelog and package.json --- modules/client-common/CHANGELOG.md | 6 ++++++ modules/client-common/package.json | 2 +- modules/client-common/src/types.ts | 1 + modules/client/CHANGELOG.md | 3 +++ modules/client/package.json | 4 ++-- .../internal/graphql-queries/proposal.ts | 5 +++++ .../src/addresslistVoting/internal/types.ts | 2 -- .../src/addresslistVoting/internal/utils.ts | 9 ++++++++ .../client/src/client-common/types/plugin.ts | 1 + .../internal/graphql-queries/proposal.ts | 5 +++++ modules/client/src/multisig/internal/types.ts | 19 ++--------------- modules/client/src/multisig/internal/utils.ts | 9 ++++++++ .../internal/graphql-queries/proposal.ts | 5 +++++ .../client/src/tokenVoting/internal/utils.ts | 9 ++++++++ .../addresslistVoting-client/methods.test.ts | 12 +++++++++-- modules/client/test/integration/constants.ts | 1 + .../multisig-client/methods.test.ts | 21 +++++++++++++++++-- .../tokenVoting-client/methods.test.ts | 12 +++++++++-- 18 files changed, 98 insertions(+), 28 deletions(-) diff --git a/modules/client-common/CHANGELOG.md b/modules/client-common/CHANGELOG.md index d6e133049..d89af3c0d 100644 --- a/modules/client-common/CHANGELOG.md +++ b/modules/client-common/CHANGELOG.md @@ -18,6 +18,12 @@ TEMPLATE: --> ## [UPCOMING] + +### Added + +- Added `actions` to `SubgraphListItem` type + +## [1.10.0] ### Added - Add support for arbitrum network diff --git a/modules/client-common/package.json b/modules/client-common/package.json index d6809eaa6..13330eb04 100644 --- a/modules/client-common/package.json +++ b/modules/client-common/package.json @@ -1,7 +1,7 @@ { "name": "@aragon/sdk-client-common", "author": "Aragon Association", - "version": "1.10.0", + "version": "1.11.0", "license": "MIT", "main": "dist/index.js", "module": "dist/sdk-client-common.esm.js", diff --git a/modules/client-common/src/types.ts b/modules/client-common/src/types.ts index b47ba6764..eba248d1a 100644 --- a/modules/client-common/src/types.ts +++ b/modules/client-common/src/types.ts @@ -237,6 +237,7 @@ export type ProposalListItemBase = { startDate: Date; endDate: Date; status: ProposalStatus; + actions: DaoAction[]; }; export type PrepareUpdateParams = { diff --git a/modules/client/CHANGELOG.md b/modules/client/CHANGELOG.md index a39c9d8ff..17c3cd996 100644 --- a/modules/client/CHANGELOG.md +++ b/modules/client/CHANGELOG.md @@ -24,6 +24,9 @@ TEMPLATE: - `isMember` function to `TokenVotingClient` - `isMember` function to `AddresslistVotingClient` - `isMember` function to `Client` +- Add `actions` to `MultisigProposalListItem` +- Add `actions` to `TokenVotingProposalListItem` +- Add `actions` to `AddresslistVotingProposalListItem` ## [1.18.2] ### Fixed diff --git a/modules/client/package.json b/modules/client/package.json index 04bc87469..5a3e78345 100644 --- a/modules/client/package.json +++ b/modules/client/package.json @@ -1,7 +1,7 @@ { "name": "@aragon/sdk-client", "author": "Aragon Association", - "version": "1.18.2", + "version": "1.19.0", "license": "MIT", "main": "dist/index.js", "module": "dist/sdk-client.esm.js", @@ -62,7 +62,7 @@ }, "dependencies": { "@aragon/osx-ethers": "1.3.0-rc0.4", - "@aragon/sdk-client-common": "^1.10.0", + "@aragon/sdk-client-common": "^1.11.0", "@aragon/sdk-ipfs": "^1.1.0", "@ethersproject/abstract-signer": "^5.5.0", "@ethersproject/bignumber": "^5.6.0", diff --git a/modules/client/src/addresslistVoting/internal/graphql-queries/proposal.ts b/modules/client/src/addresslistVoting/internal/graphql-queries/proposal.ts index 60741ab4f..e827770be 100644 --- a/modules/client/src/addresslistVoting/internal/graphql-queries/proposal.ts +++ b/modules/client/src/addresslistVoting/internal/graphql-queries/proposal.ts @@ -50,6 +50,11 @@ query AddresslistVotingProposals($where: AddresslistVotingProposal_filter!, $lim id subdomain } + actions { + to + value + data + } creator metadata yes diff --git a/modules/client/src/addresslistVoting/internal/types.ts b/modules/client/src/addresslistVoting/internal/types.ts index 3e71420b2..1f220b68d 100644 --- a/modules/client/src/addresslistVoting/internal/types.ts +++ b/modules/client/src/addresslistVoting/internal/types.ts @@ -1,6 +1,5 @@ import { ContractVotingSettings, - SubgraphAction, SubgraphProposalBase, SubgraphVoterListItemBase, VotingMode, @@ -22,7 +21,6 @@ export type SubgraphAddresslistVotingProposalListItem = SubgraphProposalBase & { export type SubgraphAddresslistVotingProposal = SubgraphProposalBase & { createdAt: string; - actions: SubgraphAction[]; supportThreshold: string; minVotingPower: string; voters: SubgraphAddresslistVotingVoterListItem[]; diff --git a/modules/client/src/addresslistVoting/internal/utils.ts b/modules/client/src/addresslistVoting/internal/utils.ts index b55f29264..c5ab09704 100644 --- a/modules/client/src/addresslistVoting/internal/utils.ts +++ b/modules/client/src/addresslistVoting/internal/utils.ts @@ -126,6 +126,15 @@ export function toAddresslistVotingProposalListItem( no: proposal.no ? parseInt(proposal.no) : 0, abstain: proposal.abstain ? parseInt(proposal.abstain) : 0, }, + actions: proposal.actions.map( + (action: SubgraphAction): DaoAction => { + return { + data: hexToBytes(action.data), + to: action.to, + value: BigInt(action.value), + }; + }, + ), votes: proposal.voters.map( (voter: SubgraphAddresslistVotingVoterListItem) => { return { diff --git a/modules/client/src/client-common/types/plugin.ts b/modules/client/src/client-common/types/plugin.ts index 23b6ff013..a270efb09 100644 --- a/modules/client/src/client-common/types/plugin.ts +++ b/modules/client/src/client-common/types/plugin.ts @@ -128,6 +128,7 @@ export type SubgraphProposalBase = { endDate: string; executed: boolean; potentiallyExecutable: boolean; + actions: SubgraphAction[] }; export type ProposalQueryParams = Pagination & { diff --git a/modules/client/src/multisig/internal/graphql-queries/proposal.ts b/modules/client/src/multisig/internal/graphql-queries/proposal.ts index aea59125e..ab32b2b6e 100644 --- a/modules/client/src/multisig/internal/graphql-queries/proposal.ts +++ b/modules/client/src/multisig/internal/graphql-queries/proposal.ts @@ -55,6 +55,11 @@ query MultisigProposals($where: MultisigProposal_filter!, $limit:Int!, $skip: In approvers { id } + actions { + to + value + data + } minApprovals plugin{ onlyListed diff --git a/modules/client/src/multisig/internal/types.ts b/modules/client/src/multisig/internal/types.ts index 5d5546e21..bc6783278 100644 --- a/modules/client/src/multisig/internal/types.ts +++ b/modules/client/src/multisig/internal/types.ts @@ -1,19 +1,4 @@ -import { SubgraphAction } from "../../client-common"; - -/* Subgraph types */ -type SubgraphProposalBase = { - id: string; - dao: { - id: string; - subdomain: string; - }; - creator: string; - metadata: string; - executed: boolean; - createdAt: string; - startDate: string; - endDate: string; -}; +import { SubgraphProposalBase } from "../../client-common"; export type SubgraphMultisigProposalBase = SubgraphProposalBase & { plugin: SubgraphMultisigVotingSettings; @@ -27,7 +12,7 @@ export type SubgraphMultisigProposalBase = SubgraphProposalBase & { export type SubgraphMultisigProposalListItem = SubgraphMultisigProposalBase; export type SubgraphMultisigProposal = SubgraphMultisigProposalBase & { - actions: SubgraphAction[]; + createdAt: string; executionTxHash: string; executionDate: string; executionBlockNumber: string; diff --git a/modules/client/src/multisig/internal/utils.ts b/modules/client/src/multisig/internal/utils.ts index a92056e4f..420aa41cd 100644 --- a/modules/client/src/multisig/internal/utils.ts +++ b/modules/client/src/multisig/internal/utils.ts @@ -95,6 +95,15 @@ export function toMultisigProposalListItem( approvals: proposal.approvers.map( (approver) => approver.id.slice(0, 42), ), + actions: proposal.actions.map( + (action: SubgraphAction): DaoAction => { + return { + data: hexToBytes(action.data), + to: action.to, + value: BigInt(action.value), + }; + }, + ), settings: { onlyListed: proposal.plugin.onlyListed, minApprovals: proposal.minApprovals, diff --git a/modules/client/src/tokenVoting/internal/graphql-queries/proposal.ts b/modules/client/src/tokenVoting/internal/graphql-queries/proposal.ts index c4f5f134a..e2d50ff0b 100644 --- a/modules/client/src/tokenVoting/internal/graphql-queries/proposal.ts +++ b/modules/client/src/tokenVoting/internal/graphql-queries/proposal.ts @@ -85,6 +85,11 @@ query TokenVotingProposals($where: TokenVotingProposal_filter!, $limit:Int!, $sk supportThreshold minVotingPower totalVotingPower + actions { + to + value + data + } voters{ voter{ address diff --git a/modules/client/src/tokenVoting/internal/utils.ts b/modules/client/src/tokenVoting/internal/utils.ts index 5f072a1d0..d480fe5b0 100644 --- a/modules/client/src/tokenVoting/internal/utils.ts +++ b/modules/client/src/tokenVoting/internal/utils.ts @@ -176,6 +176,15 @@ export function toTokenVotingProposalListItem( }; }, ), + actions: proposal.actions.map( + (action: SubgraphAction): DaoAction => { + return { + data: hexToBytes(action.data), + to: action.to, + value: BigInt(action.value), + }; + }, + ), }; } diff --git a/modules/client/test/integration/addresslistVoting-client/methods.test.ts b/modules/client/test/integration/addresslistVoting-client/methods.test.ts index e67969f5b..03fa9bc0a 100644 --- a/modules/client/test/integration/addresslistVoting-client/methods.test.ts +++ b/modules/client/test/integration/addresslistVoting-client/methods.test.ts @@ -24,7 +24,6 @@ import { ADDRESS_ONE, ADDRESS_TWO, contextParamsLocalChain, - SUBGRAPH_ACTIONS, SUBGRAPH_PLUGIN_INSTALLATION, SUBGRAPH_PROPOSAL_BASE, SUBGRAPH_VOTERS, @@ -54,6 +53,7 @@ import { SubgraphAddresslistVotingProposalListItem, } from "../../../src/addresslistVoting/internal/types"; import { + bytesToHex, Context, getExtendedProposalId, InvalidAddressOrEnsError, @@ -832,7 +832,6 @@ describe("Client Address List", () => { const subgraphProposal: SubgraphAddresslistVotingProposal = { createdAt: Math.round(Date.now() / 1000).toString(), - actions: SUBGRAPH_ACTIONS, supportThreshold: "1000000", minVotingPower: "20", voters: SUBGRAPH_VOTERS, @@ -1009,6 +1008,15 @@ describe("Client Address List", () => { expect(proposals[0].creatorAddress).toBe(SUBGRAPH_PROPOSAL_BASE.creator); expect(proposals[0].metadata.title).toBe(ipfsMetadata.title); expect(proposals[0].metadata.summary).toBe(ipfsMetadata.summary); + for (const [index, action] of proposals[0].actions.entries()) { + expect(action.value).toBe( + BigInt(SUBGRAPH_PROPOSAL_BASE.actions[index].value), + ); + expect(action.to).toBe(SUBGRAPH_PROPOSAL_BASE.actions[index].to); + expect(bytesToHex(action.data)).toBe( + SUBGRAPH_PROPOSAL_BASE.actions[index].data.toLowerCase(), + ); + } expect(proposals[0].startDate.getTime()).toBe( parseInt(SUBGRAPH_PROPOSAL_BASE.startDate) * 1000, ); diff --git a/modules/client/test/integration/constants.ts b/modules/client/test/integration/constants.ts index 6d916f2db..6f67cc8d3 100644 --- a/modules/client/test/integration/constants.ts +++ b/modules/client/test/integration/constants.ts @@ -152,6 +152,7 @@ export const SUBGRAPH_VOTERS: SubgraphVoterListItemBase[] = [ export const SUBGRAPH_PROPOSAL_BASE: SubgraphProposalBase = { id: TEST_ADDRESSLIST_PROPOSAL_ID, + actions: SUBGRAPH_ACTIONS, dao: { id: ADDRESS_ONE, subdomain: "test", diff --git a/modules/client/test/integration/multisig-client/methods.test.ts b/modules/client/test/integration/multisig-client/methods.test.ts index 1f5c8dd22..43ae00e90 100644 --- a/modules/client/test/integration/multisig-client/methods.test.ts +++ b/modules/client/test/integration/multisig-client/methods.test.ts @@ -21,7 +21,6 @@ import { ADDRESS_ONE, ADDRESS_TWO, contextParamsLocalChain, - SUBGRAPH_ACTIONS, SUBGRAPH_PLUGIN_INSTALLATION, SUBGRAPH_PROPOSAL_BASE, TEST_INVALID_ADDRESS, @@ -46,6 +45,7 @@ import { } from "../../../src/multisig/internal/graphql-queries/members"; import { SubgraphMultisigProposal } from "../../../src/multisig/internal/types"; import { + bytesToHex, Context, getExtendedProposalId, InvalidAddressOrEnsError, @@ -609,7 +609,6 @@ describe("Client Multisig", () => { const subgraphProposal: SubgraphMultisigProposal = { createdAt: Math.round(Date.now() / 1000).toString(), - actions: SUBGRAPH_ACTIONS, creationBlockNumber: "40", executionDate: Math.round(Date.now() / 1000).toString(), executionBlockNumber: "50", @@ -655,6 +654,15 @@ describe("Client Multisig", () => { expect(proposal.creationDate.getTime()).toBe( parseInt(subgraphProposal.createdAt) * 1000, ); + for (const [index, action] of proposal.actions.entries()) { + expect(action.value).toBe( + BigInt(subgraphProposal.actions[index].value), + ); + expect(action.to).toBe(subgraphProposal.actions[index].to); + expect(bytesToHex(action.data)).toBe( + subgraphProposal.actions[index].data.toLowerCase(), + ); + } expect(proposal.actions).toMatchObject(proposal.actions); expect(proposal.executionTxHash).toMatch( @@ -762,6 +770,15 @@ describe("Client Multisig", () => { expect(proposals[0].approvals).toMatchObject([ADDRESS_ONE, ADDRESS_TWO]); expect(proposals[0].settings.minApprovals).toBe(5); expect(proposals[0].settings.onlyListed).toBe(true); + for (const [index, action] of proposals[0].actions.entries()) { + expect(action.value).toBe( + BigInt(SUBGRAPH_PROPOSAL_BASE.actions[index].value), + ); + expect(action.to).toBe(SUBGRAPH_PROPOSAL_BASE.actions[index].to); + expect(bytesToHex(action.data)).toBe( + SUBGRAPH_PROPOSAL_BASE.actions[index].data.toLowerCase(), + ); + } expect(mockedClient.request).toHaveBeenCalledWith( QueryMultisigProposals, diff --git a/modules/client/test/integration/tokenVoting-client/methods.test.ts b/modules/client/test/integration/tokenVoting-client/methods.test.ts index 2852438b6..a92197eea 100644 --- a/modules/client/test/integration/tokenVoting-client/methods.test.ts +++ b/modules/client/test/integration/tokenVoting-client/methods.test.ts @@ -35,7 +35,6 @@ import { ADDRESS_THREE, ADDRESS_TWO, contextParamsLocalChain, - SUBGRAPH_ACTIONS, SUBGRAPH_PLUGIN_INSTALLATION, SUBGRAPH_PROPOSAL_BASE, TEST_INVALID_ADDRESS, @@ -81,6 +80,7 @@ import { } from "@aragon/osx-ethers"; import { BigNumber } from "@ethersproject/bignumber"; import { + bytesToHex, Context, getExtendedProposalId, InvalidAddressError, @@ -1081,7 +1081,6 @@ describe("Token Voting Client", () => { const subgraphProposal: SubgraphTokenVotingProposal = { createdAt: Math.round(Date.now() / 1000).toString(), - actions: SUBGRAPH_ACTIONS, supportThreshold: "1000000", totalVotingPower: "3000000", votingMode: VotingMode.EARLY_EXECUTION, @@ -1326,6 +1325,15 @@ describe("Token Voting Client", () => { expect(proposals.length).toBe(2); for (const [index, proposal] of proposals.entries()) { expect(proposal.id).toBe(subgraphProposals[index].id); + for (const [actionIndex, action] of proposal.actions.entries()) { + expect(action.value).toBe( + BigInt(subgraphProposals[index].actions[actionIndex].value), + ); + expect(action.to).toBe(subgraphProposals[index].actions[actionIndex].to); + expect(bytesToHex(action.data)).toBe( + subgraphProposals[index].actions[actionIndex].data.toLowerCase(), + ); + } expect(proposal.dao.address).toBe(subgraphProposals[index].dao.id); expect(proposal.dao.name).toBe( subgraphProposals[index].dao.subdomain,