Added Governance Subgraph

This commit is contained in:
Tornado Contrib 2024-04-28 22:08:48 +00:00
parent 8f656af0ac
commit 5eb3a310c5
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
9 changed files with 361 additions and 78 deletions

@ -1282,6 +1282,7 @@ export function tornadoProgram() {
const { const {
tornadoSubgraph, tornadoSubgraph,
registrySubgraph, registrySubgraph,
governanceSubgraph,
tokens, tokens,
nativeCurrency, nativeCurrency,
routerContract, routerContract,
@ -1301,9 +1302,8 @@ export function tornadoProgram() {
const governanceService = new NodeGovernanceService({ const governanceService = new NodeGovernanceService({
netId, netId,
provider, provider,
// to-do connect governance with subgraph graphApi,
graphApi: '', subgraphName: governanceSubgraph,
subgraphName: '',
Governance: Governance__factory.connect(governanceContract, provider), Governance: Governance__factory.connect(governanceContract, provider),
deployedBlock: GOVERNANCE_BLOCK, deployedBlock: GOVERNANCE_BLOCK,
fetchDataOptions, fetchDataOptions,

@ -22,6 +22,7 @@ import type {
DepositsEvents, DepositsEvents,
WithdrawalsEvents, WithdrawalsEvents,
EncryptedNotesEvents, EncryptedNotesEvents,
AllGovernanceEvents,
GovernanceProposalCreatedEvents, GovernanceProposalCreatedEvents,
GovernanceVotedEvents, GovernanceVotedEvents,
GovernanceDelegatedEvents, GovernanceDelegatedEvents,
@ -589,12 +590,6 @@ export class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesE
} }
} }
export type BaseGovernanceEventTypes =
| GovernanceProposalCreatedEvents
| GovernanceVotedEvents
| GovernanceDelegatedEvents
| GovernanceUndelegatedEvents;
export type BaseGovernanceServiceConstructor = { export type BaseGovernanceServiceConstructor = {
netId: number | string; netId: number | string;
provider: Provider; provider: Provider;
@ -605,7 +600,7 @@ export type BaseGovernanceServiceConstructor = {
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
}; };
export class BaseGovernanceService extends BaseEventsService<BaseGovernanceEventTypes> { export class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents> {
batchTransactionService: BatchTransactionService; batchTransactionService: BatchTransactionService;
constructor({ constructor({
@ -634,70 +629,71 @@ export class BaseGovernanceService extends BaseEventsService<BaseGovernanceEvent
} }
getGraphMethod() { getGraphMethod() {
return 'getGovernanceEvents'; return 'getAllGovernanceEvents';
} }
async formatEvents(events: EventLog[]): Promise<BaseGovernanceEventTypes[]> { async formatEvents(events: EventLog[]): Promise<AllGovernanceEvents[]> {
const formattedEvents = events const proposalEvents: GovernanceProposalCreatedEvents[] = [];
.map(({ blockNumber, index: logIndex, transactionHash, args, eventName: event }) => { const votedEvents: GovernanceVotedEvents[] = [];
const eventObjects = { const delegatedEvents: GovernanceDelegatedEvents[] = [];
blockNumber, const undelegatedEvents: GovernanceUndelegatedEvents[] = [];
logIndex,
transactionHash,
event,
};
if (event === 'ProposalCreated') { events.forEach(({ blockNumber, index: logIndex, transactionHash, args, eventName: event }) => {
const { id, proposer, target, startTime, endTime, description } = args; const eventObjects = {
return { blockNumber,
...eventObjects, logIndex,
id, transactionHash,
proposer, event,
target, };
startTime,
endTime,
description,
} as GovernanceProposalCreatedEvents;
}
if (event === 'Voted') { if (event === 'ProposalCreated') {
const { proposalId, voter, support, votes } = args; const { id, proposer, target, startTime, endTime, description } = args;
return {
...eventObjects,
proposalId,
voter,
support,
votes,
} as GovernanceVotedEvents;
}
if (event === 'Delegated') { proposalEvents.push({
const { account, to: delegateTo } = args; ...eventObjects,
return { id: Number(id),
...eventObjects, proposer,
account, target,
delegateTo, startTime: Number(startTime),
} as GovernanceDelegatedEvents; endTime: Number(endTime),
} description,
});
}
if (event === 'Undelegated') { if (event === 'Voted') {
const { account, from: delegateFrom } = args; const { proposalId, voter, support, votes } = args;
return {
...eventObjects,
account,
delegateFrom,
} as GovernanceUndelegatedEvents;
}
})
.filter((e) => e) as BaseGovernanceEventTypes[];
type GovernanceVotedEventsIndexed = GovernanceVotedEvents & { votedEvents.push({
index: number; ...eventObjects,
}; proposalId: Number(proposalId),
voter,
support,
votes,
from: '',
input: '',
});
}
const votedEvents = formattedEvents if (event === 'Delegated') {
.map((event, index) => ({ ...event, index })) const { account, to: delegateTo } = args;
.filter(({ event }) => event === 'Voted') as GovernanceVotedEventsIndexed[];
delegatedEvents.push({
...eventObjects,
account,
delegateTo,
});
}
if (event === 'Undelegated') {
const { account, from: delegateFrom } = args;
undelegatedEvents.push({
...eventObjects,
account,
delegateFrom,
});
}
});
if (votedEvents.length) { if (votedEvents.length) {
this.updateTransactionProgress({ percentage: 0 }); this.updateTransactionProgress({ percentage: 0 });
@ -706,7 +702,7 @@ export class BaseGovernanceService extends BaseEventsService<BaseGovernanceEvent
...new Set(votedEvents.map(({ transactionHash }) => transactionHash)), ...new Set(votedEvents.map(({ transactionHash }) => transactionHash)),
]); ]);
votedEvents.forEach((event) => { votedEvents.forEach((event, index) => {
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let { data: input, from } = txs.find((t) => t.hash === event.transactionHash) as TransactionResponse; let { data: input, from } = txs.find((t) => t.hash === event.transactionHash) as TransactionResponse;
@ -715,19 +711,17 @@ export class BaseGovernanceService extends BaseEventsService<BaseGovernanceEvent
input = ''; input = '';
} }
// @ts-expect-error check formattedEvents types later votedEvents[index].from = from;
formattedEvents[event.index].from = from; votedEvents[index].input = input;
// @ts-expect-error check formattedEvents types later
formattedEvents[event.index].input = input;
}); });
} }
return formattedEvents; return [...proposalEvents, ...votedEvents, ...delegatedEvents, ...undelegatedEvents];
} }
async getEventsFromGraph({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<BaseGovernanceEventTypes>> { async getEventsFromGraph({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<AllGovernanceEvents>> {
// TheGraph doesn't support governance subgraphs // TheGraph doesn't support governance subgraphs
if (!this.graphApi || this.graphApi.includes('api.thegraph.com')) { if (!this.graphApi || !this.subgraphName || this.graphApi.includes('api.thegraph.com')) {
return { return {
events: [], events: [],
lastBlock: fromBlock, lastBlock: fromBlock,

@ -11,7 +11,6 @@ import {
BaseEncryptedNotesServiceConstructor, BaseEncryptedNotesServiceConstructor,
BaseGovernanceServiceConstructor, BaseGovernanceServiceConstructor,
BaseRegistryServiceConstructor, BaseRegistryServiceConstructor,
BaseGovernanceEventTypes,
BaseEchoServiceConstructor, BaseEchoServiceConstructor,
BaseEchoService, BaseEchoService,
} from './base'; } from './base';
@ -21,6 +20,7 @@ import type {
WithdrawalsEvents, WithdrawalsEvents,
EncryptedNotesEvents, EncryptedNotesEvents,
RegistersEvents, RegistersEvents,
AllGovernanceEvents,
EchoEvents, EchoEvents,
} from './types'; } from './types';
@ -561,7 +561,7 @@ export class NodeGovernanceService extends BaseGovernanceService {
}; };
} }
const savedEvents = await loadSavedEvents<BaseGovernanceEventTypes>({ const savedEvents = await loadSavedEvents<AllGovernanceEvents>({
name: this.getInstanceName(), name: this.getInstanceName(),
userDirectory: this.userDirectory, userDirectory: this.userDirectory,
deployedBlock: this.deployedBlock, deployedBlock: this.deployedBlock,
@ -585,7 +585,7 @@ export class NodeGovernanceService extends BaseGovernanceService {
}; };
} }
const cachedEvents = await loadCachedEvents<BaseGovernanceEventTypes>({ const cachedEvents = await loadCachedEvents<AllGovernanceEvents>({
name: this.getInstanceName(), name: this.getInstanceName(),
cacheDirectory: this.cacheDirectory, cacheDirectory: this.cacheDirectory,
deployedBlock: this.deployedBlock, deployedBlock: this.deployedBlock,
@ -597,7 +597,7 @@ export class NodeGovernanceService extends BaseGovernanceService {
return cachedEvents; return cachedEvents;
} }
async saveEvents({ events, lastBlock }: BaseEvents<BaseGovernanceEventTypes>) { async saveEvents({ events, lastBlock }: BaseEvents<AllGovernanceEvents>) {
const instanceName = this.getInstanceName(); const instanceName = this.getInstanceName();
console.log('\ntotalEvents count - ', events.length); console.log('\ntotalEvents count - ', events.length);

@ -48,6 +48,12 @@ export type GovernanceUndelegatedEvents = GovernanceEvents & {
delegateFrom: string; delegateFrom: string;
}; };
export type AllGovernanceEvents =
| GovernanceProposalCreatedEvents
| GovernanceVotedEvents
| GovernanceDelegatedEvents
| GovernanceUndelegatedEvents;
export type RegistersEvents = MinimalEvents & RelayerParams; export type RegistersEvents = MinimalEvents & RelayerParams;
export type DepositsEvents = MinimalEvents & { export type DepositsEvents = MinimalEvents & {

@ -9,6 +9,11 @@ import type {
EncryptedNotesEvents, EncryptedNotesEvents,
BatchGraphOnProgress, BatchGraphOnProgress,
EchoEvents, EchoEvents,
AllGovernanceEvents,
GovernanceProposalCreatedEvents,
GovernanceVotedEvents,
GovernanceDelegatedEvents,
GovernanceUndelegatedEvents,
} from '../events'; } from '../events';
import { import {
_META, _META,
@ -19,6 +24,7 @@ import {
GET_NOTE_ACCOUNTS, GET_NOTE_ACCOUNTS,
GET_ENCRYPTED_NOTES, GET_ENCRYPTED_NOTES,
GET_ECHO_EVENTS, GET_ECHO_EVENTS,
GET_GOVERNANCE_EVENTS,
} from './queries'; } from './queries';
export * from './queries'; export * from './queries';
@ -910,3 +916,223 @@ export async function getAllEncryptedNotes({
}; };
} }
} }
export interface GraphGovernanceEvents {
proposals: {
blockNumber: number;
logIndex: number;
transactionHash: string;
proposalId: number;
proposer: string;
target: string;
startTime: number;
endTime: number;
description: string;
}[];
votes: {
blockNumber: number;
logIndex: number;
transactionHash: string;
proposalId: number;
voter: string;
support: boolean;
votes: string;
from: string;
input: string;
}[];
delegates: {
blockNumber: number;
logIndex: number;
transactionHash: string;
account: string;
delegateTo: string;
}[];
undelegates: {
blockNumber: number;
logIndex: number;
transactionHash: string;
account: string;
delegateFrom: string;
}[];
_meta: {
block: {
number: number;
};
hasIndexingErrors: boolean;
};
}
export interface getGovernanceEventsParams {
graphApi: string;
subgraphName: string;
fromBlock: number;
fetchDataOptions?: fetchDataOptions;
onProgress?: BatchGraphOnProgress;
}
export function getGovernanceEvents({
graphApi,
subgraphName,
fromBlock,
fetchDataOptions,
}: getGovernanceEventsParams): Promise<GraphGovernanceEvents> {
return queryGraph<GraphGovernanceEvents>({
graphApi,
subgraphName,
query: GET_GOVERNANCE_EVENTS,
variables: {
first,
fromBlock,
},
fetchDataOptions,
});
}
export async function getAllGovernanceEvents({
graphApi,
subgraphName,
fromBlock,
fetchDataOptions,
onProgress,
}: getGovernanceEventsParams): Promise<BaseGraphEvents<AllGovernanceEvents>> {
try {
const result = [];
let lastSyncBlock = fromBlock;
// eslint-disable-next-line no-constant-condition
while (true) {
const {
proposals,
votes,
delegates,
undelegates,
_meta: {
block: { number: currentBlock },
},
} = await getGovernanceEvents({ graphApi, subgraphName, fromBlock, fetchDataOptions });
lastSyncBlock = currentBlock;
const eventsLength = proposals.length + votes.length + delegates.length + undelegates.length;
if (eventsLength === 0) {
break;
}
const formattedProposals: GovernanceProposalCreatedEvents[] = proposals.map(
({ blockNumber, logIndex, transactionHash, proposalId, proposer, target, startTime, endTime, description }) => {
return {
blockNumber: Number(blockNumber),
logIndex: Number(logIndex),
transactionHash,
event: 'ProposalCreated',
id: Number(proposalId),
proposer: getAddress(proposer),
target: getAddress(target),
startTime: Number(startTime),
endTime: Number(endTime),
description,
};
},
);
const formattedVotes: GovernanceVotedEvents[] = votes.map(
({ blockNumber, logIndex, transactionHash, proposalId, voter, support, votes, from, input }) => {
// Filter spammy txs
if (!input || input.length > 2048) {
input = '';
}
return {
blockNumber: Number(blockNumber),
logIndex: Number(logIndex),
transactionHash,
event: 'Voted',
proposalId: Number(proposalId),
voter: getAddress(voter),
support,
votes,
from: getAddress(from),
input,
};
},
);
const formattedDelegates: GovernanceDelegatedEvents[] = delegates.map(
({ blockNumber, logIndex, transactionHash, account, delegateTo }) => {
return {
blockNumber: Number(blockNumber),
logIndex: Number(logIndex),
transactionHash,
event: 'Delegated',
account: getAddress(account),
delegateTo: getAddress(delegateTo),
};
},
);
const formattedUndelegates: GovernanceUndelegatedEvents[] = undelegates.map(
({ blockNumber, logIndex, transactionHash, account, delegateFrom }) => {
return {
blockNumber: Number(blockNumber),
logIndex: Number(logIndex),
transactionHash,
event: 'Undelegated',
account: getAddress(account),
delegateFrom: getAddress(delegateFrom),
};
},
);
let formattedEvents = [
...formattedProposals,
...formattedVotes,
...formattedDelegates,
...formattedUndelegates,
].sort((a, b) => {
if (a.blockNumber === b.blockNumber) {
return a.logIndex - b.logIndex;
}
return a.blockNumber - b.blockNumber;
});
if (eventsLength < 900) {
result.push(...formattedEvents);
break;
}
const [firstEvent] = formattedEvents;
const [lastEvent] = formattedEvents.slice(-1);
if (typeof onProgress === 'function') {
onProgress({
type: 'Governance Events',
fromBlock: Number(firstEvent.blockNumber),
toBlock: Number(lastEvent.blockNumber),
count: eventsLength,
});
}
formattedEvents = formattedEvents.filter(({ blockNumber }) => blockNumber !== lastEvent.blockNumber);
fromBlock = Number(lastEvent.blockNumber);
result.push(...formattedEvents);
}
const [lastEvent] = result.slice(-1);
return {
events: result,
lastSyncBlock: lastEvent && lastEvent.blockNumber >= lastSyncBlock ? lastEvent.blockNumber + 1 : lastSyncBlock,
};
} catch (err) {
console.log('Error from getAllGovernance query');
console.log(err);
return {
events: [],
lastSyncBlock: fromBlock,
};
}
}

@ -140,3 +140,58 @@ export const GET_ENCRYPTED_NOTES = `
} }
} }
`; `;
export const GET_GOVERNANCE_EVENTS = `
query getGovernanceEvents($first: Int, $fromBlock: Int) {
proposals(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) {
blockNumber
logIndex
transactionHash
proposalId
proposer
target
startTime
endTime
description
}
votes(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) {
blockNumber
logIndex
transactionHash
proposalId
voter
support
votes
from
input
}
delegates(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) {
blockNumber
logIndex
transactionHash
account
delegateTo
}
undelegates(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) {
blockNumber
logIndex
transactionHash
account
delegateFrom
}
_meta {
block {
number
}
hasIndexingErrors
}
}
`;
export const GET_GOVERNANCE_APY = `
stakeDailyBurns(first: 30, orderBy: date, orderDirection: desc) {
id
date
dailyAmountBurned
}
`;

@ -67,6 +67,7 @@ export type Config = {
ovmGasPriceOracleContract?: string; ovmGasPriceOracleContract?: string;
tornadoSubgraph: string; tornadoSubgraph: string;
registrySubgraph?: string; registrySubgraph?: string;
governanceSubgraph?: string;
subgraphs: SubgraphUrls; subgraphs: SubgraphUrls;
tokens: TokenInstances; tokens: TokenInstances;
optionalTokens?: string[]; optionalTokens?: string[];
@ -158,6 +159,7 @@ export const defaultConfig: networkConfig = {
reverseRecordsContract: '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C', reverseRecordsContract: '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C',
tornadoSubgraph: 'tornadocash/mainnet-tornado-subgraph', tornadoSubgraph: 'tornadocash/mainnet-tornado-subgraph',
registrySubgraph: 'tornadocash/tornado-relayer-registry', registrySubgraph: 'tornadocash/tornado-relayer-registry',
governanceSubgraph: 'tornadocash/tornado-governance',
subgraphs: { subgraphs: {
tornado, tornado,
theGraph, theGraph,

Binary file not shown.