From 086429df1dcf4b75261f304e124632c3227bbb86 Mon Sep 17 00:00:00 2001 From: Parker Lambert Date: Thu, 11 Nov 2021 15:45:59 -0500 Subject: [PATCH 1/7] Create rewards and exec data points in subgraph. --- packages/connect-voting/src/models/Reward.ts | 17 +++++++++ packages/connect-voting/src/models/Vote.ts | 13 ++++++- .../connect-voting/src/thegraph/connector.ts | 29 +++++++++++++- .../src/thegraph/parsers/index.ts | 1 + .../src/thegraph/parsers/rewards.ts | 29 ++++++++++++++ .../src/thegraph/queries/index.ts | 38 +++++++++++++++++++ packages/connect-voting/src/types.ts | 14 +++++++ .../connect-voting/subgraph/schema.graphql | 14 ++++++- .../connect-voting/subgraph/src/Voting.ts | 25 +++++++++++- 9 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 packages/connect-voting/src/models/Reward.ts create mode 100644 packages/connect-voting/src/thegraph/parsers/rewards.ts diff --git a/packages/connect-voting/src/models/Reward.ts b/packages/connect-voting/src/models/Reward.ts new file mode 100644 index 00000000..0f91a66a --- /dev/null +++ b/packages/connect-voting/src/models/Reward.ts @@ -0,0 +1,17 @@ +import { RewardData } from '../types' + +export default class Reward { + readonly id: string + readonly vote: string + readonly token: string + readonly to: string + readonly amount: string + + constructor(data: RewardData) { + this.id = data.id + this.vote = data.vote + this.token = data.token + this.to = data.to + this.amount = data.amount + } +} diff --git a/packages/connect-voting/src/models/Vote.ts b/packages/connect-voting/src/models/Vote.ts index 8b20b96c..9307c63b 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, RewardData } from '../types' import Cast from './Cast' +import Reward from './Reward' export default class Vote { #connector: IVotingConnector @@ -20,6 +21,9 @@ export default class Vote { readonly nay: string readonly votingPower: string readonly script: string + readonly spec: string + readonly contract: string + readonly calldata: string constructor(data: VoteData, connector: IVotingConnector) { this.#connector = connector @@ -38,6 +42,13 @@ export default class Vote { this.nay = data.nay this.votingPower = data.votingPower this.script = data.script + this.spec = data.spec + this.contract = data.contract + this.calldata = data.calldata + } + + async rewards({ first = 1000, skip = 0 } = {}): Promise { + return this.#connector.rewardsForVote(this.id, first , skip) } async casts({ first = 1000, skip = 0 } = {}): Promise { diff --git a/packages/connect-voting/src/thegraph/connector.ts b/packages/connect-voting/src/thegraph/connector.ts index bdf2c654..bab98c42 100644 --- a/packages/connect-voting/src/thegraph/connector.ts +++ b/packages/connect-voting/src/thegraph/connector.ts @@ -6,8 +6,9 @@ import { GraphQLWrapper, QueryResult } from '@aragon/connect-thegraph' import { IVotingConnector } from '../types' import Vote from '../models/Vote' import Cast from '../models/Cast' +import Reward from '../models/Reward' import * as queries from './queries' -import { parseVotes, parseCasts } from './parsers' +import { parseVotes, parseCasts, parseRewards } from './parsers' export function subgraphUrlFromChainId(chainId: number) { if (chainId === 1) { @@ -73,6 +74,32 @@ export default class VotingConnectorTheGraph implements IVotingConnector { ) } + async rewardsForVote( + vote: string, + first: number, + skip: number + ): Promise { + return this.#gql.performQueryWithParser( + queries.REWARDS_FOR_VOTE('query'), + { vote, first, skip }, + (result: QueryResult) => parseRewards(result) + ) + } + + onRewardsForVote( + vote: string, + first: number, + skip: number, + callback: SubscriptionCallback + ): SubscriptionHandler { + return this.#gql.subscribeToQueryWithParser( + queries.REWARDS_FOR_VOTE('subscription'), + { vote, first, skip }, + callback, + (result: QueryResult) => parseRewards(result) + ) + } + async castsForVote( vote: string, first: number, diff --git a/packages/connect-voting/src/thegraph/parsers/index.ts b/packages/connect-voting/src/thegraph/parsers/index.ts index 905c5968..39cfb8a2 100644 --- a/packages/connect-voting/src/thegraph/parsers/index.ts +++ b/packages/connect-voting/src/thegraph/parsers/index.ts @@ -1,2 +1,3 @@ export { parseVotes } from './votes' export { parseCasts } from './casts' +export { parseRewards } from './rewards' \ No newline at end of file diff --git a/packages/connect-voting/src/thegraph/parsers/rewards.ts b/packages/connect-voting/src/thegraph/parsers/rewards.ts new file mode 100644 index 00000000..ee9c6a35 --- /dev/null +++ b/packages/connect-voting/src/thegraph/parsers/rewards.ts @@ -0,0 +1,29 @@ +import { ErrorUnexpectedResult } from "packages/connect-core/dist/cjs" +import { QueryResult } from "packages/connect-thegraph/dist/cjs" +import Reward from "../../models/Reward" +import { RewardData } from "../../types" + +export function parseRewards(result: QueryResult): Reward[] { + const rewards = result.data.rewards + + if (!rewards) { + throw new ErrorUnexpectedResult('Unable to parse rewards.') + } + + const datas = rewards.map( + (reward: any): RewardData => { + return { + id: reward.id, + vote: reward.vote, + token: reward.token, + to: reward.to, + amount: reward.amount + } + } + ) + + return datas.map((data: RewardData) => { + return new Reward(data) + }) + } + \ No newline at end of file diff --git a/packages/connect-voting/src/thegraph/queries/index.ts b/packages/connect-voting/src/thegraph/queries/index.ts index a38aea9b..041e4654 100644 --- a/packages/connect-voting/src/thegraph/queries/index.ts +++ b/packages/connect-voting/src/thegraph/queries/index.ts @@ -21,6 +21,41 @@ export const ALL_VOTES = (type: string) => gql` nay votingPower script + spec + contract + calldata + } + } +` + +export const REWARDS_FOR_VOTE = (type: string) => gql` + ${type} Rewards($vote: ID!, $first: Int!, $skip: Int!) { + rewards(where: { vote: $vote }, first: $first, skip: $skip) { + id + token + to + amount + vote { + id + appAddress + orgAddress + creator + originalCreator + metadata + executed + executedAt + startDate + snapshotBlock + supportRequiredPct + minAcceptQuorum + yea + nay + votingPower + script + spec + contract + calldata + } } } ` @@ -46,6 +81,9 @@ export const CASTS_FOR_VOTE = (type: string) => gql` nay votingPower script + spec + contract + calldata } voter { id diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index e818c9a6..8ab0a28e 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -4,6 +4,7 @@ import { } from '@aragon/connect-types' import Vote from './models/Vote' import Cast from './models/Cast' +import Reward from './models/Reward' export interface VoteData { id: string @@ -20,6 +21,17 @@ export interface VoteData { nay: string votingPower: string script: string + spec: string + contract: string + calldata: string +} + +export interface RewardData { + id: string + vote: string + token: string + to: string + amount: string } export interface CastData { @@ -45,6 +57,8 @@ export interface IVotingConnector { skip: number, callback: SubscriptionCallback ): SubscriptionHandler + rewardsForVote(vote: string, first: number, skip: number): Promise + onRewardsForVote(vote: string, first: number, skip: number, callback: SubscriptionCallback): SubscriptionHandler castsForVote(vote: string, first: number, skip: number): Promise onCastsForVote( vote: string, diff --git a/packages/connect-voting/subgraph/schema.graphql b/packages/connect-voting/subgraph/schema.graphql index a58832f1..ab12efae 100644 --- a/packages/connect-voting/subgraph/schema.graphql +++ b/packages/connect-voting/subgraph/schema.graphql @@ -14,9 +14,21 @@ type Vote @entity { yea: BigInt! nay: BigInt! votingPower: BigInt! - script: String! + script: String! @deprecated(reason: "Use spec, contract, calldata fields") voteNum: BigInt! castVotes: [Cast!] @derivedFrom(field: "vote") + spec: BigInt! + contract: Bytes! + calldata: Bytes! + rewards: [Reward!] @derivedFrom(field: "vote") +} + +type Reward @entity { + id: ID! + token: Bytes! + to: Bytes! + amount: BigInt! + vote: Vote! } type Cast @entity { diff --git a/packages/connect-voting/subgraph/src/Voting.ts b/packages/connect-voting/subgraph/src/Voting.ts index 72b4b7ac..b1ffaa3b 100644 --- a/packages/connect-voting/subgraph/src/Voting.ts +++ b/packages/connect-voting/subgraph/src/Voting.ts @@ -1,4 +1,4 @@ -import { Address, BigInt } from '@graphprotocol/graph-ts' +import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts' import { StartVote as StartVoteEvent, CastVote as CastVoteEvent, @@ -9,6 +9,7 @@ import { Vote as VoteEntity, Cast as CastEntity, Voter as VoterEntity, + Reward as RewardEntity, } from '../generated/schema' /* eslint-disable @typescript-eslint/no-use-before-define */ @@ -31,14 +32,18 @@ export function handleStartVote(event: StartVoteEvent): void { vote.yea = voteData.value6 vote.nay = voteData.value7 vote.votingPower = voteData.value8 - vote.script = voteData.value9.toHex() + vote.script = voteData.value9.toHexString() vote.orgAddress = voting.kernel() vote.executedAt = BigInt.fromI32(0) vote.executed = false + vote.spec = new BigInt(voteData.value9.slice(0, 4)) + vote.contract = new Bytes(voteData.value9.slice(4, 24)) + vote.calldata = new Bytes(voteData.value9.slice(28)) vote.save() } + export function handleCastVote(event: CastVoteEvent): void { updateVoteState(event.address, event.params.voteId) @@ -84,6 +89,22 @@ function buildCastEntityId(voteId: BigInt, voter: Address): string { return voteId.toHexString() + '-voter:' + voter.toHexString() } +function buildRewardId(voteId: BigInt, token: Address, to: Address): string { + return voteId.toHexString() + '-reward-' + token.toHexString() + '-' + to.toHexString() +} + +function loadOrCreateReward(voteId: BigInt, token: Address, to: Address, amount: BigInt): RewardEntity { + const rewardId = buildRewardId(voteId, token, to) + let reward = RewardEntity.load(rewardId) + if (reward === null) { + reward = new RewardEntity(rewardId) + reward.token = token + reward.to = to + reward.amount = amount + } + return reward +} + function loadOrCreateVoter( votingAddress: Address, voterAddress: Address From d9725b5ff1c08c89ac6463d7a0e9c2f25dbeefeb Mon Sep 17 00:00:00 2001 From: Parker Lambert Date: Fri, 12 Nov 2021 13:26:44 -0500 Subject: [PATCH 2/7] Include better data structure for querying scripts and rewards. --- .../connect-voting/src/models/EvmScript.ts | 17 +++++ packages/connect-voting/src/models/Vote.ts | 8 --- packages/connect-voting/src/types.ts | 10 ++- .../connect-voting/subgraph/schema.graphql | 13 ++-- .../connect-voting/subgraph/src/Voting.ts | 72 ++++++++++++++----- 5 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 packages/connect-voting/src/models/EvmScript.ts diff --git a/packages/connect-voting/src/models/EvmScript.ts b/packages/connect-voting/src/models/EvmScript.ts new file mode 100644 index 00000000..0701ee7b --- /dev/null +++ b/packages/connect-voting/src/models/EvmScript.ts @@ -0,0 +1,17 @@ +import { EvmScriptData } from '../types' + +export default class EvmScript { + readonly id: string + readonly vote: string + readonly contract: string + readonly calldataLength: string + readonly calldata: string + + constructor(data: EvmScriptData) { + this.id = data.id + this.vote = data.vote + this.contract = data.contract + this.calldataLength = data.calldataLength + this.calldata = data.calldata + } +} diff --git a/packages/connect-voting/src/models/Vote.ts b/packages/connect-voting/src/models/Vote.ts index 9307c63b..1ac6c616 100644 --- a/packages/connect-voting/src/models/Vote.ts +++ b/packages/connect-voting/src/models/Vote.ts @@ -22,8 +22,6 @@ export default class Vote { readonly votingPower: string readonly script: string readonly spec: string - readonly contract: string - readonly calldata: string constructor(data: VoteData, connector: IVotingConnector) { this.#connector = connector @@ -43,12 +41,6 @@ export default class Vote { this.votingPower = data.votingPower this.script = data.script this.spec = data.spec - this.contract = data.contract - this.calldata = data.calldata - } - - async rewards({ first = 1000, skip = 0 } = {}): Promise { - return this.#connector.rewardsForVote(this.id, first , skip) } async casts({ first = 1000, skip = 0 } = {}): Promise { diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index 8ab0a28e..875c5cf6 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -22,8 +22,6 @@ export interface VoteData { votingPower: string script: string spec: string - contract: string - calldata: string } export interface RewardData { @@ -34,6 +32,14 @@ export interface RewardData { amount: string } +export interface EvmScriptData { + id: string + vote: string + contract: string + calldataLength: string + calldata: string +} + export interface CastData { id: string vote: string diff --git a/packages/connect-voting/subgraph/schema.graphql b/packages/connect-voting/subgraph/schema.graphql index ab12efae..8bf05971 100644 --- a/packages/connect-voting/subgraph/schema.graphql +++ b/packages/connect-voting/subgraph/schema.graphql @@ -14,13 +14,10 @@ type Vote @entity { yea: BigInt! nay: BigInt! votingPower: BigInt! - script: String! @deprecated(reason: "Use spec, contract, calldata fields") + script: String! voteNum: BigInt! castVotes: [Cast!] @derivedFrom(field: "vote") spec: BigInt! - contract: Bytes! - calldata: Bytes! - rewards: [Reward!] @derivedFrom(field: "vote") } type Reward @entity { @@ -31,6 +28,14 @@ type Reward @entity { vote: Vote! } +type EvmScript @entity { + id: ID! + contract: Bytes! + calldataLength: BigInt! + calldata: Bytes! + vote: Vote! +} + type Cast @entity { id: ID! vote: Vote! diff --git a/packages/connect-voting/subgraph/src/Voting.ts b/packages/connect-voting/subgraph/src/Voting.ts index b1ffaa3b..d5a40982 100644 --- a/packages/connect-voting/subgraph/src/Voting.ts +++ b/packages/connect-voting/subgraph/src/Voting.ts @@ -10,10 +10,14 @@ import { Cast as CastEntity, Voter as VoterEntity, Reward as RewardEntity, + EvmScript as EvmScriptEntity } from '../generated/schema' /* eslint-disable @typescript-eslint/no-use-before-define */ +const REWARDS_SCRIPT_ID = BigInt.fromI32(0x00000000) +const EVM_SCRIPT_ID = BigInt.fromI32(0x00000001) + export function handleStartVote(event: StartVoteEvent): void { const voteEntityId = buildVoteEntityId(event.address, event.params.voteId) const vote = new VoteEntity(voteEntityId) @@ -37,12 +41,54 @@ export function handleStartVote(event: StartVoteEvent): void { vote.executedAt = BigInt.fromI32(0) vote.executed = false vote.spec = new BigInt(voteData.value9.slice(0, 4)) - vote.contract = new Bytes(voteData.value9.slice(4, 24)) - vote.calldata = new Bytes(voteData.value9.slice(28)) vote.save() + + switch(vote.spec) { + case REWARDS_SCRIPT_ID: + saveRewards(vote.id, vote.script) + break; + case EVM_SCRIPT_ID: + saveScripts(vote.id, vote.script) + break; + } } +export function saveScripts(voteId: string, script: string): void { + const scriptBytes = Bytes.fromHexString(script) + + let location = 4 + + while (location < scriptBytes.length) { + const contract = new Address(scriptBytes.slice(location, location + 20)) + const calldataLength = new BigInt(scriptBytes.slice(location + 20, location + 24)) + const calldata = new Bytes(scriptBytes.slice(location + 24, location + 24 + calldataLength.toI32())) + + let evmScript = new EvmScriptEntity(buildEvmScriptEntityId(voteId, new Bytes(scriptBytes.slice(location, location + 24 + calldataLength.toI32())).toHexString())) + evmScript.contract = contract + evmScript.calldataLength = calldataLength + evmScript.calldata = calldata + location = location + 24 + calldataLength.toI32() + } +} + +export function saveRewards(voteId: string, script: string): void { + const scriptBytes = Bytes.fromHexString(script) + + let location = 4 + + while (location < script.length) { + const token = new Address(scriptBytes.slice(location, location + 20)) + const to = new Address(scriptBytes.slice(location + 20, location + 40)) + const amount = new BigInt(scriptBytes.slice(location + 40, location + 72)) + + let reward = new RewardEntity(buildRewardId(voteId, token, to)) + reward.amount = amount + reward.vote = voteId + reward.save() + location = location + 72 + } +} export function handleCastVote(event: CastVoteEvent): void { updateVoteState(event.address, event.params.voteId) @@ -75,6 +121,12 @@ export function handleExecuteVote(event: ExecuteVoteEvent): void { vote.save() } +function buildEvmScriptEntityId(voteId: string, data: string): string { + return ( + voteId + '-evmScript:' + data + ) +} + function buildVoteEntityId(appAddress: Address, voteNum: BigInt): string { return ( 'appAddress:' + appAddress.toHexString() + '-vote:' + voteNum.toHexString() @@ -89,20 +141,8 @@ function buildCastEntityId(voteId: BigInt, voter: Address): string { return voteId.toHexString() + '-voter:' + voter.toHexString() } -function buildRewardId(voteId: BigInt, token: Address, to: Address): string { - return voteId.toHexString() + '-reward-' + token.toHexString() + '-' + to.toHexString() -} - -function loadOrCreateReward(voteId: BigInt, token: Address, to: Address, amount: BigInt): RewardEntity { - const rewardId = buildRewardId(voteId, token, to) - let reward = RewardEntity.load(rewardId) - if (reward === null) { - reward = new RewardEntity(rewardId) - reward.token = token - reward.to = to - reward.amount = amount - } - return reward +function buildRewardId(voteId: string, token: Address, to: Address): string { + return voteId + '-reward-' + token.toHexString() + '-' + to.toHexString() } function loadOrCreateVoter( From cf7abd61f83c44bc092a1378b063553ebb920912 Mon Sep 17 00:00:00 2001 From: Parker Lambert Date: Fri, 12 Nov 2021 13:47:05 -0500 Subject: [PATCH 3/7] Remove unused code. --- .../connect-voting/src/thegraph/connector.ts | 26 --------------- .../src/thegraph/queries/index.ts | 32 ------------------- packages/connect-voting/src/types.ts | 2 -- 3 files changed, 60 deletions(-) diff --git a/packages/connect-voting/src/thegraph/connector.ts b/packages/connect-voting/src/thegraph/connector.ts index bab98c42..978eed0d 100644 --- a/packages/connect-voting/src/thegraph/connector.ts +++ b/packages/connect-voting/src/thegraph/connector.ts @@ -74,32 +74,6 @@ export default class VotingConnectorTheGraph implements IVotingConnector { ) } - async rewardsForVote( - vote: string, - first: number, - skip: number - ): Promise { - return this.#gql.performQueryWithParser( - queries.REWARDS_FOR_VOTE('query'), - { vote, first, skip }, - (result: QueryResult) => parseRewards(result) - ) - } - - onRewardsForVote( - vote: string, - first: number, - skip: number, - callback: SubscriptionCallback - ): SubscriptionHandler { - return this.#gql.subscribeToQueryWithParser( - queries.REWARDS_FOR_VOTE('subscription'), - { vote, first, skip }, - callback, - (result: QueryResult) => parseRewards(result) - ) - } - async castsForVote( vote: string, first: number, diff --git a/packages/connect-voting/src/thegraph/queries/index.ts b/packages/connect-voting/src/thegraph/queries/index.ts index 041e4654..987f3ce8 100644 --- a/packages/connect-voting/src/thegraph/queries/index.ts +++ b/packages/connect-voting/src/thegraph/queries/index.ts @@ -28,38 +28,6 @@ export const ALL_VOTES = (type: string) => gql` } ` -export const REWARDS_FOR_VOTE = (type: string) => gql` - ${type} Rewards($vote: ID!, $first: Int!, $skip: Int!) { - rewards(where: { vote: $vote }, first: $first, skip: $skip) { - id - token - to - amount - vote { - id - appAddress - orgAddress - creator - originalCreator - metadata - executed - executedAt - startDate - snapshotBlock - supportRequiredPct - minAcceptQuorum - yea - nay - votingPower - script - spec - contract - calldata - } - } - } -` - export const CASTS_FOR_VOTE = (type: string) => gql` ${type} Casts($vote: ID!, $first: Int!, $skip: Int!) { casts(where: { vote: $vote }, first: $first, skip: $skip) { diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index 875c5cf6..bd0ec1bd 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -63,8 +63,6 @@ export interface IVotingConnector { skip: number, callback: SubscriptionCallback ): SubscriptionHandler - rewardsForVote(vote: string, first: number, skip: number): Promise - onRewardsForVote(vote: string, first: number, skip: number, callback: SubscriptionCallback): SubscriptionHandler castsForVote(vote: string, first: number, skip: number): Promise onCastsForVote( vote: string, From 7c78b8393aee37bf6ca96f520465563b10f835b6 Mon Sep 17 00:00:00 2001 From: Parker Lambert Date: Thu, 18 Nov 2021 01:59:50 -0500 Subject: [PATCH 4/7] Modify mapping types. --- .../connect-voting/subgraph/src/Voting.ts | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/packages/connect-voting/subgraph/src/Voting.ts b/packages/connect-voting/subgraph/src/Voting.ts index d5a40982..68611a32 100644 --- a/packages/connect-voting/subgraph/src/Voting.ts +++ b/packages/connect-voting/subgraph/src/Voting.ts @@ -1,4 +1,4 @@ -import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts' +import { Address, BigInt, ByteArray, Bytes } from '@graphprotocol/graph-ts' import { StartVote as StartVoteEvent, CastVote as CastVoteEvent, @@ -15,14 +15,14 @@ import { /* eslint-disable @typescript-eslint/no-use-before-define */ -const REWARDS_SCRIPT_ID = BigInt.fromI32(0x00000000) -const EVM_SCRIPT_ID = BigInt.fromI32(0x00000001) +let REWARDS_SCRIPT_ID = 0x00000000 +let EVM_SCRIPT_ID = 0x00000001 export function handleStartVote(event: StartVoteEvent): void { - const voteEntityId = buildVoteEntityId(event.address, event.params.voteId) - const vote = new VoteEntity(voteEntityId) - const voting = VotingContract.bind(event.address) - const voteData = voting.getVote(event.params.voteId) + let voteEntityId = buildVoteEntityId(event.address, event.params.voteId) + let vote = new VoteEntity(voteEntityId) + let voting = VotingContract.bind(event.address) + let voteData = voting.getVote(event.params.voteId) vote.appAddress = event.address vote.creator = event.params.creator @@ -40,11 +40,11 @@ export function handleStartVote(event: StartVoteEvent): void { vote.orgAddress = voting.kernel() vote.executedAt = BigInt.fromI32(0) vote.executed = false - vote.spec = new BigInt(voteData.value9.slice(0, 4)) + vote.spec = BigInt.fromI32(Bytes.fromHexString(vote.script.substr(0, 10)).toI32()) vote.save() - switch(vote.spec) { + switch(vote.spec.toI32()) { case REWARDS_SCRIPT_ID: saveRewards(vote.id, vote.script) break; @@ -55,32 +55,28 @@ export function handleStartVote(event: StartVoteEvent): void { } export function saveScripts(voteId: string, script: string): void { - const scriptBytes = Bytes.fromHexString(script) + let location = 10 - let location = 4 - - while (location < scriptBytes.length) { - const contract = new Address(scriptBytes.slice(location, location + 20)) - const calldataLength = new BigInt(scriptBytes.slice(location + 20, location + 24)) - const calldata = new Bytes(scriptBytes.slice(location + 24, location + 24 + calldataLength.toI32())) + while (location < script.length) { + let contract = Bytes.fromHexString(script.substr(location, location + 40)) as Address + let calldataLength = Bytes.fromHexString(script.substr(location + 40, location + 48)).toU32() + let calldata = Bytes.fromHexString(script.substr(location + 48, location + 48 + calldataLength * 2 )) as Bytes - let evmScript = new EvmScriptEntity(buildEvmScriptEntityId(voteId, new Bytes(scriptBytes.slice(location, location + 24 + calldataLength.toI32())).toHexString())) + let evmScript = new EvmScriptEntity(buildEvmScriptEntityId(voteId, Bytes.fromHexString(script.substr(location, location + 48 + calldataLength * 2)).toHexString())) evmScript.contract = contract - evmScript.calldataLength = calldataLength + evmScript.calldataLength = new BigInt(calldataLength) evmScript.calldata = calldata - location = location + 24 + calldataLength.toI32() + location = location + 48 + calldataLength * 2 } } -export function saveRewards(voteId: string, script: string): void { - const scriptBytes = Bytes.fromHexString(script) - - let location = 4 +export function saveRewards(voteId: string, script: string): void { + let location = 10 while (location < script.length) { - const token = new Address(scriptBytes.slice(location, location + 20)) - const to = new Address(scriptBytes.slice(location + 20, location + 40)) - const amount = new BigInt(scriptBytes.slice(location + 40, location + 72)) + let token = Address.fromHexString(script.substr(location, location + 40)) as Address + let to = Address.fromHexString(script.substr(location + 40, location + 80)) as Address + let amount = BigInt.fromUnsignedBytes(Bytes.fromHexString(script.substr(location + 80, location + 144)) as Bytes) let reward = new RewardEntity(buildRewardId(voteId, token, to)) reward.amount = amount @@ -93,10 +89,10 @@ export function saveRewards(voteId: string, script: string): void { export function handleCastVote(event: CastVoteEvent): void { updateVoteState(event.address, event.params.voteId) - const voter = loadOrCreateVoter(event.address, event.params.voter) + let voter = loadOrCreateVoter(event.address, event.params.voter) voter.save() - const castVote = loadOrCreateCastVote( + let castVote = loadOrCreateCastVote( event.address, event.params.voteId, event.params.voter @@ -113,7 +109,7 @@ export function handleCastVote(event: CastVoteEvent): void { export function handleExecuteVote(event: ExecuteVoteEvent): void { updateVoteState(event.address, event.params.voteId) - const vote = VoteEntity.load( + let vote = VoteEntity.load( buildVoteEntityId(event.address, event.params.voteId) )! vote.executed = true @@ -149,7 +145,7 @@ function loadOrCreateVoter( votingAddress: Address, voterAddress: Address ): VoterEntity { - const voterId = buildVoterId(votingAddress, voterAddress) + let voterId = buildVoterId(votingAddress, voterAddress) let voter = VoterEntity.load(voterId) if (voter === null) { voter = new VoterEntity(voterId) @@ -163,7 +159,7 @@ function loadOrCreateCastVote( voteId: BigInt, voterAddress: Address ): CastEntity { - const castVoteId = buildCastEntityId(voteId, voterAddress) + let castVoteId = buildCastEntityId(voteId, voterAddress) let castVote = CastEntity.load(castVoteId) if (castVote === null) { castVote = new CastEntity(castVoteId) @@ -173,10 +169,10 @@ function loadOrCreateCastVote( } export function updateVoteState(votingAddress: Address, voteId: BigInt): void { - const votingApp = VotingContract.bind(votingAddress) - const voteData = votingApp.getVote(voteId) + let votingApp = VotingContract.bind(votingAddress) + let voteData = votingApp.getVote(voteId) - const vote = VoteEntity.load(buildVoteEntityId(votingAddress, voteId))! + let vote = VoteEntity.load(buildVoteEntityId(votingAddress, voteId))! vote.yea = voteData.value6 vote.nay = voteData.value7 From b1dc298faa839b5f485cf8d78cfcf50b58df51f6 Mon Sep 17 00:00:00 2001 From: Parker Lambert Date: Thu, 18 Nov 2021 18:33:00 -0500 Subject: [PATCH 5/7] Add rewards and evmscripts to Vote. --- packages/connect-voting/subgraph/schema.graphql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/connect-voting/subgraph/schema.graphql b/packages/connect-voting/subgraph/schema.graphql index 8bf05971..9aaa949e 100644 --- a/packages/connect-voting/subgraph/schema.graphql +++ b/packages/connect-voting/subgraph/schema.graphql @@ -18,6 +18,8 @@ type Vote @entity { voteNum: BigInt! castVotes: [Cast!] @derivedFrom(field: "vote") spec: BigInt! + rewards: [Reward!] @derivedFrom(field: "vote") + evmScripts: [EvmScript!] @derivedFrom(field: "vote") } type Reward @entity { From 2d256dae6822e41adfe9b4e5d93de20952c0e3d6 Mon Sep 17 00:00:00 2001 From: Parker Lambert Date: Mon, 29 Nov 2021 17:57:25 -0500 Subject: [PATCH 6/7] Complete API requirements. --- packages/connect-voting/src/models/Call.ts | 15 ++++ .../connect-voting/src/models/EvmScript.ts | 17 ---- packages/connect-voting/src/models/Reward.ts | 2 +- packages/connect-voting/src/models/Vote.ts | 26 ++++++ .../connect-voting/src/thegraph/connector.ts | 55 ++++++++++++- .../src/thegraph/parsers/calls.ts | 27 ++++++ .../src/thegraph/parsers/index.ts | 3 +- .../src/thegraph/parsers/rewards.ts | 51 ++++++------ .../src/thegraph/queries/index.ts | 29 ++++++- packages/connect-voting/src/types.ts | 17 +++- .../connect-voting/subgraph/schema.graphql | 13 ++- .../connect-voting/subgraph/src/Voting.ts | 82 +++++++++++-------- 12 files changed, 248 insertions(+), 89 deletions(-) create mode 100644 packages/connect-voting/src/models/Call.ts delete mode 100644 packages/connect-voting/src/models/EvmScript.ts create mode 100644 packages/connect-voting/src/thegraph/parsers/calls.ts diff --git a/packages/connect-voting/src/models/Call.ts b/packages/connect-voting/src/models/Call.ts new file mode 100644 index 00000000..997b0e72 --- /dev/null +++ b/packages/connect-voting/src/models/Call.ts @@ -0,0 +1,15 @@ +import { CallData } from '../types' + +export default class Call { + readonly id: string + readonly vote: string + readonly contract: string + readonly calldata: string + + constructor(data: CallData) { + this.id = data.id + this.vote = data.vote.id + this.contract = data.contract + this.calldata = data.calldata + } +} diff --git a/packages/connect-voting/src/models/EvmScript.ts b/packages/connect-voting/src/models/EvmScript.ts deleted file mode 100644 index 0701ee7b..00000000 --- a/packages/connect-voting/src/models/EvmScript.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { EvmScriptData } from '../types' - -export default class EvmScript { - readonly id: string - readonly vote: string - readonly contract: string - readonly calldataLength: string - readonly calldata: string - - constructor(data: EvmScriptData) { - this.id = data.id - this.vote = data.vote - this.contract = data.contract - this.calldataLength = data.calldataLength - this.calldata = data.calldata - } -} diff --git a/packages/connect-voting/src/models/Reward.ts b/packages/connect-voting/src/models/Reward.ts index 0f91a66a..92d3a357 100644 --- a/packages/connect-voting/src/models/Reward.ts +++ b/packages/connect-voting/src/models/Reward.ts @@ -9,7 +9,7 @@ export default class Reward { constructor(data: RewardData) { this.id = data.id - this.vote = data.vote + this.vote = data.vote.id this.token = data.token this.to = data.to this.amount = data.amount diff --git a/packages/connect-voting/src/models/Vote.ts b/packages/connect-voting/src/models/Vote.ts index 1ac6c616..d6438683 100644 --- a/packages/connect-voting/src/models/Vote.ts +++ b/packages/connect-voting/src/models/Vote.ts @@ -3,6 +3,7 @@ import { subscription } from '@aragon/connect-core' import { IVotingConnector, VoteData, RewardData } from '../types' import Cast from './Cast' import Reward from './Reward' +import Call from './Call' export default class Vote { #connector: IVotingConnector @@ -55,4 +56,29 @@ export default class Vote { this.#connector.onCastsForVote(this.id, first, skip, callback) ) } + + async rewards({ first = 1000, skip = 0 } = {}): Promise { + return this.#connector.rewardsForVote(this.id, first, skip) + } + + onRewards( + { first = 1000, skip = 0 } = {}, + callback?: SubscriptionCallback + ): SubscriptionResult { + return subscription(callback, (callback) => + this.#connector.onRewardsForVote(this.id, first, skip, callback) + ) + } + async calls({ first = 1000, skip = 0 } = {}): Promise { + return this.#connector.callsForVote(this.id, first, skip) + } + + onCalls( + { first = 1000, skip = 0 } = {}, + callback?: SubscriptionCallback + ): SubscriptionResult { + return subscription(callback, (callback) => + this.#connector.onCallsForVote(this.id, first, skip, callback) + ) + } } diff --git a/packages/connect-voting/src/thegraph/connector.ts b/packages/connect-voting/src/thegraph/connector.ts index 978eed0d..c28b9ade 100644 --- a/packages/connect-voting/src/thegraph/connector.ts +++ b/packages/connect-voting/src/thegraph/connector.ts @@ -8,7 +8,8 @@ import Vote from '../models/Vote' import Cast from '../models/Cast' import Reward from '../models/Reward' import * as queries from './queries' -import { parseVotes, parseCasts, parseRewards } from './parsers' +import { parseVotes, parseCasts, parseRewards, parseCalls } from './parsers' +import Call from '../models/Call' export function subgraphUrlFromChainId(chainId: number) { if (chainId === 1) { @@ -99,4 +100,56 @@ export default class VotingConnectorTheGraph implements IVotingConnector { (result: QueryResult) => parseCasts(result) ) } + + async rewardsForVote( + vote: string, + first: number, + skip: number + ): Promise { + return this.#gql.performQueryWithParser( + queries.REWARDS_FOR_VOTE('query'), + { vote, first, skip }, + (result: QueryResult) => parseRewards(result) + ) + } + + onRewardsForVote( + vote: string, + first: number, + skip: number, + callback: SubscriptionCallback + ): SubscriptionHandler { + return this.#gql.subscribeToQueryWithParser( + queries.REWARDS_FOR_VOTE('subscription'), + { vote, first, skip }, + callback, + (result: QueryResult) => parseRewards(result) + ) + } + + async callsForVote( + vote: string, + first: number, + skip: number + ): Promise { + return this.#gql.performQueryWithParser( + queries.CALLS_FOR_VOTE('query'), + { vote, first, skip }, + (result: QueryResult) => parseCalls(result) + ) + } + + onCallsForVote( + vote: string, + first: number, + skip: number, + callback: SubscriptionCallback + ): SubscriptionHandler { + return this.#gql.subscribeToQueryWithParser( + queries.CALLS_FOR_VOTE('subscription'), + { vote, first, skip }, + callback, + (result: QueryResult) => parseCalls(result) + ) + } } diff --git a/packages/connect-voting/src/thegraph/parsers/calls.ts b/packages/connect-voting/src/thegraph/parsers/calls.ts new file mode 100644 index 00000000..9f1185bf --- /dev/null +++ b/packages/connect-voting/src/thegraph/parsers/calls.ts @@ -0,0 +1,27 @@ +import { ErrorUnexpectedResult } from 'packages/connect-core/dist/cjs' +import { QueryResult } from 'packages/connect-thegraph/dist/cjs' +import Call from '../../models/Call' +import { CallData } from '../../types' + +export function parseCalls(result: QueryResult): Call[] { + const calls = result.data.calls + + if (!calls) { + throw new ErrorUnexpectedResult('Unable to parse calls.') + } + + const datas = calls.map( + (call: any): CallData => { + return { + id: call.id, + vote: call.vote, + contract: call.contract, + calldata: call.calldata, + } + } + ) + + return datas.map((data: CallData) => { + return new Call(data) + }) +} diff --git a/packages/connect-voting/src/thegraph/parsers/index.ts b/packages/connect-voting/src/thegraph/parsers/index.ts index 39cfb8a2..b613de73 100644 --- a/packages/connect-voting/src/thegraph/parsers/index.ts +++ b/packages/connect-voting/src/thegraph/parsers/index.ts @@ -1,3 +1,4 @@ export { parseVotes } from './votes' export { parseCasts } from './casts' -export { parseRewards } from './rewards' \ No newline at end of file +export { parseRewards } from './rewards' +export { parseCalls } from './calls' diff --git a/packages/connect-voting/src/thegraph/parsers/rewards.ts b/packages/connect-voting/src/thegraph/parsers/rewards.ts index ee9c6a35..f05da2c0 100644 --- a/packages/connect-voting/src/thegraph/parsers/rewards.ts +++ b/packages/connect-voting/src/thegraph/parsers/rewards.ts @@ -1,29 +1,28 @@ -import { ErrorUnexpectedResult } from "packages/connect-core/dist/cjs" -import { QueryResult } from "packages/connect-thegraph/dist/cjs" -import Reward from "../../models/Reward" -import { RewardData } from "../../types" +import { ErrorUnexpectedResult } from 'packages/connect-core/dist/cjs' +import { QueryResult } from 'packages/connect-thegraph/dist/cjs' +import Reward from '../../models/Reward' +import { RewardData } from '../../types' export function parseRewards(result: QueryResult): Reward[] { - const rewards = result.data.rewards - - if (!rewards) { - throw new ErrorUnexpectedResult('Unable to parse rewards.') - } - - const datas = rewards.map( - (reward: any): RewardData => { - return { - id: reward.id, - vote: reward.vote, - token: reward.token, - to: reward.to, - amount: reward.amount - } - } - ) - - return datas.map((data: RewardData) => { - return new Reward(data) - }) + const rewards = result.data.rewards + + if (!rewards) { + throw new ErrorUnexpectedResult('Unable to parse rewards.') } - \ No newline at end of file + + const datas = rewards.map( + (reward: any): RewardData => { + return { + id: reward.id, + vote: reward.vote.id, + token: reward.token, + to: reward.to, + amount: reward.amount, + } + } + ) + + return datas.map((data: RewardData) => { + return new Reward(data) + }) +} diff --git a/packages/connect-voting/src/thegraph/queries/index.ts b/packages/connect-voting/src/thegraph/queries/index.ts index 987f3ce8..483227fa 100644 --- a/packages/connect-voting/src/thegraph/queries/index.ts +++ b/packages/connect-voting/src/thegraph/queries/index.ts @@ -50,8 +50,6 @@ export const CASTS_FOR_VOTE = (type: string) => gql` votingPower script spec - contract - calldata } voter { id @@ -63,3 +61,30 @@ export const CASTS_FOR_VOTE = (type: string) => gql` } } ` + +export const REWARDS_FOR_VOTE = (type: string) => gql` + ${type} Rewards($vote: ID!, $first: Int!, $skip: Int!) { + rewards(where: { vote: $vote }, first: $first, skip: $skip) { + id + vote { + id + } + token + amount + to + } + } +` + +export const CALLS_FOR_VOTE = (type: string) => gql` + ${type} Calls($vote: ID!, $first: Int!, $skip: Int!) { + calls(where: { vote: $vote }, first: $first, skip: $skip) { + id + vote { + id + } + contract + calldata + } + } +` diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index bd0ec1bd..e7d6a4f1 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -5,6 +5,7 @@ import { import Vote from './models/Vote' import Cast from './models/Cast' import Reward from './models/Reward' +import Call from './models/Call' export interface VoteData { id: string @@ -32,7 +33,7 @@ export interface RewardData { amount: string } -export interface EvmScriptData { +export interface CallData { id: string vote: string contract: string @@ -70,4 +71,18 @@ export interface IVotingConnector { skip: number, callback: SubscriptionCallback ): SubscriptionHandler + rewardsForVote(vote: string, first: number, skip: number): Promise + onRewardsForVote( + vote: string, + first: number, + skip: number, + callback: SubscriptionCallback + ): SubscriptionHandler + callsForVote(vote: string, first: number, skip: number): Promise + onCallsForVote( + vote: string, + first: number, + skip: number, + callback: SubscriptionCallback + ): SubscriptionHandler } diff --git a/packages/connect-voting/subgraph/schema.graphql b/packages/connect-voting/subgraph/schema.graphql index 9aaa949e..60b9f2e9 100644 --- a/packages/connect-voting/subgraph/schema.graphql +++ b/packages/connect-voting/subgraph/schema.graphql @@ -16,26 +16,25 @@ type Vote @entity { votingPower: BigInt! script: String! voteNum: BigInt! - castVotes: [Cast!] @derivedFrom(field: "vote") spec: BigInt! + castVotes: [Cast!] @derivedFrom(field: "vote") rewards: [Reward!] @derivedFrom(field: "vote") - evmScripts: [EvmScript!] @derivedFrom(field: "vote") + calls: [Call!] @derivedFrom(field: "vote") } type Reward @entity { id: ID! + vote: Vote! token: Bytes! - to: Bytes! amount: BigInt! - vote: Vote! + to: Bytes! } -type EvmScript @entity { +type Call @entity { id: ID! + vote: Vote! contract: Bytes! - calldataLength: BigInt! calldata: Bytes! - vote: Vote! } type Cast @entity { diff --git a/packages/connect-voting/subgraph/src/Voting.ts b/packages/connect-voting/subgraph/src/Voting.ts index 68611a32..1af56d1f 100644 --- a/packages/connect-voting/subgraph/src/Voting.ts +++ b/packages/connect-voting/subgraph/src/Voting.ts @@ -10,14 +10,9 @@ import { Cast as CastEntity, Voter as VoterEntity, Reward as RewardEntity, - EvmScript as EvmScriptEntity + Call as CallEntity, } from '../generated/schema' -/* eslint-disable @typescript-eslint/no-use-before-define */ - -let REWARDS_SCRIPT_ID = 0x00000000 -let EVM_SCRIPT_ID = 0x00000001 - export function handleStartVote(event: StartVoteEvent): void { let voteEntityId = buildVoteEntityId(event.address, event.params.voteId) let vote = new VoteEntity(voteEntityId) @@ -40,49 +35,72 @@ export function handleStartVote(event: StartVoteEvent): void { vote.orgAddress = voting.kernel() vote.executedAt = BigInt.fromI32(0) vote.executed = false - vote.spec = BigInt.fromI32(Bytes.fromHexString(vote.script.substr(0, 10)).toI32()) + vote.spec = BigInt.fromI32( + Bytes.fromHexString(vote.script.substr(0, 10)).toI32() + ) vote.save() - switch(vote.spec.toI32()) { - case REWARDS_SCRIPT_ID: + let REWARD_SPEC_ID = 0x00000000 + let CALL_SPEC_ID = 0x00000001 + + switch (vote.spec.toI32()) { + case REWARD_SPEC_ID: saveRewards(vote.id, vote.script) - break; - case EVM_SCRIPT_ID: - saveScripts(vote.id, vote.script) - break; + break + case CALL_SPEC_ID: + saveCalls(vote.id, vote.script) + break } } -export function saveScripts(voteId: string, script: string): void { +export function saveCalls(voteId: string, script: string): void { let location = 10 while (location < script.length) { - let contract = Bytes.fromHexString(script.substr(location, location + 40)) as Address - let calldataLength = Bytes.fromHexString(script.substr(location + 40, location + 48)).toU32() - let calldata = Bytes.fromHexString(script.substr(location + 48, location + 48 + calldataLength * 2 )) as Bytes - - let evmScript = new EvmScriptEntity(buildEvmScriptEntityId(voteId, Bytes.fromHexString(script.substr(location, location + 48 + calldataLength * 2)).toHexString())) - evmScript.contract = contract - evmScript.calldataLength = new BigInt(calldataLength) - evmScript.calldata = calldata - location = location + 48 + calldataLength * 2 + let contract = Address.fromHexString(script.substr(location, 40)) as Address + let calldataLength = BigInt.fromUnsignedBytes( + Bytes.fromHexString(script.substr(location + 40, 8)) as Bytes + ) + let calldataLengthNumber = calldataLength.toI32() + let calldata = Bytes.fromHexString( + script.substr(location + 48, calldataLengthNumber * 2) + ) as Bytes + + let call = new CallEntity( + buildCallEntityId( + voteId, + Bytes.fromHexString( + script.substr(location, calldataLengthNumber * 2) + `-${location}` + ).toHexString() + ) + ) + + call.contract = contract + call.calldata = calldata + call.vote = voteId + call.save() + location = location + 48 + calldataLengthNumber * 2 } } -export function saveRewards(voteId: string, script: string): void { +export function saveRewards(voteId: string, script: string): void { let location = 10 while (location < script.length) { - let token = Address.fromHexString(script.substr(location, location + 40)) as Address - let to = Address.fromHexString(script.substr(location + 40, location + 80)) as Address - let amount = BigInt.fromUnsignedBytes(Bytes.fromHexString(script.substr(location + 80, location + 144)) as Bytes) + let token = Address.fromHexString(script.substr(location, 40)) as Address + let to = Address.fromHexString(script.substr(location + 40, 40)) as Address + let amount = BigInt.fromUnsignedBytes( + Bytes.fromHexString(script.substr(location + 80, 64)) as Bytes + ) let reward = new RewardEntity(buildRewardId(voteId, token, to)) + reward.token = token reward.amount = amount + reward.to = to reward.vote = voteId reward.save() - location = location + 72 + location = location + 144 } } @@ -117,10 +135,8 @@ export function handleExecuteVote(event: ExecuteVoteEvent): void { vote.save() } -function buildEvmScriptEntityId(voteId: string, data: string): string { - return ( - voteId + '-evmScript:' + data - ) +function buildCallEntityId(voteId: string, data: string): string { + return voteId + '-call:' + data } function buildVoteEntityId(appAddress: Address, voteNum: BigInt): string { @@ -138,7 +154,7 @@ function buildCastEntityId(voteId: BigInt, voter: Address): string { } function buildRewardId(voteId: string, token: Address, to: Address): string { - return voteId + '-reward-' + token.toHexString() + '-' + to.toHexString() + return voteId + '-reward-' + token.toHexString() + '-' + to.toHexString() } function loadOrCreateVoter( From 77d12478e3fbdce12f9a28e7f8d4f6a536ea01bd Mon Sep 17 00:00:00 2001 From: Parker Lambert Date: Mon, 29 Nov 2021 18:40:41 -0500 Subject: [PATCH 7/7] Include a few misses. --- packages/connect-voting/src/thegraph/parsers/calls.ts | 2 +- packages/connect-voting/src/types.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/connect-voting/src/thegraph/parsers/calls.ts b/packages/connect-voting/src/thegraph/parsers/calls.ts index 9f1185bf..9c51fb68 100644 --- a/packages/connect-voting/src/thegraph/parsers/calls.ts +++ b/packages/connect-voting/src/thegraph/parsers/calls.ts @@ -14,7 +14,7 @@ export function parseCalls(result: QueryResult): Call[] { (call: any): CallData => { return { id: call.id, - vote: call.vote, + vote: call.vote.id, contract: call.contract, calldata: call.calldata, } diff --git a/packages/connect-voting/src/types.ts b/packages/connect-voting/src/types.ts index e7d6a4f1..573333e7 100644 --- a/packages/connect-voting/src/types.ts +++ b/packages/connect-voting/src/types.ts @@ -27,7 +27,7 @@ export interface VoteData { export interface RewardData { id: string - vote: string + vote: VoteData token: string to: string amount: string @@ -35,9 +35,8 @@ export interface RewardData { export interface CallData { id: string - vote: string + vote: VoteData contract: string - calldataLength: string calldata: string }