diff --git a/packages/connect-voting/src/__test__/votes.test.ts b/packages/connect-voting/src/__test__/votes.test.ts index 2907ad63..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' @@ -87,6 +88,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(VoteStatus.Rejected) + }) + describe('when querying for the casts of a vote', () => { let casts: Cast[] 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..9c2cded1 100644 --- a/packages/connect-voting/src/models/Vote.ts +++ b/packages/connect-voting/src/models/Vote.ts @@ -1,7 +1,8 @@ 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' 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(): VoteStatus { + const currentTimestamp = currentTimestampEvm() + + if (!this.executed) { + if (currentTimestamp.gte(bn(this.endDate))) { + return this.isAccepted ? VoteStatus.Accepted : VoteStatus.Rejected + } + + return VoteStatus.Ongoing + } + + return VoteStatus.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..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 @@ -13,6 +20,7 @@ export interface VoteData { executed: boolean executedAt: string startDate: string + endDate: string snapshotBlock: string supportRequiredPct: string minAcceptQuorum: string @@ -20,6 +28,7 @@ export interface VoteData { nay: string votingPower: string script: string + isAccepted: boolean } export interface CastData { diff --git a/packages/connect-voting/subgraph/schema.graphql b/packages/connect-voting/subgraph/schema.graphql index a58832f1..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! @@ -16,6 +17,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..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 @@ -35,7 +36,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) +}