From bc74922ed2b9fffd85d16d16c78cc045f460d40a Mon Sep 17 00:00:00 2001 From: PJColombo Date: Sun, 7 Nov 2021 15:04:49 +0100 Subject: [PATCH 01/11] Subgraph: Add vote result status --- packages/connect-voting/subgraph/schema.graphql | 1 + packages/connect-voting/subgraph/src/Voting.ts | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/connect-voting/subgraph/schema.graphql b/packages/connect-voting/subgraph/schema.graphql index a58832f1..b49b7648 100644 --- a/packages/connect-voting/subgraph/schema.graphql +++ b/packages/connect-voting/subgraph/schema.graphql @@ -16,6 +16,7 @@ type Vote @entity { votingPower: BigInt! script: String! voteNum: BigInt! + isAccepted: Boolean! castVotes: [Cast!] @derivedFrom(field: "vote") } diff --git a/packages/connect-voting/subgraph/src/Voting.ts b/packages/connect-voting/subgraph/src/Voting.ts index 72b4b7ac..da94bfef 100644 --- a/packages/connect-voting/subgraph/src/Voting.ts +++ b/packages/connect-voting/subgraph/src/Voting.ts @@ -35,6 +35,7 @@ export function handleStartVote(event: StartVoteEvent): void { vote.orgAddress = voting.kernel() vote.executedAt = BigInt.fromI32(0) vote.executed = false + vote.isAccepted = isAccepted(vote.yea, vote.nay, vote.votingPower, vote.supportRequiredPct, vote.minAcceptQuorum, voting.PCT_BASE()) vote.save() } @@ -118,6 +119,16 @@ export function updateVoteState(votingAddress: Address, voteId: BigInt): void { const vote = VoteEntity.load(buildVoteEntityId(votingAddress, voteId))! vote.yea = voteData.value6 vote.nay = voteData.value7 + vote.isAccepted = isAccepted(vote.yea, vote.nay, vote.votingPower, vote.supportRequiredPct, vote.minAcceptQuorum, votingApp.PCT_BASE()) vote.save() } + +function isAccepted(yeas: BigInt, nays: BigInt, votingPower: BigInt, supportRequiredPct: BigInt, minimumAcceptanceQuorumPct: BigInt, pctBase: BigInt): boolean { + return hasReachedValuePct(yeas, yeas.plus(nays), supportRequiredPct, pctBase) && + hasReachedValuePct(yeas, votingPower, minimumAcceptanceQuorumPct, pctBase) +} + +function hasReachedValuePct(value: BigInt, total: BigInt, pct: BigInt, pctBase: BigInt): boolean { + return total.notEqual(BigInt.fromI32(0)) && (value.times(pctBase).div(total)).gt(pct) +} From e0958857ba2ce1483b6f789b1a25043e2ac66842 Mon Sep 17 00:00:00 2001 From: PJColombo Date: Sun, 7 Nov 2021 16:11:44 +0100 Subject: [PATCH 02/11] Subgraph: Add vote end date --- packages/connect-voting/subgraph/schema.graphql | 1 + packages/connect-voting/subgraph/src/Voting.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/connect-voting/subgraph/schema.graphql b/packages/connect-voting/subgraph/schema.graphql index b49b7648..d975b317 100644 --- a/packages/connect-voting/subgraph/schema.graphql +++ b/packages/connect-voting/subgraph/schema.graphql @@ -8,6 +8,7 @@ type Vote @entity { executed: Boolean! executedAt: BigInt! startDate: BigInt! + endDate: BigInt! snapshotBlock: BigInt! supportRequiredPct: BigInt! minAcceptQuorum: BigInt! diff --git a/packages/connect-voting/subgraph/src/Voting.ts b/packages/connect-voting/subgraph/src/Voting.ts index da94bfef..d6692d01 100644 --- a/packages/connect-voting/subgraph/src/Voting.ts +++ b/packages/connect-voting/subgraph/src/Voting.ts @@ -25,6 +25,7 @@ export function handleStartVote(event: StartVoteEvent): void { vote.metadata = event.params.metadata vote.voteNum = event.params.voteId vote.startDate = voteData.value2 + vote.endDate = vote.startDate.plus(voting.voteTime()) vote.snapshotBlock = voteData.value3 vote.supportRequiredPct = voteData.value4 vote.minAcceptQuorum = voteData.value5 @@ -36,7 +37,6 @@ export function handleStartVote(event: StartVoteEvent): void { vote.executedAt = BigInt.fromI32(0) vote.executed = false vote.isAccepted = isAccepted(vote.yea, vote.nay, vote.votingPower, vote.supportRequiredPct, vote.minAcceptQuorum, voting.PCT_BASE()) - vote.save() } From 3da159b8ec3b58fd6d8f2891e71df1825a83f4b1 Mon Sep 17 00:00:00 2001 From: PJColombo Date: Sun, 7 Nov 2021 17:17:47 +0100 Subject: [PATCH 03/11] Connector: Expose vote status --- packages/connect-voting/src/helpers/index.ts | 2 ++ .../connect-voting/src/helpers/numbers.ts | 3 +++ packages/connect-voting/src/helpers/time.ts | 4 ++++ packages/connect-voting/src/models/Vote.ts | 19 +++++++++++++++++++ .../src/thegraph/queries/index.ts | 4 ++++ packages/connect-voting/src/types.ts | 2 ++ 6 files changed, 34 insertions(+) create mode 100644 packages/connect-voting/src/helpers/index.ts create mode 100644 packages/connect-voting/src/helpers/numbers.ts create mode 100644 packages/connect-voting/src/helpers/time.ts diff --git a/packages/connect-voting/src/helpers/index.ts b/packages/connect-voting/src/helpers/index.ts new file mode 100644 index 00000000..0c315ac6 --- /dev/null +++ b/packages/connect-voting/src/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './numbers' +export * from './time' diff --git a/packages/connect-voting/src/helpers/numbers.ts b/packages/connect-voting/src/helpers/numbers.ts new file mode 100644 index 00000000..3d9e50ee --- /dev/null +++ b/packages/connect-voting/src/helpers/numbers.ts @@ -0,0 +1,3 @@ +import { BigNumber } from 'ethers' + +export const bn = (x: string | number): BigNumber => BigNumber.from(x.toString()) diff --git a/packages/connect-voting/src/helpers/time.ts b/packages/connect-voting/src/helpers/time.ts new file mode 100644 index 00000000..ae6ad5ca --- /dev/null +++ b/packages/connect-voting/src/helpers/time.ts @@ -0,0 +1,4 @@ +import { BigNumber } from 'ethers' +import { bn } from './numbers' + +export const currentTimestampEvm = (): BigNumber => bn(Math.floor(Date.now() / 1000)) diff --git a/packages/connect-voting/src/models/Vote.ts b/packages/connect-voting/src/models/Vote.ts index 8b20b96c..65513d33 100644 --- a/packages/connect-voting/src/models/Vote.ts +++ b/packages/connect-voting/src/models/Vote.ts @@ -2,6 +2,7 @@ import { SubscriptionCallback, SubscriptionResult } from '@aragon/connect-types' import { subscription } from '@aragon/connect-core' import { IVotingConnector, VoteData } from '../types' import Cast from './Cast' +import { bn, currentTimestampEvm } from '../helpers' export default class Vote { #connector: IVotingConnector @@ -13,6 +14,7 @@ export default class Vote { readonly executed: boolean readonly executedAt: string readonly startDate: string + readonly endDate: string readonly snapshotBlock: string readonly supportRequiredPct: string readonly minAcceptQuorum: string @@ -20,6 +22,7 @@ export default class Vote { readonly nay: string readonly votingPower: string readonly script: string + readonly isAccepted: boolean constructor(data: VoteData, connector: IVotingConnector) { this.#connector = connector @@ -31,6 +34,7 @@ export default class Vote { this.executed = data.executed this.executedAt = data.executedAt this.startDate = data.startDate + this.endDate = data.endDate this.snapshotBlock = data.snapshotBlock this.supportRequiredPct = data.supportRequiredPct this.minAcceptQuorum = data.minAcceptQuorum @@ -38,6 +42,21 @@ export default class Vote { this.nay = data.nay this.votingPower = data.votingPower this.script = data.script + this.isAccepted = data.isAccepted + } + + get status(): string { + const currentTimestamp = currentTimestampEvm() + + if (!this.executed) { + if (currentTimestamp.gte(bn(this.endDate))) { + return this.isAccepted ? "Accepted" : "Rejected" + } + + return "Ongoing" + } + + return "Executed" } async casts({ first = 1000, skip = 0 } = {}): Promise { diff --git a/packages/connect-voting/src/thegraph/queries/index.ts b/packages/connect-voting/src/thegraph/queries/index.ts index a38aea9b..2bce0bbf 100644 --- a/packages/connect-voting/src/thegraph/queries/index.ts +++ b/packages/connect-voting/src/thegraph/queries/index.ts @@ -14,12 +14,14 @@ export const ALL_VOTES = (type: string) => gql` executed executedAt startDate + endDate snapshotBlock supportRequiredPct minAcceptQuorum yea nay votingPower + isAccepted script } } @@ -39,12 +41,14 @@ export const CASTS_FOR_VOTE = (type: string) => gql` executed executedAt startDate + endDate snapshotBlock supportRequiredPct minAcceptQuorum yea nay votingPower + isAccepted script } voter { diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index e818c9a6..3b2ebe95 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -13,6 +13,7 @@ export interface VoteData { executed: boolean executedAt: string startDate: string + endDate: string snapshotBlock: string supportRequiredPct: string minAcceptQuorum: string @@ -20,6 +21,7 @@ export interface VoteData { nay: string votingPower: string script: string + isAccepted: boolean } export interface CastData { From 3afe899cb57ec3985aa8d3ba3b2f3549023d61bf Mon Sep 17 00:00:00 2001 From: PJColombo Date: Sun, 7 Nov 2021 17:48:52 +0100 Subject: [PATCH 04/11] Connector: Add vote status unit tests --- packages/connect-voting/src/__test__/votes.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/connect-voting/src/__test__/votes.test.ts b/packages/connect-voting/src/__test__/votes.test.ts index 2907ad63..bf32ebb9 100644 --- a/packages/connect-voting/src/__test__/votes.test.ts +++ b/packages/connect-voting/src/__test__/votes.test.ts @@ -87,6 +87,18 @@ describe('when connecting to a voting app', () => { expect(vote.startDate).toEqual('1599675534') }) + test('should have a valid endDate', () => { + expect(vote.endDate).toEqual('1600280334') + }) + + test('should have not be accepted', () => { + expect(vote.isAccepted).toBe(false) + }) + + test('should have a valid status', () => { + expect(vote.status).toEqual('Rejected') + }) + describe('when querying for the casts of a vote', () => { let casts: Cast[] From 81c08b9d16e632f34f00f83c3c4cacd728224aa7 Mon Sep 17 00:00:00 2001 From: PJColombo Date: Tue, 9 Nov 2021 23:56:03 +0100 Subject: [PATCH 05/11] Use enum for vote statuses --- packages/connect-voting/src/__test__/votes.test.ts | 3 ++- packages/connect-voting/src/models/Vote.ts | 10 +++++----- packages/connect-voting/src/types.ts | 7 +++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/connect-voting/src/__test__/votes.test.ts b/packages/connect-voting/src/__test__/votes.test.ts index bf32ebb9..379c154a 100644 --- a/packages/connect-voting/src/__test__/votes.test.ts +++ b/packages/connect-voting/src/__test__/votes.test.ts @@ -1,4 +1,5 @@ import { VotingConnectorTheGraph, Vote, Cast } from '../../src' +import { VoteStatus } from '../types' const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby-staging' @@ -96,7 +97,7 @@ describe('when connecting to a voting app', () => { }) test('should have a valid status', () => { - expect(vote.status).toEqual('Rejected') + expect(vote.status).toEqual(VoteStatus.Rejected) }) describe('when querying for the casts of a vote', () => { diff --git a/packages/connect-voting/src/models/Vote.ts b/packages/connect-voting/src/models/Vote.ts index 65513d33..9c2cded1 100644 --- a/packages/connect-voting/src/models/Vote.ts +++ b/packages/connect-voting/src/models/Vote.ts @@ -1,6 +1,6 @@ import { SubscriptionCallback, SubscriptionResult } from '@aragon/connect-types' import { subscription } from '@aragon/connect-core' -import { IVotingConnector, VoteData } from '../types' +import { IVotingConnector, VoteData, VoteStatus } from '../types' import Cast from './Cast' import { bn, currentTimestampEvm } from '../helpers' @@ -45,18 +45,18 @@ export default class Vote { this.isAccepted = data.isAccepted } - get status(): string { + get status(): VoteStatus { const currentTimestamp = currentTimestampEvm() if (!this.executed) { if (currentTimestamp.gte(bn(this.endDate))) { - return this.isAccepted ? "Accepted" : "Rejected" + return this.isAccepted ? VoteStatus.Accepted : VoteStatus.Rejected } - return "Ongoing" + return VoteStatus.Ongoing } - return "Executed" + return VoteStatus.Executed } async casts({ first = 1000, skip = 0 } = {}): Promise { diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index 3b2ebe95..ead60b44 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -5,6 +5,13 @@ import { import Vote from './models/Vote' import Cast from './models/Cast' +export enum VoteStatus { + Accepted = "Accepted", + Executed = "Executed", + Ongoing = "Ongoing", + Rejected = "Rejected", +} + export interface VoteData { id: string creator: string From 6f913d274ba2cf4898fa1f4344f7846b87c84c98 Mon Sep 17 00:00:00 2001 From: PJColombo Date: Fri, 12 Nov 2021 04:55:13 +0100 Subject: [PATCH 06/11] Core: Support fallback case and get parameter values when fetching an app's method --- packages/connect-core/src/index.ts | 1 + packages/connect-core/src/types.ts | 1 + packages/connect-core/src/utils/app.ts | 19 ++++++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/connect-core/src/index.ts b/packages/connect-core/src/index.ts index 13b330f0..66111250 100644 --- a/packages/connect-core/src/index.ts +++ b/packages/connect-core/src/index.ts @@ -22,6 +22,7 @@ export { ConnectionContext, IpfsResolver, AppData, + AppMethod, ForwardingPathData, PermissionData, RepoData, diff --git a/packages/connect-core/src/types.ts b/packages/connect-core/src/types.ts index 30c36730..d7042e27 100644 --- a/packages/connect-core/src/types.ts +++ b/packages/connect-core/src/types.ts @@ -137,6 +137,7 @@ export type Metadata = (AragonArtifact | AragonManifest)[] export interface AppMethod { roles: string[] sig: string + params?: any[] /** * This field might not be able if the contract does not use * conventional solidity syntax and Aragon naming standards diff --git a/packages/connect-core/src/utils/app.ts b/packages/connect-core/src/utils/app.ts index 79e1b2ea..76758085 100644 --- a/packages/connect-core/src/utils/app.ts +++ b/packages/connect-core/src/utils/app.ts @@ -1,4 +1,4 @@ -import { utils as ethersUtils } from 'ethers' +import { utils as ethersUtils, utils } from 'ethers' import { Abi, AppMethod } from '../types' import { ErrorInvalid } from '../errors' @@ -8,6 +8,10 @@ export const apmAppId = (appName: string): string => ethersUtils.namehash(`${appName}.aragonpm.eth`) function signatureFromAbi(signature: string, abi: Abi): string { + if (signature === 'fallback') { + return '()' + } + const matches = signature.match(/(.*)\((.*)\)/m) if (!matches) { @@ -44,7 +48,7 @@ function findAppMethod( if (Array.isArray(functions)) { method = functions .map((f) => { - return { ...f, sig: signatureFromAbi(f.sig, app.abi) } + return { ...f, sig: signatureFromAbi(f.sig, app.abi), params: [] } }) .find(methodTestFn) } @@ -79,12 +83,21 @@ export function findAppMethodFromData( { allowDeprecated = true } = {} ): AppMethod | undefined { const methodId = data.substring(0, 10) - return findAppMethod( + const appMethod = findAppMethod( app, (method: AppMethod) => ethersUtils.id(method.sig).substring(0, 10) === methodId, { allowDeprecated } ) + + // Decode method's parameters + if (appMethod?.abi) { + const inputTypes = appMethod.abi?.inputs.map(({ type }) => type) + + appMethod.params = [...utils.defaultAbiCoder.decode(inputTypes, `0x${data.slice(10)}`)] + } + + return appMethod } /** From c882d691e34eec36e95f00956a389beb9a5dd69b Mon Sep 17 00:00:00 2001 From: PJColombo Date: Fri, 12 Nov 2021 05:42:57 +0100 Subject: [PATCH 07/11] Voting Connector: Implemented function to get all the actions inside a script --- .../connect-voting/src/helpers/actions.ts | 44 +++++++++++++++++++ packages/connect-voting/src/helpers/index.ts | 1 + packages/connect-voting/src/models/Vote.ts | 30 +++++++++++-- packages/connect-voting/src/types.ts | 14 ++++++ 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 packages/connect-voting/src/helpers/actions.ts diff --git a/packages/connect-voting/src/helpers/actions.ts b/packages/connect-voting/src/helpers/actions.ts new file mode 100644 index 00000000..2a9130fb --- /dev/null +++ b/packages/connect-voting/src/helpers/actions.ts @@ -0,0 +1,44 @@ +import { utils } from 'ethers' +import { AppMethod } from "@aragon/connect" +import { Reward } from '../types' + +export const getRewards = (appId: string, fnData: AppMethod): Reward[] | undefined => { + const {params, sig } = fnData + + if (!params || !params.length) { + return + } + + const sigHash = utils.id(sig).substring(0, 10) + + switch (appId) { + // finance.aragonpm.eth + case '0xbf8491150dafc5dcaee5b861414dca922de09ccffa344964ae167212e8c673ae': { + switch (sigHash) { + // newImmediatePayment(address,address,uint256,string) + case '0xf6364846': + return [{ + receiver: params[1], + token: params[0], + amount: params[2].toString() + }] + } + break + } + // agent.aragonpm.eth + case '0x9ac98dc5f995bf0211ed589ef022719d1487e5cb2bab505676f0d084c07cf89a': + // vault.aragonpm.eth + // eslint-disable-next-line no-fallthrough + case '0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1': + switch (sigHash) { + // transfer(address,address,uint256) + case '0xbeabacc8': + return [{ + receiver: params[1], + token: params[0], + amount: params[2].toString(), + }] + } + break + } +} diff --git a/packages/connect-voting/src/helpers/index.ts b/packages/connect-voting/src/helpers/index.ts index 0c315ac6..cd066d7c 100644 --- a/packages/connect-voting/src/helpers/index.ts +++ b/packages/connect-voting/src/helpers/index.ts @@ -1,2 +1,3 @@ export * from './numbers' +export * from './actions' export * from './time' diff --git a/packages/connect-voting/src/models/Vote.ts b/packages/connect-voting/src/models/Vote.ts index 9c2cded1..d77f9073 100644 --- a/packages/connect-voting/src/models/Vote.ts +++ b/packages/connect-voting/src/models/Vote.ts @@ -1,8 +1,9 @@ +import { decodeCallScript, findAppMethodFromData, App } from '@aragon/connect' +import { addressesEqual, subscription } from '@aragon/connect-core' import { SubscriptionCallback, SubscriptionResult } from '@aragon/connect-types' -import { subscription } from '@aragon/connect-core' -import { IVotingConnector, VoteData, VoteStatus } from '../types' import Cast from './Cast' -import { bn, currentTimestampEvm } from '../helpers' +import { Action, IVotingConnector, VoteData, VoteStatus } from '../types' +import { bn, currentTimestampEvm, getRewards } from '../helpers' export default class Vote { #connector: IVotingConnector @@ -24,6 +25,7 @@ export default class Vote { readonly script: string readonly isAccepted: boolean + constructor(data: VoteData, connector: IVotingConnector) { this.#connector = connector @@ -59,6 +61,28 @@ export default class Vote { return VoteStatus.Executed } + getActions(installedApps: App[]): Action[] { + const rawActions = decodeCallScript(this.script) + + return rawActions.map(({ to, data}): Action => { + const targetApp = installedApps.find(app => addressesEqual(app.address, to)) + const fnData = targetApp ? findAppMethodFromData(targetApp, data) : undefined + + // Check targetApp again to avoid typescript undefined warnings below + if (!targetApp || !fnData) { + return { + to + } + } + + return { + to, + fnData: findAppMethodFromData(targetApp, data), + rewards: getRewards(targetApp.appId, fnData) + } + }) + } + async casts({ first = 1000, skip = 0 } = {}): Promise { return this.#connector.castsForVote(this.id, first, skip) } diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index ead60b44..197982ae 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -1,4 +1,6 @@ +import { AppMethod } from '@aragon/connect-core' import { + Address, SubscriptionCallback, SubscriptionHandler, } from '@aragon/connect-types' @@ -45,6 +47,18 @@ export interface VoterData { address: string } +export interface Reward { + receiver: Address + token: Address + amount: string +} + +export interface Action { + to: Address + fnData?: AppMethod + rewards?: Reward[] +} + export interface IVotingConnector { disconnect(): Promise votesForApp(appAddress: string, first: number, skip: number): Promise From 095d1ac19803420253041e1232f3576efcac6279 Mon Sep 17 00:00:00 2001 From: PJColombo Date: Fri, 12 Nov 2021 15:48:26 +0100 Subject: [PATCH 08/11] Core: Remove redudant import --- packages/connect-core/src/utils/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/connect-core/src/utils/app.ts b/packages/connect-core/src/utils/app.ts index 76758085..d9b16a5f 100644 --- a/packages/connect-core/src/utils/app.ts +++ b/packages/connect-core/src/utils/app.ts @@ -1,4 +1,4 @@ -import { utils as ethersUtils, utils } from 'ethers' +import { utils as ethersUtils } from 'ethers' import { Abi, AppMethod } from '../types' import { ErrorInvalid } from '../errors' @@ -94,7 +94,7 @@ export function findAppMethodFromData( if (appMethod?.abi) { const inputTypes = appMethod.abi?.inputs.map(({ type }) => type) - appMethod.params = [...utils.defaultAbiCoder.decode(inputTypes, `0x${data.slice(10)}`)] + appMethod.params = [...ethersUtils.defaultAbiCoder.decode(inputTypes, `0x${data.slice(10)}`)] } return appMethod From 0f56ea69738b93b8732dc25e8221e571298dd66a Mon Sep 17 00:00:00 2001 From: PJColombo Date: Fri, 12 Nov 2021 19:10:36 +0100 Subject: [PATCH 09/11] Voting Connector: Add actions and rewards functionality unit tests --- .../connect-voting/src/__test__/votes.test.ts | 85 ++++++++++++++++++- .../connect-voting/src/helpers/actions.ts | 6 +- packages/connect-voting/src/models/Vote.ts | 3 +- packages/connect-voting/src/types.ts | 2 +- 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/packages/connect-voting/src/__test__/votes.test.ts b/packages/connect-voting/src/__test__/votes.test.ts index 379c154a..b414c616 100644 --- a/packages/connect-voting/src/__test__/votes.test.ts +++ b/packages/connect-voting/src/__test__/votes.test.ts @@ -1,12 +1,19 @@ +import { BigNumber } from 'ethers' +import { App, connect } from '@aragon/connect' import { VotingConnectorTheGraph, Vote, Cast } from '../../src' -import { VoteStatus } from '../types' +import { Action, VoteStatus } from '../types' const VOTING_SUBGRAPH_URL = - 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby-staging' + 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby' + const VOTING_APP_ADDRESS = '0x37187b0f2089b028482809308e776f92eeb7334e' +// For testing action-fetching functionality +const ACTIONS_ORG_ADDRESS = "0x63210F64Ef6F4EBB9727F6c5665CB8bbeDf20480" +const ACTIONS_VOTING_APP_ADDRESS = '0x9943c2f55d91308b8ddbc58b6e70d1774ace125e' describe('when connecting to a voting app', () => { let connector: VotingConnectorTheGraph + let votes: Vote[] beforeAll(() => { connector = new VotingConnectorTheGraph({ @@ -19,8 +26,6 @@ describe('when connecting to a voting app', () => { }) describe('when querying for all the votes of a voting app', () => { - let votes: Vote[] - beforeAll(async () => { votes = await connector.votesForApp(VOTING_APP_ADDRESS, 1000, 0) }) @@ -113,4 +118,76 @@ describe('when connecting to a voting app', () => { }) }) }) + + describe("when looking at the vote's actions of a voting app", () => { + let installedApps: App[] + let signallingVoteActions: Action[] + let onlyCodeExecutionActions: Action[] + let voteActions: Action[] + + beforeAll(async () => { + const org = await connect(ACTIONS_ORG_ADDRESS, "thegraph", { network: 4 }) + installedApps = await org.apps() + connector = new VotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) + votes = await connector.votesForApp(ACTIONS_VOTING_APP_ADDRESS, 1000, 0) + + onlyCodeExecutionActions = votes[0].getActions(installedApps) + signallingVoteActions = votes[1].getActions(installedApps) + voteActions = votes[4].getActions(installedApps) + }) + + test("should return a list of actions", () => { + expect(voteActions.length).toBeGreaterThan(0) + }) + + test("shouldn't return anything when getting actions from a signaling vote", () => { + expect(signallingVoteActions).toEqual([]) + }) + + test("shouldn't return rewards when getting actions from a vote that only executes code", () => { + const action = onlyCodeExecutionActions[0] + expect(action.rewards).toEqual([]) + }) + + describe("when looking at a specific vote's action and reward", () => { + let rewardedAction: Action + + beforeAll(() => { + rewardedAction = voteActions[0] + }) + + test('should have a valid to (target contract address)', () => { + expect(rewardedAction.to).toEqual("0xcaa6526abb106ff5c5f937e3ea9499243df86b7a") + }) + + test("should have a valid fnData", () => { + const { abi, notice, params, roles, sig } = rewardedAction.fnData! + + expect(Object.keys(abi!).length).toBeGreaterThan(0) + expect(notice).toEqual("Create a new payment of `@tokenAmount(_token, _amount)` to `_receiver` for '`_reference`'") + expect(params!).toEqual(['0x0000000000000000000000000000000000000000', + '0x9943c2f55D91308B8DDbc58B6e70d1774AcE125e', BigNumber.from('3000000000000000000'), "\"reference\""]) + expect(roles).toEqual([ 'CREATE_PAYMENTS_ROLE' ]) + expect(sig).toEqual("newImmediatePayment(address,address,uint256,string)") + }) + + test("should have a list of rewards", () => { + expect(rewardedAction.rewards.length).toBeGreaterThan(0) + }) + + test("should have a valid reward", () => { + const reward = rewardedAction.rewards[0] + const { amount, token, receiver } = reward + const ETH = '0x0000000000000000000000000000000000000000' + + expect(amount).toEqual('3000000000000000000') + expect(token).toEqual(ETH) + expect(receiver).toEqual('0x9943c2f55D91308B8DDbc58B6e70d1774AcE125e') + }) + }) + + }) + }) diff --git a/packages/connect-voting/src/helpers/actions.ts b/packages/connect-voting/src/helpers/actions.ts index 2a9130fb..33490ceb 100644 --- a/packages/connect-voting/src/helpers/actions.ts +++ b/packages/connect-voting/src/helpers/actions.ts @@ -2,11 +2,11 @@ import { utils } from 'ethers' import { AppMethod } from "@aragon/connect" import { Reward } from '../types' -export const getRewards = (appId: string, fnData: AppMethod): Reward[] | undefined => { +export const getRewards = (appId: string, fnData: AppMethod): Reward[] => { const {params, sig } = fnData if (!params || !params.length) { - return + return [] } const sigHash = utils.id(sig).substring(0, 10) @@ -41,4 +41,6 @@ export const getRewards = (appId: string, fnData: AppMethod): Reward[] | undefin } break } + + return [] } diff --git a/packages/connect-voting/src/models/Vote.ts b/packages/connect-voting/src/models/Vote.ts index d77f9073..6be1806c 100644 --- a/packages/connect-voting/src/models/Vote.ts +++ b/packages/connect-voting/src/models/Vote.ts @@ -71,7 +71,8 @@ export default class Vote { // Check targetApp again to avoid typescript undefined warnings below if (!targetApp || !fnData) { return { - to + to, + rewards: [] } } diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index 197982ae..50d15ecd 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -56,7 +56,7 @@ export interface Reward { export interface Action { to: Address fnData?: AppMethod - rewards?: Reward[] + rewards: Reward[] } export interface IVotingConnector { From f4cb8a6e98d23f092d79691f81e1f914d8c368d4 Mon Sep 17 00:00:00 2001 From: PJColombo Date: Sat, 13 Nov 2021 16:02:17 +0100 Subject: [PATCH 10/11] Core: Return complete fallback signature --- packages/connect-core/src/utils/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/connect-core/src/utils/app.ts b/packages/connect-core/src/utils/app.ts index d9b16a5f..82c3dd97 100644 --- a/packages/connect-core/src/utils/app.ts +++ b/packages/connect-core/src/utils/app.ts @@ -9,7 +9,7 @@ export const apmAppId = (appName: string): string => function signatureFromAbi(signature: string, abi: Abi): string { if (signature === 'fallback') { - return '()' + return 'fallback()' } const matches = signature.match(/(.*)\((.*)\)/m) From 633bcaad8cb27688a0fe9a9469691e4fe97fa3c3 Mon Sep 17 00:00:00 2001 From: PJColombo Date: Sat, 13 Nov 2021 16:49:33 +0100 Subject: [PATCH 11/11] Voting Connector: Add small fixes --- packages/connect-core/src/utils/app.ts | 2 +- packages/connect-voting/src/__test__/votes.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/connect-core/src/utils/app.ts b/packages/connect-core/src/utils/app.ts index 82c3dd97..f670f559 100644 --- a/packages/connect-core/src/utils/app.ts +++ b/packages/connect-core/src/utils/app.ts @@ -92,7 +92,7 @@ export function findAppMethodFromData( // Decode method's parameters if (appMethod?.abi) { - const inputTypes = appMethod.abi?.inputs.map(({ type }) => type) + const inputTypes = appMethod.abi.inputs.map(({ type }) => type) appMethod.params = [...ethersUtils.defaultAbiCoder.decode(inputTypes, `0x${data.slice(10)}`)] } diff --git a/packages/connect-voting/src/__test__/votes.test.ts b/packages/connect-voting/src/__test__/votes.test.ts index b414c616..1082bcb1 100644 --- a/packages/connect-voting/src/__test__/votes.test.ts +++ b/packages/connect-voting/src/__test__/votes.test.ts @@ -7,7 +7,7 @@ const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby' const VOTING_APP_ADDRESS = '0x37187b0f2089b028482809308e776f92eeb7334e' -// For testing action-fetching functionality +// For testing vote action functionality const ACTIONS_ORG_ADDRESS = "0x63210F64Ef6F4EBB9727F6c5665CB8bbeDf20480" const ACTIONS_VOTING_APP_ADDRESS = '0x9943c2f55d91308b8ddbc58b6e70d1774ace125e' @@ -119,10 +119,10 @@ describe('when connecting to a voting app', () => { }) }) - describe("when looking at the vote's actions of a voting app", () => { + describe("when looking at the votes actions of a voting app", () => { let installedApps: App[] let signallingVoteActions: Action[] - let onlyCodeExecutionActions: Action[] + let codeExecutionVoteActions: Action[] let voteActions: Action[] beforeAll(async () => { @@ -133,7 +133,7 @@ describe('when connecting to a voting app', () => { }) votes = await connector.votesForApp(ACTIONS_VOTING_APP_ADDRESS, 1000, 0) - onlyCodeExecutionActions = votes[0].getActions(installedApps) + codeExecutionVoteActions = votes[0].getActions(installedApps) signallingVoteActions = votes[1].getActions(installedApps) voteActions = votes[4].getActions(installedApps) }) @@ -147,7 +147,7 @@ describe('when connecting to a voting app', () => { }) test("shouldn't return rewards when getting actions from a vote that only executes code", () => { - const action = onlyCodeExecutionActions[0] + const action = codeExecutionVoteActions[0] expect(action.rewards).toEqual([]) })