diff --git a/subgraphs/venus-governance/schema.graphql b/subgraphs/venus-governance/schema.graphql index c8eb3bb2..85fe87aa 100644 --- a/subgraphs/venus-governance/schema.graphql +++ b/subgraphs/venus-governance/schema.graphql @@ -1,11 +1,3 @@ -enum ProposalStatus { - PENDING - ACTIVE - CANCELLED - QUEUED - EXECUTED -} - type Delegate @entity { "A Delegate is any address that has been delegated with voting tokens by a token holder, id is the blockchain address of said delegate" id: ID! @@ -66,11 +58,17 @@ type Proposal @entity { "String description of the change" description: String! - "Status of the proposal" - status: ProposalStatus! - "Once the proposal is queued for execution it will have an ETA of the execution" - executionETA: BigInt + executionEta: BigInt + + "Whether a proposal has been queued" + queued: Boolean + + "Whether a proposal has been canceled" + canceled: Boolean + + "Whether a proposal has been executed" + executed: Boolean "Votes associated to this proposal" votes: [Vote!]! @derivedFrom(field: "proposal") @@ -117,9 +115,6 @@ type Governance @entity { "Total number of votes delegated expressed in the smallest unit of XVS" totalVotesMantissa: BigInt! - - "Number of proposals currently queued for execution" - proposalsQueued: BigInt! "The number of votes required to reach quorum" quorumVotesMantissa: BigInt! diff --git a/subgraphs/venus-governance/src/constants/index.ts b/subgraphs/venus-governance/src/constants/index.ts index 1206978a..0e2165d0 100644 --- a/subgraphs/venus-governance/src/constants/index.ts +++ b/subgraphs/venus-governance/src/constants/index.ts @@ -3,11 +3,6 @@ import { BigDecimal, BigInt } from '@graphprotocol/graph-ts'; export const BIGINT_ZERO = BigInt.fromI32(0); export const BIGINT_ONE = BigInt.fromI32(1); export const BIGDECIMAL_ZERO = new BigDecimal(BIGINT_ZERO); -export const PENDING = 'PENDING'; -export const CANCELLED = 'CANCELLED'; -export const EXECUTED = 'EXECUTED'; -export const QUEUED = 'QUEUED'; -export const ACTIVE = 'ACTIVE'; // Vote support export const FOR = 'FOR'; diff --git a/subgraphs/venus-governance/src/mappings/alpha.ts b/subgraphs/venus-governance/src/mappings/alpha.ts index 0fc8ddc7..25f19bcb 100644 --- a/subgraphs/venus-governance/src/mappings/alpha.ts +++ b/subgraphs/venus-governance/src/mappings/alpha.ts @@ -7,14 +7,12 @@ import { ProposalQueued, VoteCast, } from '../../generated/GovernorAlpha/GovernorAlpha'; -import { ACTIVE, CANCELLED, PENDING } from '../constants'; import { createProposal, createVoteAlpha } from '../operations/create'; -import { getProposal } from '../operations/get'; import { getOrCreateDelegate } from '../operations/getOrCreate'; import { + updateProposalCanceled, updateProposalExecuted, updateProposalQueued, - updateProposalStatus, } from '../operations/update'; // - event: ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string) @@ -39,8 +37,7 @@ export function handleProposalCreated(event: ProposalCreated): void { // handler: handleProposalCanceled export function handleProposalCanceled(event: ProposalCanceled): void { - const proposalId = event.params.id.toString(); - updateProposalStatus(proposalId, CANCELLED); + updateProposalCanceled(event); } // - event: ProposalQueued(uint256,uint256) @@ -64,18 +61,8 @@ 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.toHexString()); createVoteAlpha(event); - const proposalId = event.params.proposalId.toString(); - const proposal = getProposal(proposalId); - if (proposal.status == PENDING) { - updateProposalStatus(proposalId, ACTIVE); - } } export function handleVoteCastV2(event: VoteCast): void { createVoteAlpha(event); - const proposalId = event.params.proposalId.toString(); - const proposal = getProposal(proposalId); - if (proposal.status == PENDING) { - updateProposalStatus(proposalId, ACTIVE); - } } diff --git a/subgraphs/venus-governance/src/mappings/bravo.ts b/subgraphs/venus-governance/src/mappings/bravo.ts index 90c12dba..bd0a1bee 100644 --- a/subgraphs/venus-governance/src/mappings/bravo.ts +++ b/subgraphs/venus-governance/src/mappings/bravo.ts @@ -11,14 +11,14 @@ import { ProposalQueued, VoteCast, } from '../../generated/GovernorBravoDelegate/GovernorBravoDelegate'; -import { ACTIVE, CANCELLED, CRITICAL, FAST_TRACK, NORMAL, PENDING } from '../constants'; +import { CRITICAL, FAST_TRACK, NORMAL } from '../constants'; import { createProposal, createVoteBravo } from '../operations/create'; -import { getGovernanceEntity, getProposal } from '../operations/get'; +import { getGovernanceEntity } from '../operations/get'; import { getOrCreateDelegate } from '../operations/getOrCreate'; import { + updateProposalCanceled, updateProposalExecuted, updateProposalQueued, - updateProposalStatus, } from '../operations/update'; export function handleProposalCreated(event: ProposalCreated): void { @@ -35,8 +35,7 @@ export function handleProposalCreatedV2(event: ProposalCreatedV2): void { } export function handleProposalCanceled(event: ProposalCanceled): void { - const proposalId = event.params.id.toString(); - updateProposalStatus(proposalId, CANCELLED); + updateProposalCanceled(event); } export function handleProposalQueued(event: ProposalQueued): void { @@ -49,11 +48,6 @@ export function handleProposalExecuted(event: ProposalExecuted): void { export function handleBravoVoteCast(event: VoteCast): void { createVoteBravo(event); - const proposalId = event.params.proposalId.toString(); - const proposal = getProposal(proposalId); - if (proposal.status == PENDING) { - updateProposalStatus(proposalId, ACTIVE); - } } 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 1d825cf5..3d5e4e93 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, ACTIVE, AGAINST, BIGINT_ONE, FOR, NORMAL, PENDING } from '../constants'; +import { ABSTAIN, AGAINST, BIGINT_ONE, FOR, NORMAL } from '../constants'; import { getVoteId } from '../utilities/ids'; import { getDelegate, getGovernanceEntity, getProposal } from './get'; @@ -26,7 +26,9 @@ export function createProposal(event: E): Proposal { proposal.startBlock = event.params.startBlock; proposal.endBlock = event.params.endBlock; proposal.description = event.params.description; - proposal.status = event.block.number >= proposal.startBlock ? ACTIVE : PENDING; + proposal.queued = false; + proposal.canceled = false; + proposal.executed = false; proposal.type = NORMAL; proposal.save(); diff --git a/subgraphs/venus-governance/src/operations/update.ts b/subgraphs/venus-governance/src/operations/update.ts index c5d8ccb4..529362bc 100644 --- a/subgraphs/venus-governance/src/operations/update.ts +++ b/subgraphs/venus-governance/src/operations/update.ts @@ -1,49 +1,35 @@ -import { BIGINT_ONE, BIGINT_ZERO, CANCELLED, EXECUTED, QUEUED } from '../constants'; +import { BIGINT_ONE } from '../constants'; import { nullAddress } from '../constants/addresses'; import { getGovernanceEntity, getProposal } from './get'; import { getOrCreateDelegate } from './getOrCreate'; -export const updateProposalStatus = (id: string, status: string): void => { - const proposal = getProposal(id); - proposal.status = status; - proposal.save(); -}; - export function updateProposalCanceled(event: E): void { const params = event.params; const proposal = getProposal(params.id.toString()); - proposal.status = CANCELLED; + proposal.canceled = true; proposal.save(); } export function updateProposalQueued(event: E): void { const params = event.params; - const governance = getGovernanceEntity(); const proposal = getProposal(params.id.toString()); - proposal.status = QUEUED; - proposal.executionETA = params.eta; + proposal.queued = true; + proposal.executionEta = params.eta; proposal.save(); - - governance.proposalsQueued = governance.proposalsQueued.plus(BIGINT_ONE); - governance.save(); } export function updateProposalExecuted(event: E): void { const params = event.params; - const governance = getGovernanceEntity(); const proposal = getProposal(params.id.toString()); - proposal.status = EXECUTED; - proposal.executionETA = null; + proposal.executed = true; proposal.save(); - - governance.proposalsQueued = governance.proposalsQueued.minus(BIGINT_ONE); - governance.save(); } export function updateDelegateChanged(event: E): void { + const governance = getGovernanceEntity(); const params = event.params; const fromDelegate = params.fromDelegate.toHexString(); const toDelegate = params.toDelegate.toHexString(); @@ -59,6 +45,9 @@ export function updateDelegateChanged(event: E): void { const oldDelegate = oldDelegateResult.entity; oldDelegate.delegateCount = oldDelegate.delegateCount - 1; oldDelegate.save(); + + governance.totalDelegates = governance.totalDelegates.minus(BIGINT_ONE); + governance.save(); } if (toDelegate != nullAddress.toHexString()) { @@ -66,6 +55,9 @@ export function updateDelegateChanged(event: E): void { const newDelegate = newDelegateResult.entity; newDelegate.delegateCount = newDelegate.delegateCount + 1; newDelegate.save(); + + governance.totalDelegates = governance.totalDelegates.plus(BIGINT_ONE); + governance.save(); } } diff --git a/subgraphs/venus-governance/subgraph-client/index.ts b/subgraphs/venus-governance/subgraph-client/index.ts index ee2bb4d9..04a43258 100644 --- a/subgraphs/venus-governance/subgraph-client/index.ts +++ b/subgraphs/venus-governance/subgraph-client/index.ts @@ -1,3 +1,4 @@ +import { DocumentNode } from 'graphql'; import { Client as UrqlClient, createClient } from 'urql/core'; import { @@ -18,28 +19,36 @@ class SubgraphClient { }); } + async query(document: DocumentNode, args: Record) { + const result = await this.urqlClient.query(document, args).toPromise(); + if (result.error) { + console.error(result.error); + } + return result; + } + async getProposalById(id: string) { - const result = await this.urqlClient.query(ProposalByIdDocument, { id: id }).toPromise(); + const result = await this.query(ProposalByIdDocument, { id: id }); return result; } async getDelegateById(id: string) { - const result = await this.urqlClient.query(DelegateByIdDocument, { id: id }).toPromise(); + const result = await this.query(DelegateByIdDocument, { id: id }); return result; } async getDelegates() { - const result = await this.urqlClient.query(DelegatesDocument, {}).toPromise(); + const result = await this.query(DelegatesDocument, {}); return result; } async getProposals() { - const result = await this.urqlClient.query(ProposalsDocument, {}).toPromise(); + const result = await this.query(ProposalsDocument, {}); return result; } async getPermissions() { - const result = await this.urqlClient.query(PermissionsDocument, {}).toPromise(); + const result = await this.query(PermissionsDocument, {}); return result; } } diff --git a/subgraphs/venus-governance/tests/integration/alpha.ts b/subgraphs/venus-governance/tests/integration/alpha.ts index 11e18614..deffc404 100644 --- a/subgraphs/venus-governance/tests/integration/alpha.ts +++ b/subgraphs/venus-governance/tests/integration/alpha.ts @@ -14,14 +14,19 @@ describe('GovernorAlpha', function () { let signers: SignerWithAddress[]; let governorAlpha: Contract; let governorAlpha2: Contract; + let _: SignerWithAddress; + let user1: SignerWithAddress; + let user2: SignerWithAddress; + let user3: SignerWithAddress; + let user4: SignerWithAddress; before(async function () { - this.timeout(50000000); // sometimes it takes a long time + this.timeout(100000000); // sometimes it takes a long time governorAlpha = await ethers.getContract('GovernorAlpha'); governorAlpha2 = await ethers.getContract('GovernorAlpha2'); signers = await ethers.getSigners(); - const [_, user1, user2, user3, user4] = signers; + [_, user1, user2, user3, user4] = signers; await enfranchiseAccount(user1, scaleValue(100000, 18)); await enfranchiseAccount(user2, scaleValue(200000, 18)); @@ -53,8 +58,7 @@ describe('GovernorAlpha', function () { } = await subgraphClient.getProposalById('1'); expect(proposal.id).to.be.equal('1'); expect(proposal.description).to.be.equal('Test proposal 1'); - expect(proposal.status).to.be.equal('PENDING'); - expect(proposal.executionETA).to.be.null; + expect(proposal.executionEta).to.be.null; expect(proposal.targets).to.deep.equal([ '0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396'.toLowerCase(), ]); @@ -66,8 +70,6 @@ describe('GovernorAlpha', function () { it('index for vote cast', async function () { const [_, user1, user2, user3, user4] = signers; - const time = Date.now() + 106400; - await ethers.provider.send('evm_setNextBlockTimestamp', [time]); await mine(1); let tx = await governorAlpha.connect(user1).castVote('1', false); @@ -109,11 +111,19 @@ describe('GovernorAlpha', function () { } = await subgraphClient.getDelegateById(user4.address.toLowerCase()); expect(delegate4.proposals).to.deep.equal([{ id: '1', __typename: 'Proposal' }]); }); - }); - // @TODO Update proposal Status - // @TODO Changing delegates - // @TODO Governance + it('should transition to canceled', async () => { + await governorAlpha.connect(signers[0]).cancel('1'); + + await waitForSubgraphToBeSynced(SYNC_DELAY); + + const { + data: { proposal }, + } = await subgraphClient.getProposalById('1'); + + expect(proposal.canceled).to.equal(true); + }); + }); describe('Alpha2', function () { it('indexes created proposals - alpha2', async function () { @@ -130,8 +140,13 @@ describe('GovernorAlpha', function () { 'Test proposal 21', // description ]; - const tx = await governorAlpha2.connect(user1).propose(...vip); - await tx.wait(1); + await governorAlpha2.connect(user1).propose(...vip); + + await mine(1); + + // Voting so it passes + await governorAlpha2.connect(user4).castVote('21', true); + await governorAlpha2.connect(user3).castVote('21', true); await waitForSubgraphToBeSynced(SYNC_DELAY); @@ -141,8 +156,7 @@ describe('GovernorAlpha', function () { expect(proposal.id).to.be.equal('21'); expect(proposal.description).to.be.equal('Test proposal 21'); - expect(proposal.status).to.be.equal('PENDING'); - expect(proposal.executionETA).to.be.null; + expect(proposal.executionEta).to.be.null; expect(proposal.targets).to.deep.equal([ '0x939bD8d64c0A9583A7Dcea9933f7b21697ab6396'.toLowerCase(), ]); @@ -150,5 +164,44 @@ describe('GovernorAlpha', function () { expect(proposal.signatures).to.deep.equal(['setPendingAdmin(address)']); expect(proposal.calldatas).to.deep.equal([callData]); }); + + it('should transition to queued', async () => { + let votingPeriod = +(await governorAlpha2.votingPeriod()); + while (votingPeriod > 0) { + votingPeriod--; + await mine(1); + } + await waitForSubgraphToBeSynced(SYNC_DELAY); + + await governorAlpha2.queue(21); + + const governorAlpha2Timelock = await ethers.getContract('GovernorAlpha2Timelock'); + const eta = + (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp + + +(await governorAlpha2Timelock.delay()); + + await waitForSubgraphToBeSynced(SYNC_DELAY); + + const { + data: { proposal }, + } = await subgraphClient.getProposalById('21'); + + expect(proposal.queued).to.equal(true); + expect(proposal.executionEta).to.equal(eta.toString()); + + await ethers.provider.send('evm_setNextBlockTimestamp', [eta + 1]); + }); + + it('should transition to executed', async () => { + await governorAlpha2.execute('21'); + + await waitForSubgraphToBeSynced(SYNC_DELAY); + + const { + data: { proposal }, + } = await subgraphClient.getProposalById('21'); + + expect(proposal.executed).to.equal(true); + }); }); }); diff --git a/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql b/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql index 7442d9de..61e98f37 100644 --- a/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql +++ b/subgraphs/venus-governance/tests/integration/queries/proposalByIdQuery.graphql @@ -11,8 +11,10 @@ query ProposalById($id: ID!) { startBlock endBlock description - status - executionETA + executionEta + queued + canceled + executed votes { id votes diff --git a/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql b/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql index 83f362e7..78f26d30 100644 --- a/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql +++ b/subgraphs/venus-governance/tests/integration/queries/proposalsQuery.graphql @@ -11,8 +11,10 @@ query Proposals { startBlock endBlock description - status - executionETA + executionEta + queued + canceled + executed votes { id votes diff --git a/subgraphs/venus-governance/tests/integration/utils/constants.ts b/subgraphs/venus-governance/tests/integration/utils/constants.ts index 4ce9aad5..972629ab 100644 --- a/subgraphs/venus-governance/tests/integration/utils/constants.ts +++ b/subgraphs/venus-governance/tests/integration/utils/constants.ts @@ -3,3 +3,5 @@ export const SUBGRAPH_ACCOUNT = 'venusprotocol'; export const SUBGRAPH_NAME = 'venus-governance'; export const SYNC_DELAY = 2000; + +export const mockAddress = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'; diff --git a/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts b/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts index 6f8905ad..3ad4c8ce 100644 --- a/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts +++ b/subgraphs/venus-governance/tests/unit/Alpha/index.test.ts @@ -104,7 +104,7 @@ describe('Alpha', () => { const assertProposalDocument = (key: string, value: string): void => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('status', 'CANCELLED'); + assertProposalDocument('canceled', 'true'); }); test('queue proposal', () => { @@ -122,8 +122,8 @@ describe('Alpha', () => { assert.fieldEquals('Governance', governorBravoDelegateAddress.toHex(), key, value); }; - assertProposalDocument('status', 'QUEUED'); - assertProposalDocument('executionETA', eta.toString()); + assertProposalDocument('queued', 'true'); + assertProposalDocument('executionEta', eta.toString()); assertGovernanceDocument('proposalsQueued', '1'); }); @@ -145,8 +145,7 @@ describe('Alpha', () => { assert.fieldEquals('Governance', governorBravoDelegateAddress.toHex(), key, value); }; - assertProposalDocument('status', 'EXECUTED'); - assertProposalDocument('executionETA', 'null'); + assertProposalDocument('executed', 'true'); assertGovernanceDocument('proposalsQueued', '0'); }); diff --git a/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts b/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts index 3b7f21e6..3f265338 100644 --- a/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts +++ b/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts @@ -124,7 +124,6 @@ describe('Bravo', () => { assertProposalDocument('startBlock', `${startBlock}`); assertProposalDocument('endBlock', `${endBlock}`); assertProposalDocument('description', description); - assertProposalDocument('status', 'PENDING'); }); test('create proposal V2', () => { @@ -167,7 +166,6 @@ describe('Bravo', () => { assertProposalDocument('startBlock', `${startBlock}`); assertProposalDocument('endBlock', `${endBlock}`); assertProposalDocument('description', description); - assertProposalDocument('status', 'PENDING'); assertProposalDocument('type', 'CRITICAL'); }); @@ -196,7 +194,7 @@ describe('Bravo', () => { const assertProposalDocument = (key: string, value: string): void => { assert.fieldEquals('Proposal', '1', key, value); }; - assertProposalDocument('status', 'CANCELLED'); + assertProposalDocument('canceled', 'true'); }); test('queue proposal', () => { @@ -214,8 +212,8 @@ describe('Bravo', () => { assert.fieldEquals('Governance', governorBravoDelegateAddress.toHex(), key, value); }; - assertProposalDocument('status', 'QUEUED'); - assertProposalDocument('executionETA', eta.toString()); + assertProposalDocument('queued', 'true'); + assertProposalDocument('executionEta', eta.toString()); assertGovernanceDocument('proposalsQueued', '1'); }); @@ -237,8 +235,7 @@ describe('Bravo', () => { assert.fieldEquals('Governance', governorBravoDelegateAddress.toHex(), key, value); }; - assertProposalDocument('status', 'EXECUTED'); - assertProposalDocument('executionETA', 'null'); + assertProposalDocument('executed', 'true'); assertGovernanceDocument('proposalsQueued', '0'); });