diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 23277608..06b43bec 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -58,6 +58,7 @@ type TokenVotingPlugin implements IPlugin @entity { proposals: [TokenVotingProposal!]! @derivedFrom(field: "plugin") votingMode: VotingMode + votingModeIndex: Int8 supportThreshold: BigInt minParticipation: BigInt minDuration: BigInt @@ -111,6 +112,7 @@ type TokenVotingProposal implements IProposal @entity { metadata: String votingMode: VotingMode! + votingModeIndex: Int8! supportThreshold: BigInt! minVotingPower: BigInt! snapshotBlock: BigInt! diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index dfc98d8e..eba94df5 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -14,7 +14,12 @@ import { MembershipContractAnnounced, TokenVoting, } from '../../generated/templates/TokenVoting/TokenVoting'; -import {RATIO_BASE, VOTER_OPTIONS, VOTING_MODES} from '../utils/constants'; +import { + RATIO_BASE, + VOTER_OPTIONS, + VOTING_MODES, + VOTING_MODE_UNDEFINED, +} from '../utils/constants'; import {identifyAndFetchOrCreateERC20TokenEntity} from '../utils/erc20'; import {generateMemberEntityId, generateVoteEntityId} from '../utils/ids'; import { @@ -70,9 +75,13 @@ export function _handleProposalCreated( // ProposalParameters const parameters = proposal.value.value2; - const votingMode = VOTING_MODES.get(parameters.votingMode); - - proposalEntity.votingMode = votingMode as string; + let votingModeIndex = parameters.votingMode; + proposalEntity.votingModeIndex = votingModeIndex; + if (!VOTING_MODES.has(votingModeIndex)) { + // if the voting mode is not defined, set it to 'Undefined' + votingModeIndex = VOTING_MODE_UNDEFINED; + } + proposalEntity.votingMode = VOTING_MODES.get(votingModeIndex) as string; proposalEntity.supportThreshold = parameters.supportThreshold; proposalEntity.snapshotBlock = parameters.snapshotBlock; proposalEntity.minVotingPower = parameters.minVotingPower; @@ -264,9 +273,14 @@ export function handleVotingSettingsUpdated( generatePluginEntityId(event.address) ); if (pluginEntity) { - const votingMode = VOTING_MODES.get(event.params.votingMode); + let votingModeIndex = event.params.votingMode; + pluginEntity.votingModeIndex = votingModeIndex; + if (!VOTING_MODES.has(votingModeIndex)) { + // if the voting mode is not defined, set it to 'Undefined' + votingModeIndex = VOTING_MODE_UNDEFINED; + } - pluginEntity.votingMode = votingMode as string; + pluginEntity.votingMode = VOTING_MODES.get(votingModeIndex) as string; pluginEntity.supportThreshold = event.params.supportThreshold; pluginEntity.minParticipation = event.params.minParticipation; pluginEntity.minDuration = event.params.minDuration; diff --git a/packages/subgraph/src/utils/constants.ts b/packages/subgraph/src/utils/constants.ts index 7e6d2617..3bb5272b 100644 --- a/packages/subgraph/src/utils/constants.ts +++ b/packages/subgraph/src/utils/constants.ts @@ -1,5 +1,7 @@ export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000'; +export const VOTING_MODE_UNDEFINED = 10; + // AS does not support initializing Map with data, a chain of sets is used instead export const VOTER_OPTIONS = new Map() .set(0, 'None') @@ -16,12 +18,8 @@ export const VOTE_OPTIONS = new Map() export const VOTING_MODES = new Map() .set(0, 'Standard') .set(1, 'EarlyExecution') - .set(2, 'VoteReplacement'); - -export const VOTING_MODE_INDEXES = new Map() - .set('Standard', '0') - .set('EarlyExecution', '1') - .set('VoteReplacement', '2'); + .set(2, 'VoteReplacement') + .set(VOTING_MODE_UNDEFINED, 'Undefined'); export const TOKEN_VOTING_INTERFACE_ID = '0x50eb001e'; export const GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID = '0x0f13099a'; diff --git a/packages/subgraph/tests/helpers/method-classes.ts b/packages/subgraph/tests/helpers/method-classes.ts index 03d65252..cdd425bb 100644 --- a/packages/subgraph/tests/helpers/method-classes.ts +++ b/packages/subgraph/tests/helpers/method-classes.ts @@ -28,7 +28,7 @@ import { VOTER_OPTIONS, VOTE_OPTIONS, VOTING_MODES, - VOTING_MODE_INDEXES, + VOTING_MODE_UNDEFINED, } from '../../src/utils/constants'; import {generateMemberEntityId} from '../../src/utils/ids'; import { @@ -85,7 +85,7 @@ import { createDummyAction, generateActionEntityId, } from '@aragon/osx-commons-subgraph'; -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {Address, BigInt, Bytes, ethereum, Int8} from '@graphprotocol/graph-ts'; class ERC20WrapperContractMethods extends ERC20WrapperContract { withDefaultValues(): ERC20WrapperContractMethods { @@ -187,7 +187,9 @@ class TokenVotingVoterMethods extends TokenVotingVoter { } class TokenVotingProposalMethods extends TokenVotingProposal { - withDefaultValues(): TokenVotingProposalMethods { + withDefaultValues( + votingModeIndex: number = parseInt(VOTING_MODE) + ): TokenVotingProposalMethods { this.id = PROPOSAL_ENTITY_ID; this.daoAddress = Bytes.fromHexString(DAO_ADDRESS); @@ -198,7 +200,12 @@ class TokenVotingProposalMethods extends TokenVotingProposal { this.open = true; this.executed = false; - this.votingMode = VOTING_MODE; + // for event we need the index of the mapping to simulate the contract event + this.votingModeIndex = votingModeIndex as Int8; + this.votingMode = VOTING_MODES.has(votingModeIndex) + ? (VOTING_MODES.get(votingModeIndex) as string) + : (VOTING_MODES.get(VOTING_MODE_UNDEFINED) as string); + this.supportThreshold = BigInt.fromString(SUPPORT_THRESHOLD); this.minVotingPower = BigInt.fromString(MIN_VOTING_POWER); this.startDate = BigInt.fromString(START_DATE); @@ -231,7 +238,7 @@ class TokenVotingProposalMethods extends TokenVotingProposal { this.pluginProposalId.toString(), this.open, this.executed, - this.votingMode, + this.votingModeIndex.toString(), // we need the index for this mocked call this.supportThreshold.toString(), this.minVotingPower.toString(), this.startDate.toString(), @@ -362,22 +369,21 @@ class TokenVotingVoteMethods extends TokenVotingVote { class TokenVotingPluginMethods extends TokenVotingPlugin { // build entity // if id not changed it will update - withDefaultValues(): TokenVotingPluginMethods { - let votingModeIndex = parseInt(VOTING_MODE); - if (!VOTING_MODES.has(votingModeIndex)) { - throw new Error('voting mode is not valid.'); - } - - // we use casting here to remove autocompletion complaint - // since we know it will be captured by the previous check - let votingMode = VOTING_MODES.get(votingModeIndex) as string; + withDefaultValues( + votingModeIndex: number = parseInt(VOTING_MODE) + ): TokenVotingPluginMethods { const pluginAddress = Address.fromString(CONTRACT_ADDRESS); const pluginEntityId = generatePluginEntityId(pluginAddress); this.id = pluginEntityId; this.daoAddress = Bytes.fromHexString(DAO_ADDRESS); this.pluginAddress = pluginAddress; - this.votingMode = votingMode; + + this.votingModeIndex = votingModeIndex as Int8; // for event we need the index of the mapping to simulate the contract event + this.votingMode = VOTING_MODES.has(votingModeIndex) + ? (VOTING_MODES.get(votingModeIndex) as string) + : (VOTING_MODES.get(VOTING_MODE_UNDEFINED) as string); + this.supportThreshold = BigInt.fromString(SUPPORT_THRESHOLD); this.minParticipation = BigInt.fromString(MIN_PARTICIPATION); this.minDuration = BigInt.fromString(MIN_DURATION); @@ -396,22 +402,8 @@ class TokenVotingPluginMethods extends TokenVotingPlugin { } createEvent_VotingSettingsUpdated(): VotingSettingsUpdated { - if (this.votingMode === null) { - throw new Error('Voting mode is null.'); - } - - // we cast to string only for stoping rust compiler complaints. - let votingMode: string = this.votingMode as string; - if (!VOTING_MODE_INDEXES.has(votingMode)) { - throw new Error('Voting mode index is not valid.'); - } - - // we use casting here to remove autocompletion complaint - // since we know it will be captured by the previous check - let votingModeIndex = VOTING_MODE_INDEXES.get(votingMode) as string; - let event = createNewVotingSettingsUpdatedEvent( - votingModeIndex, // for event we need the index of the mapping to simulate the contract event + this.votingModeIndex.toString(), // we need the index to simulate the event (this.supportThreshold as BigInt).toString(), (this.minParticipation as BigInt).toString(), (this.minDuration as BigInt).toString(), @@ -435,7 +427,7 @@ class TokenVotingPluginMethods extends TokenVotingPlugin { } setNewPluginSetting( - newVotingMode: string = VOTING_MODES.get(parseInt(TWO)) as string, + votingModeIndex: number = parseInt(TWO), newSupportThreshold: BigInt = BigInt.fromString(NEW_SUPPORT_THRESHOLD), newMinParticipation: BigInt = BigInt.fromString(NEW_MIN_PARTICIPATION), newMinDuration: BigInt = BigInt.fromString(NEW_MIN_DURATION), @@ -443,8 +435,10 @@ class TokenVotingPluginMethods extends TokenVotingPlugin { NEW_MIN_PROPOSER_VOTING_POWER ) ): TokenVotingPluginMethods { - let votingMode = newVotingMode; - this.votingMode = votingMode; + this.votingModeIndex = votingModeIndex as Int8; + this.votingMode = VOTING_MODES.has(votingModeIndex) + ? (VOTING_MODES.get(votingModeIndex) as string) + : (VOTING_MODES.get(VOTING_MODE_UNDEFINED) as string); this.supportThreshold = newSupportThreshold; this.minParticipation = newMinParticipation; this.minDuration = newMinDuration; diff --git a/packages/subgraph/tests/plugin/governance-erc20.test.ts b/packages/subgraph/tests/plugin/governance-erc20.test.ts index 17be10e8..e5e5d185 100644 --- a/packages/subgraph/tests/plugin/governance-erc20.test.ts +++ b/packages/subgraph/tests/plugin/governance-erc20.test.ts @@ -466,7 +466,7 @@ describe('Governance ERC20', () => { assert.entityCount('TokenVotingMember', 2); }); - test("It should initialize with the user's existing voting power and delegation, if she has any", () => { + test("it should initialize with the user's existing voting power and delegation, if they have any", () => { let memberOne = new ExtendedTokenVotingMember().withDefaultValues( fromAddress, pluginAddress diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 8bf0c9c7..10b19483 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -5,7 +5,6 @@ import { _handleProposalCreated, handleMembershipContractAnnounced, } from '../../src/plugin/plugin'; -import {VOTING_MODES} from '../../src/utils/constants'; import {GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID} from '../../src/utils/constants'; import { ExtendedERC20Contract, @@ -18,69 +17,127 @@ import { } from '../helpers/extended-schema'; import { STRING_DATA, - VOTING_MODE, ONE, ZERO, TWO, ERC20_AMOUNT_FULL, + UNDEFINED_VOTING_MODE, } from '../utils/constants'; import {bigInt, BigInt} from '@graphprotocol/graph-ts'; import { afterAll, + afterEach, assert, clearStore, describe, test, } from 'matchstick-as/assembly/index'; -test('Run TokenVoting (handleProposalCreated) mappings with mock event', () => { - // check store is empty before running the test - assert.entityCount('TokenVotingPlugin', 0); - assert.entityCount('TokenVotingProposal', 0); - assert.entityCount('Action', 0); +describe('handleProposalCreated', () => { + afterEach(() => { + clearStore(); + }); - // create state - let tokenVotingPlugin = new ExtendedTokenVotingPlugin().withDefaultValues(); - tokenVotingPlugin.buildOrUpdate(); - // assert with default value - // eg. proposalCount is `0`. - tokenVotingPlugin.assertEntity(); + test('it should create the plugin, proposal and actions', () => { + // check store is empty before running the test + assert.entityCount('TokenVotingPlugin', 0); + assert.entityCount('TokenVotingProposal', 0); + assert.entityCount('Action', 0); - let proposal = new ExtendedTokenVotingProposal().withDefaultValues(); + // create state + let tokenVotingPlugin = new ExtendedTokenVotingPlugin().withDefaultValues(); + tokenVotingPlugin.buildOrUpdate(); + // assert with default value + // eg. proposalCount is `0`. + tokenVotingPlugin.assertEntity(); - let action = new ExtendedAction().withDefaultValues(); + let proposal = new ExtendedTokenVotingProposal().withDefaultValues(); + let action = new ExtendedAction().withDefaultValues(); - // create calls - tokenVotingPlugin.proposalCount = BigInt.fromString(ONE); - tokenVotingPlugin.mockCall_getProposalCountCall(); - proposal.mockCall_getProposal([action.getDummyAction()]); - proposal.mockCall_totalVotingPower(); + // create calls + tokenVotingPlugin.proposalCount = BigInt.fromString(ONE); + tokenVotingPlugin.mockCall_getProposalCountCall(); + proposal.mockCall_getProposal([action.getDummyAction()]); + proposal.mockCall_totalVotingPower(); - // create event - let event = proposal.createEvent_ProposalCreated( - [action.getDummyAction()], - STRING_DATA - ); + // create event + let event = proposal.createEvent_ProposalCreated( + [action.getDummyAction()], + STRING_DATA + ); - // handle event - _handleProposalCreated(event, proposal.daoAddress.toHexString(), STRING_DATA); + // handle event + _handleProposalCreated( + event, + proposal.daoAddress.toHexString(), + STRING_DATA + ); - // checks - // expected changes - proposal.creationBlockNumber = BigInt.fromString(ONE); - proposal.votingMode = VOTING_MODES.get(parseInt(VOTING_MODE)) as string; + // checks + // expected changes + proposal.creationBlockNumber = BigInt.fromString(ONE); - // check the proposal and the plugin - assert.entityCount('TokenVotingPlugin', 1); - assert.entityCount('TokenVotingProposal', 1); - proposal.assertEntity(); - tokenVotingPlugin.assertEntity(); + // check the proposal and the plugin + assert.entityCount('TokenVotingPlugin', 1); + assert.entityCount('TokenVotingProposal', 1); + proposal.assertEntity(); + tokenVotingPlugin.assertEntity(); - // check the actions - assert.entityCount('Action', 1); - action.assertEntity(); + // check the actions + assert.entityCount('Action', 1); + action.assertEntity(); + }); - clearStore(); + test('it should create the plugin, proposal and actions (with Undefined VotingMode)', () => { + // check store is empty before running the test + assert.entityCount('TokenVotingPlugin', 0); + assert.entityCount('TokenVotingProposal', 0); + assert.entityCount('Action', 0); + + // create state + let tokenVotingPlugin = new ExtendedTokenVotingPlugin().withDefaultValues( + UNDEFINED_VOTING_MODE + ); + tokenVotingPlugin.buildOrUpdate(); + + // check before handle event + tokenVotingPlugin.assertEntity(); + + let proposal = new ExtendedTokenVotingProposal().withDefaultValues( + UNDEFINED_VOTING_MODE + ); + let action = new ExtendedAction().withDefaultValues(); + + // mock call because the votingMode is being gotten from the plugin + proposal.mockCall_getProposal([action.getDummyAction()]); + + // create event + let event = proposal.createEvent_ProposalCreated( + [action.getDummyAction()], + STRING_DATA + ); + + // handle event + _handleProposalCreated( + event, + proposal.daoAddress.toHexString(), + STRING_DATA + ); + + // check the proposal and the plugin + assert.entityCount('TokenVotingPlugin', 1); + assert.entityCount('TokenVotingProposal', 1); + + proposal.creationBlockNumber = BigInt.fromString(ONE); + proposal.assertEntity(); + + tokenVotingPlugin.proposalCount = BigInt.fromString(ONE); + tokenVotingPlugin.assertEntity(); + + // check the actions + assert.entityCount('Action', 1); + action.assertEntity(); + }); }); test('Run TokenVoting (handleVoteCast) mappings with mock event', () => { @@ -257,24 +314,46 @@ test('Run TokenVoting (handleProposalExecuted) mappings with mock event', () => clearStore(); }); -test('Run TokenVoting (handleVotingSettingsUpdated) mappings with mock event', () => { - // create state - let tokenVotingPlugin = new ExtendedTokenVotingPlugin().withDefaultValues(); - tokenVotingPlugin.buildOrUpdate(); +describe('handleVotingSettingsUpdated', () => { + afterAll(() => { + clearStore(); + }); + + test('it should update the plugin setting', () => { + // create state + let tokenVotingPlugin = new ExtendedTokenVotingPlugin().withDefaultValues(); + tokenVotingPlugin.buildOrUpdate(); - // update plugin configuration - tokenVotingPlugin.setNewPluginSetting(); + // update plugin configuration + tokenVotingPlugin.setNewPluginSetting(); - // create event - let event = tokenVotingPlugin.createEvent_VotingSettingsUpdated(); + // create event + let event = tokenVotingPlugin.createEvent_VotingSettingsUpdated(); - // handle event - handleVotingSettingsUpdated(event); + // handle event + handleVotingSettingsUpdated(event); - // checks - tokenVotingPlugin.assertEntity(); + // checks + tokenVotingPlugin.assertEntity(); + }); - clearStore(); + test('it should update the plugin setting (with Undefined VotingMode)', () => { + // create state + let tokenVotingPlugin = new ExtendedTokenVotingPlugin().withDefaultValues(); + tokenVotingPlugin.buildOrUpdate(); + + // update plugin configuration + tokenVotingPlugin.setNewPluginSetting(UNDEFINED_VOTING_MODE); + + // create event + let event = tokenVotingPlugin.createEvent_VotingSettingsUpdated(); + + // handle event + handleVotingSettingsUpdated(event); + + // checks + tokenVotingPlugin.assertEntity(); + }); }); describe('handleMembershipContractAnnounced', () => { diff --git a/packages/subgraph/tests/utils/constants.ts b/packages/subgraph/tests/utils/constants.ts index 420441eb..c03b8f17 100644 --- a/packages/subgraph/tests/utils/constants.ts +++ b/packages/subgraph/tests/utils/constants.ts @@ -32,6 +32,7 @@ export const HOUR = '3600'; export const TWO_HOURS = '7200'; export const VOTING_MODE: string = ONE; // EarlyExecution +export const UNDEFINED_VOTING_MODE = 11; export const SUPPORT_THRESHOLD = '500000'; // 50*10**4 = 50% export const NEW_SUPPORT_THRESHOLD = '400000'; // 40*10**4 = 40% export const MIN_PARTICIPATION = '500000'; // 50*10**4 = 50%