Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: compute registry data #1

Merged
merged 5 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
},
"prettier": "@snapshot-labs/prettier-config",
"dependencies": {
"@apollo/client": "^3.11.4",
"@ethersproject/units": "^5.7.0",
"@snapshot-labs/checkpoint": "^0.1.0-beta.39",
"@snapshot-labs/snapshot.js": "^0.12.6",
"@types/node": "^18.11.6",
"async-mutex": "^0.5.0",
"cors": "^2.8.5",
Expand Down
104 changes: 96 additions & 8 deletions src/compute.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
// @ts-ignore
import { formatUnits } from '@ethersproject/units';
import { register } from '@snapshot-labs/checkpoint/dist/src/register';
import snapshotjs from '@snapshot-labs/snapshot.js';
import { Mutex } from 'async-mutex';
import { Governance } from '../.checkpoint/models';
import { COMPUTE_DELAY_SECONDS, SCORE_API_URL } from './constants';
import { getSpace } from './hub';
import { Delegate, Governance } from '../.checkpoint/models';

const DECIMALS = 18;
const DELEGATION_STRATEGIES = [
'delegation',
'erc20-balance-of-delegation',
'delegation-with-cap',
'delegation-with-overrides'
];

const lastCompute = new Map<string, number>();
const mutex = new Mutex();

// This function is called everytime governance information is queried from GraphQL API.
Expand All @@ -18,16 +30,92 @@ export async function compute(governances: string[]) {
try {
register.setCurrentBlock(register.getCurrentBlock() + 1n);

console.log('compute governances for', governances);

for (const governance of governances) {
console.log('computing', governance);

const computedAt = lastCompute.get(governance) ?? 0;
const now = Math.floor(Date.now() / 1000);
if (now - computedAt < COMPUTE_DELAY_SECONDS) {
console.log('ignoring because of recent compute');
continue;
}
lastCompute.set(governance, now);

const space = await getSpace(governance);
const delegations = await snapshotjs.utils.getDelegatesBySpace(
space.network,
governance,
'latest'
);

const delegatorCounter = {};
for (const delegation of delegations) {
if (!delegatorCounter[delegation.delegate]) {
delegatorCounter[delegation.delegate] = 0;
}

delegatorCounter[delegation.delegate] += 1;
}

const delegationsMap = Object.fromEntries(
delegations.map(d => [d.delegate, d])
);
const delegatesAddresses = Object.keys(delegationsMap);
const uniqueDelegates = Object.values(delegationsMap);

const strategies = space.strategies.filter(strategy =>
DELEGATION_STRATEGIES.includes(strategy.name)
);

const scores = await snapshotjs.utils.getScores(
governance,
strategies,
space.network,
delegatesAddresses,
'latest',
`${SCORE_API_URL}/api/scores`
);

const delegates = uniqueDelegates.map(delegate => ({
...delegate,
score: BigInt(
Math.floor((scores[0][delegate.delegate] ?? 0) * 10 ** DECIMALS)
)
}));

const sortedDelegates = delegates
.filter(delegate => delegate.score > 0n)
.sort((a, b) => (b.score > a.score ? 1 : -1));

const totalVotes = sortedDelegates.reduce(
(acc, delegate) => acc + delegate.score,
0n
);

const governanceEntity = new Governance(governance);
governanceEntity.currentDelegates = 0;
governanceEntity.totalDelegates = 0;
governanceEntity.delegatedVotesRaw = '0';
governanceEntity.delegatedVotes = '0';
governanceEntity.currentDelegates = uniqueDelegates.length;
governanceEntity.totalDelegates = uniqueDelegates.length;
governanceEntity.delegatedVotesRaw = totalVotes.toString();
governanceEntity.delegatedVotes = formatUnits(totalVotes, DECIMALS);
await governanceEntity.save();

for (const delegate of sortedDelegates) {
const delegateEntity = new Delegate(
`${governance}/${delegate.delegate}`
);
delegateEntity.governance = governance;
delegateEntity.user = delegate.delegate;
delegateEntity.delegatedVotesRaw = delegate.score.toString();
delegateEntity.delegatedVotes = formatUnits(delegate.score, DECIMALS);
delegateEntity.tokenHoldersRepresentedAmount =
delegatorCounter[delegate.delegate];
await delegateEntity.save();
}

console.log('finished compute', governance);
}
} catch (e) {
console.error('compute error', e);
} finally {
release();
}
Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const HUB_URL = process.env.HUB_URL ?? 'https://hub.snapshot.org';
export const SCORE_API_URL =
process.env.SCORE_API_URL ?? 'https://score.snapshot.org';

export const COMPUTE_DELAY_SECONDS = 30 * 60; // 30 minutes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the delay to recompute score from delegates? If so it's better to set it as 1 day. It would be too much subgraph and RPC requests to allow and update every 30mn.

40 changes: 40 additions & 0 deletions src/hub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ApolloClient, gql, InMemoryCache } from '@apollo/client/core';
import { HUB_URL } from './constants';

export const SPACE_QUERY = gql`
query Space($id: String!) {
space(id: $id) {
id
network
strategies {
name
network
params
}
}
}
`;

type Space = {
id: string;
network: string;
strategies: {
name: string;
network: string;
params: any;
}[];
};

const client = new ApolloClient({
uri: `${HUB_URL}/graphql`,
cache: new InMemoryCache()
});

export async function getSpace(space: string): Promise<Space> {
const { data } = await client.query({
query: SPACE_QUERY,
variables: { id: space }
});

return data.space;
}
Loading
Loading