From bc74922ed2b9fffd85d16d16c78cc045f460d40a Mon Sep 17 00:00:00 2001 From: PJColombo Date: Sun, 7 Nov 2021 15:04:49 +0100 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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