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

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

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

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

@ -9,6 +9,11 @@ import type {
EncryptedNotesEvents,
BatchGraphOnProgress,
EchoEvents,
AllGovernanceEvents,
GovernanceProposalCreatedEvents,
GovernanceVotedEvents,
GovernanceDelegatedEvents,
GovernanceUndelegatedEvents,
} from '../events';
import {
_META,
@ -19,6 +24,7 @@ import {
GET_NOTE_ACCOUNTS,
GET_ENCRYPTED_NOTES,
GET_ECHO_EVENTS,
GET_GOVERNANCE_EVENTS,
} 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;
tornadoSubgraph: string;
registrySubgraph?: string;
governanceSubgraph?: string;
subgraphs: SubgraphUrls;
tokens: TokenInstances;
optionalTokens?: string[];
@ -158,6 +159,7 @@ export const defaultConfig: networkConfig = {
reverseRecordsContract: '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C',
tornadoSubgraph: 'tornadocash/mainnet-tornado-subgraph',
registrySubgraph: 'tornadocash/tornado-relayer-registry',
governanceSubgraph: 'tornadocash/tornado-governance',
subgraphs: {
tornado,
theGraph,

Binary file not shown.