diff --git a/docker-compose.yaml b/docker-compose.yaml index 9f938a3c..00b7d32d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -17,6 +17,7 @@ services: condition: service_started environment: ETHEREUM_REORG_THRESHOLD: 1 + GRAPH_ALLOW_NON_DETERMINISTIC_FULLTEXT_SEARCH: "true" postgres_host: postgres postgres_user: graph-node postgres_pass: let-me-in diff --git a/subgraphs/venus-governance/schema.graphql b/subgraphs/venus-governance/schema.graphql index 83317acb..e36952af 100644 --- a/subgraphs/venus-governance/schema.graphql +++ b/subgraphs/venus-governance/schema.graphql @@ -30,6 +30,28 @@ enum PROPOSAL_TYPE { CRITICAL } +type _Schema_ + @fulltext( + name: "proposalSearch" + language: en + algorithm: rank + include: [{ entity: "Proposal", fields: [{ name: "description" }] }] + ) + +type ProposalAction @entity { + "Transaction hash used as the ID" + id: ID! + + "Transaction hash for the proposal action" + txHash: Bytes! + + "Block number of the proposal action" + blockNumber: BigInt! + + "Timestamp of the transaction block" + timestamp: BigInt! +} + type Proposal @entity { "Internal proposal ID, in this implementation it seems to be a autoincremental id" id: ID! @@ -62,13 +84,25 @@ type Proposal @entity { executionEta: BigInt "Whether a proposal has been queued" - queued: Boolean + queued: ProposalAction "Whether a proposal has been canceled" - canceled: Boolean + canceled: ProposalAction "Whether a proposal has been executed" - executed: Boolean + executed: ProposalAction + + "Total of for votes on the proposal" + forVotes: BigInt! + + "Total of against votes on the proposal" + againstVotes: BigInt! + + "Total of abstain votes on the proposal" + abstainVotes: BigInt! + + "Difference between for and against" + passing: Boolean! "Votes associated to this proposal" votes: [Vote!]! @derivedFrom(field: "proposal") diff --git a/subgraphs/venus-governance/src/mappings/alpha.ts b/subgraphs/venus-governance/src/mappings/alpha.ts index eb965c7f..1b107312 100644 --- a/subgraphs/venus-governance/src/mappings/alpha.ts +++ b/subgraphs/venus-governance/src/mappings/alpha.ts @@ -10,6 +10,7 @@ import { import { createProposal, createVoteAlpha } from '../operations/create'; import { getOrCreateDelegate } from '../operations/getOrCreate'; import { + updateAlphaProposalVotes, updateProposalCanceled, updateProposalExecuted, updateProposalQueued, @@ -61,8 +62,10 @@ export function handleVoteCast(event: VoteCast): void { // Alpha V1 doesn't require staking in the vault so we need to create delegates when casting a vote getOrCreateDelegate(event.params.voter); createVoteAlpha(event); + updateAlphaProposalVotes(event.params.proposalId, event.params.votes, event.params.support); } export function handleVoteCastV2(event: VoteCast): void { createVoteAlpha(event); + updateAlphaProposalVotes(event.params.proposalId, event.params.votes, event.params.support); } diff --git a/subgraphs/venus-governance/src/mappings/bravo.ts b/subgraphs/venus-governance/src/mappings/bravo.ts index 07c36c67..283572dc 100644 --- a/subgraphs/venus-governance/src/mappings/bravo.ts +++ b/subgraphs/venus-governance/src/mappings/bravo.ts @@ -16,6 +16,7 @@ import { createProposal, createVoteBravo } from '../operations/create'; import { getGovernanceEntity } from '../operations/get'; import { getOrCreateDelegate } from '../operations/getOrCreate'; import { + updateBravoProposalVotes, updateGovernanceEntity, updateProposalCanceled, updateProposalExecuted, @@ -49,6 +50,7 @@ export function handleProposalExecuted(event: ProposalExecuted): void { export function handleBravoVoteCast(event: VoteCast): void { createVoteBravo(event); + updateBravoProposalVotes(event.params.proposalId, event.params.votes, event.params.support); } export function handleNewImplementation(event: NewImplementation): void { diff --git a/subgraphs/venus-governance/src/operations/create.ts b/subgraphs/venus-governance/src/operations/create.ts index 3ee5cae8..b6fb5505 100644 --- a/subgraphs/venus-governance/src/operations/create.ts +++ b/subgraphs/venus-governance/src/operations/create.ts @@ -3,7 +3,7 @@ import { Address, Bytes } from '@graphprotocol/graph-ts'; import { VoteCast as VoteCastAlpha } from '../../generated/GovernorAlpha/GovernorAlpha'; import { VoteCast as VoteCastBravo } from '../../generated/GovernorBravoDelegate/GovernorBravoDelegate'; import { Proposal, Vote } from '../../generated/schema'; -import { ABSTAIN, AGAINST, BIGINT_ONE, FOR, NORMAL } from '../constants'; +import { ABSTAIN, AGAINST, BIGINT_ONE, BIGINT_ZERO, FOR, NORMAL } from '../constants'; import { getVoteId } from '../utilities/ids'; import { getGovernanceEntity, getProposal } from './get'; import { getOrCreateDelegate } from './getOrCreate'; @@ -27,10 +27,11 @@ export function createProposal(event: E): Proposal { proposal.startBlock = event.params.startBlock; proposal.endBlock = event.params.endBlock; proposal.description = event.params.description; - proposal.queued = false; - proposal.canceled = false; - proposal.executed = false; proposal.type = NORMAL; + proposal.forVotes = BIGINT_ZERO; + proposal.againstVotes = BIGINT_ZERO; + proposal.abstainVotes = BIGINT_ZERO; + proposal.passing = false; proposal.save(); diff --git a/subgraphs/venus-governance/src/operations/update.ts b/subgraphs/venus-governance/src/operations/update.ts index 090390a5..1336cf49 100644 --- a/subgraphs/venus-governance/src/operations/update.ts +++ b/subgraphs/venus-governance/src/operations/update.ts @@ -1,5 +1,7 @@ +import { BigInt } from '@graphprotocol/graph-ts'; + import { GovernorBravoDelegate2 } from '../../generated/GovernorBravoDelegate2/GovernorBravoDelegate2'; -import { Governance } from '../../generated/schema'; +import { Governance, ProposalAction } from '../../generated/schema'; import { BIGINT_ONE } from '../constants'; import { governorBravoDelegatorAddress, nullAddress } from '../constants/addresses'; import { getGovernanceId } from '../utilities/ids'; @@ -10,7 +12,13 @@ export function updateProposalCanceled(event: E): void { const params = event.params; const proposal = getProposal(params.id.toString()); - proposal.canceled = true; + const canceledAction = new ProposalAction(event.transaction.hash.toHexString()); + canceledAction.blockNumber = event.block.number; + canceledAction.timestamp = event.block.timestamp; + canceledAction.txHash = event.transaction.hash; + canceledAction.save(); + + proposal.canceled = canceledAction.id; proposal.save(); } @@ -18,7 +26,13 @@ export function updateProposalQueued(event: E): void { const params = event.params; const proposal = getProposal(params.id.toString()); - proposal.queued = true; + const queuedAction = new ProposalAction(event.transaction.hash.toHexString()); + queuedAction.blockNumber = event.block.number; + queuedAction.timestamp = event.block.timestamp; + queuedAction.txHash = event.transaction.hash; + queuedAction.save(); + + proposal.queued = queuedAction.id; proposal.executionEta = params.eta; proposal.save(); } @@ -27,7 +41,13 @@ export function updateProposalExecuted(event: E): void { const params = event.params; const proposal = getProposal(params.id.toString()); - proposal.executed = true; + const executedAction = new ProposalAction(event.transaction.hash.toHexString()); + executedAction.blockNumber = event.block.number; + executedAction.timestamp = event.block.timestamp; + executedAction.txHash = event.transaction.hash; + executedAction.save(); + + proposal.executed = executedAction.id; proposal.save(); } @@ -94,3 +114,27 @@ export function updateGovernanceEntity(): void { governance.proposalMaxOperations = governorBravoDelegate2.proposalMaxOperations(); governance.save(); } + +export function updateAlphaProposalVotes(id: BigInt, votes: BigInt, support: boolean): void { + const proposal = getProposal(id.toString()); + if (support) { + proposal.forVotes = proposal.forVotes.plus(votes); + } else { + proposal.againstVotes = proposal.againstVotes.plus(votes); + } + proposal.passing = proposal.forVotes > proposal.againstVotes; + proposal.save(); +} + +export function updateBravoProposalVotes(id: BigInt, votes: BigInt, support: i32): void { + const proposal = getProposal(id.toString()); + if (support == 0) { + proposal.againstVotes = proposal.againstVotes.plus(votes); + } else if (support == 1) { + proposal.forVotes = proposal.forVotes.plus(votes); + } else { + proposal.abstainVotes = proposal.abstainVotes.plus(votes); + } + proposal.passing = proposal.forVotes > proposal.againstVotes; + proposal.save(); +} diff --git a/subgraphs/venus-governance/template.yaml b/subgraphs/venus-governance/template.yaml index b92fe2dc..43b243a5 100644 --- a/subgraphs/venus-governance/template.yaml +++ b/subgraphs/venus-governance/template.yaml @@ -3,6 +3,8 @@ description: Venus Governance Subgraph repository: https://github.com/protofire/venus-governance-subgraph schema: file: ./schema.graphql +features: + - fullTextSearch dataSources: - kind: ethereum/contract name: GovernorAlpha diff --git a/subgraphs/venus-governance/tests/integration/alpha.ts b/subgraphs/venus-governance/tests/integration/alpha.ts index 5dfaab3a..312a39a6 100644 --- a/subgraphs/venus-governance/tests/integration/alpha.ts +++ b/subgraphs/venus-governance/tests/integration/alpha.ts @@ -83,6 +83,11 @@ describe('GovernorAlpha', function () { } = await subgraphClient.getProposalById('1'); expect(proposal.votes.length).to.be.equal(4); + expect(proposal.forVotes).to.be.equal(scaleValue(1000000, 18).toFixed()); + expect(proposal.againstVotes).to.be.equal(scaleValue(100000, 18).toFixed()); + expect(proposal.abstainVotes).to.be.equal('0'); + expect(proposal.passing).to.be.equal(true); + const { data: { delegate: delegate1 }, } = await subgraphClient.getDelegateById(user1.address.toLowerCase()); @@ -115,7 +120,9 @@ describe('GovernorAlpha', function () { data: { proposal }, } = await subgraphClient.getProposalById('1'); - expect(proposal.canceled).to.equal(true); + expect(typeof proposal.canceled.blockNumber).to.equal('string'); + expect(typeof proposal.canceled.txHash).to.equal('string'); + expect(typeof proposal.canceled.timestamp).to.equal('string'); }); }); @@ -157,6 +164,11 @@ describe('GovernorAlpha', function () { expect(proposal.values).to.deep.equal(['0']); expect(proposal.signatures).to.deep.equal(['setPendingAdmin(address)']); expect(proposal.calldatas).to.deep.equal([callData]); + + expect(proposal.forVotes).to.be.equal(scaleValue(800000, 18).toFixed()); + expect(proposal.againstVotes).to.be.equal('0'); + expect(proposal.abstainVotes).to.be.equal('0'); + expect(proposal.passing).to.be.equal(true); }); it('should transition to queued', async () => { @@ -180,7 +192,10 @@ describe('GovernorAlpha', function () { data: { proposal }, } = await subgraphClient.getProposalById('21'); - expect(proposal.queued).to.equal(true); + expect(typeof proposal.queued.blockNumber).to.equal('string'); + expect(typeof proposal.queued.txHash).to.equal('string'); + expect(typeof proposal.queued.timestamp).to.equal('string'); + expect(proposal.executionEta).to.equal(eta.toString()); await mine(1); @@ -196,7 +211,9 @@ describe('GovernorAlpha', function () { data: { proposal }, } = await subgraphClient.getProposalById('21'); - expect(proposal.executed).to.equal(true); + expect(typeof proposal.executed.blockNumber).to.equal('string'); + expect(typeof proposal.executed.txHash).to.equal('string'); + expect(typeof proposal.executed.timestamp).to.equal('string'); }); }); }); diff --git a/subgraphs/venus-governance/tests/integration/bravo.ts b/subgraphs/venus-governance/tests/integration/bravo.ts index 9b981ad7..056740bf 100644 --- a/subgraphs/venus-governance/tests/integration/bravo.ts +++ b/subgraphs/venus-governance/tests/integration/bravo.ts @@ -4,7 +4,7 @@ import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { expect } from 'chai'; import { Contract } from 'ethers'; import { ethers, network } from 'hardhat'; -import { waitForSubgraphToBeSynced } from 'venus-subgraph-utils'; +import { scaleValue, waitForSubgraphToBeSynced } from 'venus-subgraph-utils'; import subgraphClient from '../../subgraph-client/index'; import { SYNC_DELAY, mockAddress } from './utils/constants'; @@ -102,6 +102,11 @@ describe('GovernorBravo', function () { } = await subgraphClient.getProposalById('22'); expect(proposal.votes.length).to.be.equal(2); + expect(proposal.againstVotes).to.be.equal(scaleValue(700000, 18).toFixed()); + expect(proposal.forVotes).to.be.equal(scaleValue(200000, 18).toFixed()); + expect(proposal.abstainVotes).to.be.equal('0'); + expect(proposal.passing).to.be.equal(false); + const { data: { delegate: delegate1 }, } = await subgraphClient.getDelegateById(user1.address.toLowerCase()); @@ -131,7 +136,9 @@ describe('GovernorBravo', function () { data: { proposal }, } = await subgraphClient.getProposalById('22'); - expect(proposal.canceled).to.equal(true); + expect(typeof proposal.canceled.blockNumber).to.equal('string'); + expect(typeof proposal.canceled.txHash).to.equal('string'); + expect(typeof proposal.canceled.timestamp).to.equal('string'); }); it('should index queued proposal event', async function () { @@ -171,8 +178,16 @@ describe('GovernorBravo', function () { data: { proposal }, } = await subgraphClient.getProposalById('23'); - expect(proposal.queued).to.equal(true); + expect(typeof proposal.queued.blockNumber).to.equal('string'); + expect(typeof proposal.queued.txHash).to.equal('string'); + expect(typeof proposal.queued.timestamp).to.equal('string'); expect(proposal.executionEta).to.equal(eta.toString()); + + expect(proposal.againstVotes).to.be.equal('0'); + expect(proposal.forVotes).to.be.equal(scaleValue(800000, 18).toFixed()); + expect(proposal.abstainVotes).to.be.equal('0'); + expect(proposal.passing).to.be.equal(true); + await mine(1); await ethers.provider.send('evm_setNextBlockTimestamp', [eta]); }); @@ -186,7 +201,9 @@ describe('GovernorBravo', function () { data: { proposal }, } = await subgraphClient.getProposalById('23'); - expect(proposal.executed).to.equal(true); + expect(typeof proposal.executed.blockNumber).to.equal('string'); + expect(typeof proposal.executed.txHash).to.equal('string'); + expect(typeof proposal.executed.timestamp).to.equal('string'); }); }); @@ -305,6 +322,11 @@ describe('GovernorBravo', function () { } = await subgraphClient.getProposalById('1'); expect(proposal.votes.length).to.be.equal(4); + expect(proposal.againstVotes).to.be.equal(scaleValue(100000, 18).toFixed()); + expect(proposal.forVotes).to.be.equal(scaleValue(1000000, 18).toFixed()); + expect(proposal.abstainVotes).to.be.equal('0'); + expect(proposal.passing).to.be.equal(true); + const { data: { delegate: delegate1 }, } = await subgraphClient.getDelegateById(user1.address.toLowerCase()); @@ -339,7 +361,9 @@ describe('GovernorBravo', function () { data: { proposal }, } = await subgraphClient.getProposalById('24'); - expect(proposal.canceled).to.equal(true); + expect(typeof proposal.canceled.blockNumber).to.equal('string'); + expect(typeof proposal.canceled.txHash).to.equal('string'); + expect(typeof proposal.canceled.timestamp).to.equal('string'); }); it('should index queued proposal event', async function () { @@ -386,9 +410,17 @@ describe('GovernorBravo', function () { data: { proposal }, } = await subgraphClient.getProposalById('25'); - expect(proposal.queued).to.equal(true); + expect(typeof proposal.queued.blockNumber).to.equal('string'); + expect(typeof proposal.queued.txHash).to.equal('string'); + expect(typeof proposal.queued.timestamp).to.equal('string'); + expect(proposal.executionEta).to.equal(eta.toString()); + expect(proposal.againstVotes).to.be.equal('0'); + expect(proposal.forVotes).to.be.equal(scaleValue(800000, 18).toFixed()); + expect(proposal.abstainVotes).to.be.equal('0'); + expect(proposal.passing).to.be.equal(true); + await mine(1); await ethers.provider.send('evm_setNextBlockTimestamp', [eta]); }); @@ -402,7 +434,9 @@ describe('GovernorBravo', function () { data: { proposal }, } = await subgraphClient.getProposalById('25'); - expect(proposal.executed).to.equal(true); + expect(typeof proposal.executed.blockNumber).to.equal('string'); + expect(typeof proposal.executed.txHash).to.equal('string'); + expect(typeof proposal.executed.timestamp).to.equal('string'); }); }); }); diff --git a/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql b/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql index 89b4f54f..7bee0e61 100644 --- a/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql +++ b/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql @@ -12,10 +12,26 @@ query ProposalById($id: ID!) { endBlock description executionEta - queued - canceled - executed + queued { + blockNumber + timestamp + txHash + } + canceled { + blockNumber + timestamp + txHash + } + executed { + blockNumber + timestamp + txHash + } type + forVotes + againstVotes + abstainVotes + passing votes { id votes diff --git a/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql b/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql index b2420b92..0119dd7c 100644 --- a/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql +++ b/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql @@ -12,10 +12,26 @@ query Proposals { endBlock description executionEta - queued - canceled - executed + queued { + blockNumber + timestamp + txHash + } + canceled { + blockNumber + timestamp + txHash + } + executed { + blockNumber + timestamp + txHash + } type + forVotes + againstVotes + abstainVotes + passing votes { id votes diff --git a/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts b/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts index 2e4994d8..f7de0cac 100644 --- a/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts +++ b/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts @@ -106,7 +106,7 @@ describe('Alpha', () => { const assertProposalDocument = (key: string, value: string): void => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('canceled', 'true'); + assertProposalDocument('canceled', proposalCanceledEvent.transaction.hash.toHexString()); }); test('queue proposal', () => { @@ -120,7 +120,7 @@ describe('Alpha', () => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('queued', 'true'); + assertProposalDocument('queued', proposalQueuedEvent.transaction.hash.toHexString()); assertProposalDocument('executionEta', eta.toString()); }); @@ -138,7 +138,7 @@ describe('Alpha', () => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('executed', 'true'); + assertProposalDocument('executed', proposalExecutedEvent.transaction.hash.toHexString()); }); test('vote cast', () => { @@ -154,6 +154,13 @@ describe('Alpha', () => { assert.fieldEquals('Vote', voteId, key, value); }; + const assertProposalDocument = (key: string, value: string): void => { + assert.fieldEquals('Proposal', '1', key, value); + }; + assertProposalDocument('forVotes', votes.toString()); + assertProposalDocument('abstainVotes', '0'); + assertProposalDocument('againstVotes', '0'); + assertVoteDocument('proposal', '1'); assertVoteDocument('voter', user1.toHexString()); assertVoteDocument('votes', votes.toString()); diff --git a/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts b/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts index 52b90b37..96af1478 100644 --- a/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts +++ b/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts @@ -201,7 +201,7 @@ describe('Bravo', () => { const assertProposalDocument = (key: string, value: string): void => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('canceled', 'true'); + assertProposalDocument('canceled', proposalCanceledEvent.transaction.hash.toHexString()); }); test('queue proposal', () => { @@ -215,7 +215,7 @@ describe('Bravo', () => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('queued', 'true'); + assertProposalDocument('queued', proposalQueuedEvent.transaction.hash.toHexString()); assertProposalDocument('executionEta', eta.toString()); }); @@ -233,7 +233,7 @@ describe('Bravo', () => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('executed', 'true'); + assertProposalDocument('executed', proposalExecutedEvent.transaction.hash.toHexString()); }); test('vote cast', () => { diff --git a/yarn.lock b/yarn.lock index 07679cbe..499c230f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15926,6 +15926,16 @@ __metadata: languageName: node linkType: hard +"vai-subgraph@workspace:subgraphs/vai": + version: 0.0.0-use.local + resolution: "vai-subgraph@workspace:subgraphs/vai" + dependencies: + apollo-fetch: ^0.7.0 + urql: ^3.0.3 + venus-subgraph-utils: 0.0.0 + languageName: unknown + linkType: soft + "validate-npm-package-license@npm:^3.0.1": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4"