Skip to content

feat: set proposal's strategies fiat value #567

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

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SIDEKICK_URL=https://sh5.co
PINEAPPLE_URL=https://.../upload # optional
SCORE_API_URL=https://score.snapshot.org
SCHNAPS_API_URL=https://schnaps.snapshot.box
# If you need unlimted access to score-api, use `https://score.snapshot.org?apiKey=...`
# If you need unlimited access to score-api, use `https://score.snapshot.org?apiKey=...`
RATE_LIMIT_DATABASE_URL= # optional
RATE_LIMIT_KEYS_PREFIX=snapshot-sequencer: # optional
BROVIDER_URL=https://rpc.snapshot.org # optional
Expand All @@ -19,3 +19,5 @@ STARKNET_RPC_URL= # optional
AUTH_SECRET=1dfd84a695705665668c260222ded178d1f1d62d251d7bee8148428dac6d0487 # optional
WALLETCONNECT_PROJECT_ID=e6454bd61aba40b786e866a69bd4c5c6
REOWN_SECRET= # optional
OVERLORD_URL=https://overlord.snapshot.org
LAST_CB=1
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@snapshot-labs/eslint-config": "^0.1.0-beta.18",
"@snapshot-labs/prettier-config": "^0.1.0-beta.7",
"@types/jest": "^29.5.2",
"@types/node": "^14.0.13",
"@types/node": "^16.0.0",
"dotenv-cli": "^7.2.1",
"eslint": "^8.28.0",
"jest": "^29.6.1",
Expand Down
26 changes: 26 additions & 0 deletions src/helpers/entityValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { jsonRpcRequest } from './utils';

type Proposal = {
network: string;
strategies: any[];
start: number;
};

const OVERLORD_URL = process.env.OVERLORD_URL ?? 'https://overlord.snapshot.org';
// Round strategy values to 9 decimal places
const STRATEGIES_VALUE_PRECISION = 9;

export async function getStrategiesValue(proposal: Proposal): Promise<number[]> {
const result: number[] = await jsonRpcRequest(OVERLORD_URL, 'get_vp_value_by_strategy', {
network: proposal.network,
strategies: proposal.strategies,
snapshot: proposal.start
});

// Handle unlikely case where strategies value array length does not match strategies length
if (result.length !== proposal.strategies.length) {
throw new Error('Strategies value length mismatch');
}

return result.map(value => parseFloat(value.toFixed(STRATEGIES_VALUE_PRECISION)));
}
24 changes: 2 additions & 22 deletions src/helpers/shutter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { randomBytes } from 'crypto';
import { arrayify } from '@ethersproject/bytes';
import { toUtf8String } from '@ethersproject/strings';
import { decrypt, init } from '@shutter-network/shutter-crypto';
import { capture } from '@snapshot-labs/snapshot-sentry';
import express from 'express';
import log from './log';
import db from './mysql';
import { fetchWithKeepAlive, getIp, jsonParse, rpcError, rpcSuccess } from './utils';
import { getIp, jsonParse, jsonRpcRequest, rpcError, rpcSuccess } from './utils';
import { updateProposalAndVotes } from '../scores';

init().then(() => log.info('[shutter] init'));
Expand Down Expand Up @@ -36,28 +35,9 @@ function idToProposal(id: string): string {
return `0x${id}`;
}

export async function rpcRequest(method, params, url: string = SHUTTER_URL) {
const init = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: randomBytes(6).toString('hex')
})
};
const res = await fetchWithKeepAlive(url, init);
const { result } = await res.json();
return result;
}

export async function getDecryptionKey(proposal: string, url: string = SHUTTER_URL) {
const id = proposalToId(proposal);
const result = await rpcRequest('get_decryption_key', ['1', id], url);
const result = await jsonRpcRequest(url, 'get_decryption_key', ['1', id]);
log.info(`[shutter] get_decryption_key ${proposal} ${JSON.stringify(result)}`);

return result;
Expand Down
42 changes: 41 additions & 1 deletion src/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createHash } from 'crypto';
import { createHash, randomUUID } from 'crypto';
import http from 'http';
import https from 'https';
import { URL } from 'url';
Expand Down Expand Up @@ -62,6 +62,46 @@ export function rpcError(res, code, e, id) {
});
}

export async function jsonRpcRequest(url: string, method: string, params: any): Promise<any> {
const init = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: randomUUID()
})
};

try {
const res = await fetchWithKeepAlive(url, init);

if (!res.ok) {
throw new Error(`HTTP error: ${res.status} ${res.statusText}`);
}

const response = await res.json();

if (response.error) {
throw new Error(
`JSON-RPC error: ${response.error.message || response.error.code || 'Unknown error'}`
);
}

return response.result;
} catch (error) {
capture(error, {
url,
request: { method, params }
});
throw error;
}
}

export function hasStrategyOverride(strategies: any[]) {
const keywords = [
'"aura-vlaura-vebal-with-overrides"',
Expand Down
27 changes: 23 additions & 4 deletions src/writer/proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import networks from '@snapshot-labs/snapshot.js/src/networks.json';
import { uniq } from 'lodash';
import { validateSpaceSettings } from './settings';
import { getPremiumNetworkIds, getSpace } from '../helpers/actions';
import { getStrategiesValue } from '../helpers/entityValue';
import log from '../helpers/log';
import { containsFlaggedLinks, flaggedAddresses } from '../helpers/moderation';
import { isMalicious } from '../helpers/monitoring';
Expand All @@ -13,6 +14,7 @@ import { captureError, getQuorum, jsonParse, validateChoices } from '../helpers/

const scoreAPIUrl = process.env.SCORE_API_URL || 'https://score.snapshot.org';
const broviderUrl = process.env.BROVIDER_URL || 'https://rpc.snapshot.org';
const LAST_CB = parseInt(process.env.LAST_CB ?? '1');

export const getProposalsCount = async (space, author) => {
const query = `
Expand Down Expand Up @@ -241,9 +243,24 @@ export async function verify(body): Promise<any> {
if (msg.payload.choices.length > choicesLimit) {
return Promise.reject(`number of choices can not exceed ${choicesLimit}`);
}

let strategiesValue: number[] = [];

try {
strategiesValue = await getStrategiesValue({
network: space.network,
start: msg.payload.start,
strategies: space.strategies
});
} catch (e: any) {
log.warn('unable to get strategies value', e.message);
return Promise.reject('failed to get strategies value');
}

return { strategiesValue };
}

export async function action(body, ipfs, receipt, id): Promise<void> {
export async function action(body, ipfs, receipt, id, context): Promise<void> {
const msg = jsonParse(body.msg);
const space = msg.space;

Expand All @@ -268,7 +285,7 @@ export async function action(body, ipfs, receipt, id): Promise<void> {
try {
quorum = await getQuorum(spaceSettings.plugins.quorum, spaceNetwork, proposalSnapshot);
} catch (e: any) {
console.log('unable to get quorum', e.message);
log.warn('unable to get quorum', e.message);
return Promise.reject('unable to get quorum');
}
}
Expand Down Expand Up @@ -301,10 +318,12 @@ export async function action(body, ipfs, receipt, id): Promise<void> {
scores_state: 'pending',
scores_total: 0,
scores_updated: 0,
vp_value_by_strategy: JSON.stringify([]),
scores_total_value: 0,
vp_value_by_strategy: JSON.stringify(context.strategiesValue),
votes: 0,
validation,
flagged: +containsFlaggedLinks(msg.payload.body)
flagged: +containsFlaggedLinks(msg.payload.body),
cb: LAST_CB
};

const query = `
Expand Down
5 changes: 5 additions & 0 deletions test/integration/ingestor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ jest.mock('@snapshot-labs/pineapple', () => {
};
});

jest.mock('../../src/helpers/entityValue', () => ({
__esModule: true,
getStrategiesValue: jest.fn(() => Promise.resolve([]))
}));

const proposalRequest = {
headers: { 'x-real-ip': '1.1.1.1' },
body: proposalInput
Expand Down
9 changes: 7 additions & 2 deletions test/integration/writer/proposal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ jest.mock('../../../src/helpers/moderation', () => {
};
});

jest.mock('../../../src/helpers/entityValue', () => ({
__esModule: true,
getStrategiesValue: jest.fn(() => Promise.resolve([]))
}));

const getSpaceMock = jest.spyOn(actionHelper, 'getSpace');
getSpaceMock.mockResolvedValue(spacesGetSpaceFixtures);

Expand All @@ -45,7 +50,7 @@ describe('writer/proposal', () => {
expect.hasAssertions();
mockContainsFlaggedLinks.mockReturnValueOnce(true);
const id = '0x01-flagged';
expect(await action(input, 'ipfs', 'receipt', id)).toBeUndefined();
expect(await action(input, 'ipfs', 'receipt', id, { strategiesValue: [] })).toBeUndefined();
expect(mockContainsFlaggedLinks).toBeCalledTimes(1);

const [proposal] = await db.queryAsync('SELECT * FROM proposals WHERE id = ?', [id]);
Expand All @@ -57,7 +62,7 @@ describe('writer/proposal', () => {
it('creates and does not flag proposal', async () => {
expect.hasAssertions();
const id = '0x02-non-flagged';
expect(await action(input, 'ipfs', 'receipt', id)).toBeUndefined();
expect(await action(input, 'ipfs', 'receipt', id, { strategiesValue: [] })).toBeUndefined();
expect(mockContainsFlaggedLinks).toBeCalledTimes(1);

const [proposal] = await db.queryAsync('SELECT * FROM proposals WHERE id = ?', [id]);
Expand Down
Loading