From 25f225af78a3b289cd296d07c1548e7fbdb222ac Mon Sep 17 00:00:00 2001 From: Corey Rice Date: Tue, 24 Oct 2023 18:48:58 -0300 Subject: [PATCH] test: add governance entity tests --- subgraphs/venus-governance/schema.graphql | 19 +---- .../venus-governance/src/constants/index.ts | 4 ++ .../venus-governance/src/mappings/bravo.ts | 2 + .../venus-governance/src/operations/create.ts | 2 +- .../venus-governance/src/operations/get.ts | 71 +++---------------- .../venus-governance/src/operations/update.ts | 23 +++++- .../venus-governance/src/utilities/ids.ts | 4 +- .../venus-governance/subgraph-client/index.ts | 6 ++ .../tests/integration/bravo.ts | 35 ++++++++- .../queries/governanceQuery.graphql | 15 ++++ .../tests/integration/utils/constants.ts | 1 + .../tests/unit/Bravo/index.test.ts | 34 ++------- 12 files changed, 100 insertions(+), 116 deletions(-) create mode 100644 subgraphs/venus-governance/tests/integration/queries/governanceQuery.graphql diff --git a/subgraphs/venus-governance/schema.graphql b/subgraphs/venus-governance/schema.graphql index c0b77821..9395b2cf 100644 --- a/subgraphs/venus-governance/schema.graphql +++ b/subgraphs/venus-governance/schema.graphql @@ -105,7 +105,7 @@ type Governance @entity { id: ID! "Number of proposals created" - proposals: BigInt! + totalProposals: BigInt! "Total number of accounts delegates that can participate in governance by voting or creating proposals" totalDelegates: BigInt! @@ -135,23 +135,6 @@ type Governance @entity { proposalMaxOperations: BigInt! } -type GovernanceRoute @entity { - "Index of the governance route" - id: ID! - "Governor implementation the route belongs to" - governor: Bytes! - "Address of timelock contract for route" - timelock: Bytes! - "Queue execution delay in blocks" - queueDelayBlocks: BigInt! - "The delay before voting on a proposal may take place, once proposed, in blocks" - votingDelayBlocks: BigInt! - "The duration of voting on a proposal, in blocks" - votingPeriodBlocks: BigInt! - "The number of votes required in order for a voter to become a proposer" - proposalThresholdMantissa: BigInt! -} - enum PermissionStatus { GRANTED, REVOKED diff --git a/subgraphs/venus-governance/src/constants/index.ts b/subgraphs/venus-governance/src/constants/index.ts index 0e2165d0..02756775 100644 --- a/subgraphs/venus-governance/src/constants/index.ts +++ b/subgraphs/venus-governance/src/constants/index.ts @@ -4,6 +4,10 @@ export const BIGINT_ZERO = BigInt.fromI32(0); export const BIGINT_ONE = BigInt.fromI32(1); export const BIGDECIMAL_ZERO = new BigDecimal(BIGINT_ZERO); +// Ids +export const GOVERNANCE = 'GOVERNANCE'; +export const SEPERATOR = '-'; + // Vote support export const FOR = 'FOR'; export const AGAINST = 'AGAINST'; diff --git a/subgraphs/venus-governance/src/mappings/bravo.ts b/subgraphs/venus-governance/src/mappings/bravo.ts index d5ebaccb..07c36c67 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 { + updateGovernanceEntity, updateProposalCanceled, updateProposalExecuted, updateProposalQueued, @@ -54,6 +55,7 @@ export function handleNewImplementation(event: NewImplementation): void { const governance = getGovernanceEntity(); governance.implementation = event.params.newImplementation; governance.save(); + updateGovernanceEntity(); } export function handleNewPendingAdmin(event: NewPendingAdmin): void { diff --git a/subgraphs/venus-governance/src/operations/create.ts b/subgraphs/venus-governance/src/operations/create.ts index 2acc7f58..ec0903d5 100644 --- a/subgraphs/venus-governance/src/operations/create.ts +++ b/subgraphs/venus-governance/src/operations/create.ts @@ -13,7 +13,7 @@ export function createProposal(event: E): Proposal { const governance = getGovernanceEntity(); - governance.proposals = governance.proposals.plus(BIGINT_ONE); + governance.totalProposals = governance.totalProposals.plus(BIGINT_ONE); governance.save(); const targets = event.params.targets.map((address: Address) => Bytes.fromHexString(address.toHexString()), diff --git a/subgraphs/venus-governance/src/operations/get.ts b/subgraphs/venus-governance/src/operations/get.ts index 24711309..fe83c213 100644 --- a/subgraphs/venus-governance/src/operations/get.ts +++ b/subgraphs/venus-governance/src/operations/get.ts @@ -1,22 +1,20 @@ -import { Address, BigInt, log } from '@graphprotocol/graph-ts'; +import { Address, log } from '@graphprotocol/graph-ts'; -import { GovernorBravoDelegate2 } from '../../generated/GovernorBravoDelegate2/GovernorBravoDelegate2'; -import { Timelock } from '../../generated/GovernorBravoDelegate2/Timelock'; -import { Delegate, Governance, GovernanceRoute, Proposal } from '../../generated/schema'; +import { Delegate, Governance, Proposal } from '../../generated/schema'; import { BIGINT_ZERO } from '../constants'; -import { governorBravoDelegatorAddress, nullAddress } from '../constants/addresses'; -import { getDelegateId } from '../utilities/ids'; +import { nullAddress } from '../constants/addresses'; +import { getDelegateId, getGovernanceId } from '../utilities/ids'; /** * While technically this function does also create, we don't care because it only happens once as the id is a constant. + * The initial values are mocked because they are updated when an implementation is set * @returns Governance */ export const getGovernanceEntity = (): Governance => { - let governance = Governance.load(governorBravoDelegatorAddress.toHex()); + let governance = Governance.load(getGovernanceId()); if (!governance) { - const governorBravoDelegate2 = GovernorBravoDelegate2.bind(governorBravoDelegatorAddress); - governance = new Governance(governorBravoDelegatorAddress.toHex()); - governance.proposals = BIGINT_ZERO; + governance = new Governance(getGovernanceId()); + governance.totalProposals = BIGINT_ZERO; governance.totalDelegates = BIGINT_ZERO; governance.totalVoters = BIGINT_ZERO; governance.totalVotesMantissa = BIGINT_ZERO; @@ -26,59 +24,6 @@ export const getGovernanceEntity = (): Governance => { governance.guardian = nullAddress; governance.quorumVotesMantissa = BIGINT_ZERO; governance.proposalMaxOperations = BIGINT_ZERO; - - // There is only one active governance entity - // but while indexing proposals created with previous governance contracts all these calls will fail - // This method only exists on the latest governance interface so if it succeeds we can safely index the contract - const normalProposalConfigResult = governorBravoDelegate2.try_proposalConfigs(new BigInt(0)); - if (normalProposalConfigResult.reverted === false) { - governance.admin = governorBravoDelegate2.admin(); - governance.implementation = governorBravoDelegate2.implementation(); - governance.guardian = governorBravoDelegate2.guardian(); - governance.quorumVotesMantissa = governorBravoDelegate2.quorumVotes(); - governance.proposalMaxOperations = governorBravoDelegate2.proposalMaxOperations(); - // Governance Routes are set in initialization - // Normal - const normalProposalConfig = normalProposalConfigResult.value; - const normalTimelockAddress = governorBravoDelegate2.proposalTimelocks(new BigInt(0)); - const normalTimelock = Timelock.bind(normalTimelockAddress); - const normalGovernanceRoute = new GovernanceRoute('0'); - normalGovernanceRoute.governor = governorBravoDelegatorAddress; - normalGovernanceRoute.timelock = normalTimelockAddress; - normalGovernanceRoute.queueDelayBlocks = normalTimelock.delay(); - normalGovernanceRoute.votingDelayBlocks = normalProposalConfig.getVotingDelay(); - normalGovernanceRoute.votingPeriodBlocks = normalProposalConfig.getVotingPeriod(); - normalGovernanceRoute.proposalThresholdMantissa = normalProposalConfig.getProposalThreshold(); - normalGovernanceRoute.save(); - // Fast track - const fastTrackProposalConfig = governorBravoDelegate2.proposalConfigs(new BigInt(1)); - const fastTrackTimelockAddress = governorBravoDelegate2.proposalTimelocks(new BigInt(1)); - const fastTrackTimelock = Timelock.bind(normalTimelockAddress); - const fastTrackGovernanceRoute = new GovernanceRoute('1'); - fastTrackGovernanceRoute.governor = governorBravoDelegatorAddress; - fastTrackGovernanceRoute.timelock = fastTrackTimelockAddress; - fastTrackGovernanceRoute.queueDelayBlocks = fastTrackTimelock.delay(); - fastTrackGovernanceRoute.votingDelayBlocks = fastTrackProposalConfig.getVotingDelay(); - fastTrackGovernanceRoute.votingPeriodBlocks = fastTrackProposalConfig.getVotingPeriod(); - fastTrackGovernanceRoute.proposalThresholdMantissa = - fastTrackProposalConfig.getProposalThreshold(); - fastTrackGovernanceRoute.save(); - // Critical - const criticalProposalConfig = governorBravoDelegate2.proposalConfigs(new BigInt(2)); - const criticalTimelockAddress = governorBravoDelegate2.proposalTimelocks(new BigInt(2)); - const criticalTimelock = Timelock.bind(normalTimelockAddress); - const criticalGovernanceRoute = new GovernanceRoute('2'); - criticalGovernanceRoute.governor = governorBravoDelegatorAddress; - criticalGovernanceRoute.timelock = criticalTimelockAddress; - criticalGovernanceRoute.queueDelayBlocks = criticalTimelock.delay(); - criticalGovernanceRoute.votingDelayBlocks = criticalProposalConfig.getVotingDelay(); - criticalGovernanceRoute.votingPeriodBlocks = criticalProposalConfig.getVotingPeriod(); - criticalGovernanceRoute.proposalThresholdMantissa = - criticalProposalConfig.getProposalThreshold(); - criticalGovernanceRoute.save(); - } - - governance.save(); } return governance as Governance; diff --git a/subgraphs/venus-governance/src/operations/update.ts b/subgraphs/venus-governance/src/operations/update.ts index 90ce90c9..722bfc45 100644 --- a/subgraphs/venus-governance/src/operations/update.ts +++ b/subgraphs/venus-governance/src/operations/update.ts @@ -1,5 +1,8 @@ +import { GovernorBravoDelegate2 } from '../../generated/GovernorBravoDelegate2/GovernorBravoDelegate2'; +import { Governance } from '../../generated/schema'; import { BIGINT_ONE } from '../constants'; -import { nullAddress } from '../constants/addresses'; +import { governorBravoDelegatorAddress, nullAddress } from '../constants/addresses'; +import { getGovernanceId } from '../utilities/ids'; import { getGovernanceEntity, getProposal } from './get'; import { getOrCreateDelegate } from './getOrCreate'; @@ -44,8 +47,10 @@ export function updateDelegateChanged(event: E): void { const oldDelegate = oldDelegateResult.entity; oldDelegate.delegateCount = oldDelegate.delegateCount - 1; oldDelegate.save(); + } - governance.totalDelegates = governance.totalDelegates.minus(BIGINT_ONE); + if (fromDelegate == nullAddress.toHexString()) { + governance.totalDelegates = governance.totalDelegates.plus(BIGINT_ONE); governance.save(); } @@ -54,8 +59,10 @@ export function updateDelegateChanged(event: E): void { const newDelegate = newDelegateResult.entity; newDelegate.delegateCount = newDelegate.delegateCount + 1; newDelegate.save(); + } - governance.totalDelegates = governance.totalDelegates.plus(BIGINT_ONE); + if (fromDelegate == nullAddress.toHexString()) { + governance.totalDelegates = governance.totalDelegates.minus(BIGINT_ONE); governance.save(); } } @@ -76,3 +83,13 @@ export function updateDelegateVoteChanged(event: E): void { governance.totalVotesMantissa = governance.totalVotesMantissa.plus(votesDifference); governance.save(); } + +export function updateGovernanceEntity(): void { + const governorBravoDelegate2 = GovernorBravoDelegate2.bind(governorBravoDelegatorAddress); + const governance = Governance.load(getGovernanceId())!; + governance.quorumVotesMantissa = governorBravoDelegate2.quorumVotes(); + governance.admin = governorBravoDelegate2.admin(); + governance.guardian = governorBravoDelegate2.guardian(); + governance.proposalMaxOperations = governorBravoDelegate2.proposalMaxOperations(); + governance.save(); +} diff --git a/subgraphs/venus-governance/src/utilities/ids.ts b/subgraphs/venus-governance/src/utilities/ids.ts index 6db7fd40..ac63891d 100644 --- a/subgraphs/venus-governance/src/utilities/ids.ts +++ b/subgraphs/venus-governance/src/utilities/ids.ts @@ -1,6 +1,6 @@ import { Address, BigInt } from '@graphprotocol/graph-ts'; -const SEPERATOR = '-'; +import { GOVERNANCE, SEPERATOR } from '../constants'; export const getVoteId = (voter: Address, proposalId: BigInt): string => [voter.toHexString(), proposalId.toString()].join(SEPERATOR); @@ -13,3 +13,5 @@ export const getPermissionId = ( [accountAddress.toHexString(), contractAddress.toHexString(), functionSig].join(SEPERATOR); export const getDelegateId = (account: Address): string => account.toHexString(); + +export const getGovernanceId = (): string => GOVERNANCE; diff --git a/subgraphs/venus-governance/subgraph-client/index.ts b/subgraphs/venus-governance/subgraph-client/index.ts index 04a43258..b494905a 100644 --- a/subgraphs/venus-governance/subgraph-client/index.ts +++ b/subgraphs/venus-governance/subgraph-client/index.ts @@ -4,6 +4,7 @@ import { Client as UrqlClient, createClient } from 'urql/core'; import { DelegateByIdDocument, DelegatesDocument, + GovernanceDocument, PermissionsDocument, ProposalByIdDocument, ProposalsDocument, @@ -51,6 +52,11 @@ class SubgraphClient { const result = await this.query(PermissionsDocument, {}); return result; } + + async getGovernance() { + const result = await this.query(GovernanceDocument, {}); + return result; + } } export default new SubgraphClient( diff --git a/subgraphs/venus-governance/tests/integration/bravo.ts b/subgraphs/venus-governance/tests/integration/bravo.ts index 2ddf37fc..168c8465 100644 --- a/subgraphs/venus-governance/tests/integration/bravo.ts +++ b/subgraphs/venus-governance/tests/integration/bravo.ts @@ -191,11 +191,28 @@ describe('GovernorBravo', function () { }); describe('GovernorBravo2', function () { - before(async () => { + it('should update GovernorEntity when setting implementation', async function () { + const timelock = await ethers.getContract('Timelock'); + const governorBravoDelegateV1 = await ethers.getContract('GovernorBravoDelegateV1'); + // Assert original values + let { + data: { governance }, + } = await subgraphClient.getGovernance(); + + expect(governance.totalProposals).to.equal('4'); + expect(governance.totalDelegates).to.equal('4'); + expect(governance.totalVoters).to.equal('4'); + expect(governance.totalVotesMantissa).to.equal('1700000000000000000000000'); + expect(governance.quorumVotesMantissa).to.equal('600000000000000000000000'); + expect(governance.implementation).to.equal(governorBravoDelegateV1.address.toLowerCase()); + expect(governance.pendingAdmin).to.equal(null); + expect(governance.admin).to.equal(signers[0].address.toLowerCase()); + expect(governance.guardian).to.equal(signers[0].address.toLowerCase()); + expect(governance.proposalMaxOperations).to.equal('10'); + const governorBravoDelegatorV2 = await ethers.getContract('GovernorBravoDelegate'); const xvsVaultProxy = await ethers.getContract('XVSVaultProxy'); const xvsVault = await ethers.getContractAt('XVSVault', xvsVaultProxy.address); - const timelock = await ethers.getContract('Timelock'); await governorBravoDelegator._setImplementation(governorBravoDelegatorV2.address); governorBravo = await ethers.getContractAt('GovernorBravoDelegate', governorBravo.address); @@ -223,7 +240,19 @@ describe('GovernorBravo', function () { const timelocks = [timelock.address, timelock.address, timelock.address]; - governorBravo.initialize(xvsVault.address, proposalConfigs, timelocks, signers[0].address); + await governorBravo.initialize( + xvsVault.address, + proposalConfigs, + timelocks, + signers[0].address, + ); + await waitForSubgraphToBeSynced(SYNC_DELAY); + // Assert updated values + ({ + data: { governance }, + } = await subgraphClient.getGovernance()); + + expect(governance.implementation).to.equal(governorBravoDelegatorV2.address.toLowerCase()); }); it('should index created proposal with routes successfully', async function () { diff --git a/subgraphs/venus-governance/tests/integration/queries/governanceQuery.graphql b/subgraphs/venus-governance/tests/integration/queries/governanceQuery.graphql new file mode 100644 index 00000000..994c4a00 --- /dev/null +++ b/subgraphs/venus-governance/tests/integration/queries/governanceQuery.graphql @@ -0,0 +1,15 @@ +query Governance { + governance(id: "GOVERNANCE") { + id + totalProposals + totalDelegates + totalVoters + totalVotesMantissa + quorumVotesMantissa + implementation + pendingAdmin + admin + guardian + proposalMaxOperations + } +} diff --git a/subgraphs/venus-governance/tests/integration/utils/constants.ts b/subgraphs/venus-governance/tests/integration/utils/constants.ts index 972629ab..28eee096 100644 --- a/subgraphs/venus-governance/tests/integration/utils/constants.ts +++ b/subgraphs/venus-governance/tests/integration/utils/constants.ts @@ -5,3 +5,4 @@ export const SUBGRAPH_NAME = 'venus-governance'; export const SYNC_DELAY = 2000; export const mockAddress = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'; +export const nullAddress = '0x0000000000000000000000000000000000000000'; diff --git a/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts b/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts index a49681ea..52b90b37 100644 --- a/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts +++ b/subgraphs/venus-governance/tests/unit/Bravo/index.test.ts @@ -17,7 +17,7 @@ import { ProposalQueued, } from '../../../generated/GovernorBravoDelegate/GovernorBravoDelegate'; import { Delegate } from '../../../generated/schema'; -import { governorBravoDelegatorAddress } from '../../../src/constants/addresses'; +import { GOVERNANCE } from '../../../src/constants'; import { handleBravoVoteCast, handleNewAdmin, @@ -267,12 +267,7 @@ describe('Bravo', () => { ); handleNewImplementation(newImplementationEvent); - assert.fieldEquals( - 'Governance', - governorBravoDelegatorAddress.toHex(), - 'implementation', - newImplementation.toHexString(), - ); + assert.fieldEquals('Governance', GOVERNANCE, 'implementation', newImplementation.toHexString()); }); test('registers new pending admin', () => { @@ -285,12 +280,7 @@ describe('Bravo', () => { ); handleNewPendingAdmin(pendingAdminEvent); - assert.fieldEquals( - 'Governance', - governorBravoDelegatorAddress.toHex(), - 'pendingAdmin', - newPendingAdmin.toHexString(), - ); + assert.fieldEquals('Governance', GOVERNANCE, 'pendingAdmin', newPendingAdmin.toHexString()); }); test('registers new admin', () => { @@ -299,13 +289,8 @@ describe('Bravo', () => { const newAdminEvent = createNewAdminEvent(governanceAddress, oldAdmin, newAdmin); handleNewAdmin(newAdminEvent); - assert.fieldEquals( - 'Governance', - governorBravoDelegatorAddress.toHex(), - 'admin', - newAdmin.toHexString(), - ); - assert.fieldEquals('Governance', governorBravoDelegatorAddress.toHex(), 'pendingAdmin', 'null'); + assert.fieldEquals('Governance', GOVERNANCE, 'admin', newAdmin.toHexString()); + assert.fieldEquals('Governance', GOVERNANCE, 'pendingAdmin', 'null'); }); test('registers new guardian', () => { @@ -314,12 +299,7 @@ describe('Bravo', () => { const newGuardianEvent = createNewGuardianEvent(governanceAddress, oldGuardian, newGuardian); handleNewGuardian(newGuardianEvent); - assert.fieldEquals( - 'Governance', - governorBravoDelegatorAddress.toHex(), - 'guardian', - newGuardian.toHexString(), - ); + assert.fieldEquals('Governance', GOVERNANCE, 'guardian', newGuardian.toHexString()); }); test('registers new proposal max operations', () => { @@ -334,7 +314,7 @@ describe('Bravo', () => { handleProposalMaxOperationsUpdated(newProposalMaxOperationsEvent); assert.fieldEquals( 'Governance', - governorBravoDelegatorAddress.toHex(), + GOVERNANCE, 'proposalMaxOperations', newProposalMaxOperations.toString(), );