Skip to content

Commit

Permalink
Merge branch 'master' into feat-whitelist-custom-domain-for-walletcon…
Browse files Browse the repository at this point in the history
…nect
  • Loading branch information
wa0x6e authored Feb 24, 2025
2 parents a745833 + 556c109 commit 8e0202c
Show file tree
Hide file tree
Showing 15 changed files with 451 additions and 130 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@snapshot-labs/pineapple": "^1.1.0",
"@snapshot-labs/snapshot-metrics": "^1.4.1",
"@snapshot-labs/snapshot-sentry": "^1.5.5",
"@snapshot-labs/snapshot.js": "^0.12.44",
"@snapshot-labs/snapshot.js": "^0.12.49",
"bluebird": "^3.7.2",
"connection-string": "^1.0.1",
"cors": "^2.8.5",
Expand Down
72 changes: 60 additions & 12 deletions src/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,83 @@ import snapshot from '@snapshot-labs/snapshot.js';
import db from './mysql';
import { DEFAULT_NETWORK_ID, jsonParse, NETWORK_ID_WHITELIST } from './utils';

export async function addOrUpdateSpace(space: string, settings: any) {
if (!settings?.name) return false;
if (settings.domain) {
settings.domain = settings.domain?.replace(/(^\w+:|^)\/\//, '').replace(/\/$/, '');
function normalizeSettings(settings: any) {
const _settings = snapshot.utils.clone(settings);

if (_settings.domain) {
_settings.domain = _settings.domain?.replace(/(^\w+:|^)\/\//, '').replace(/\/$/, '');
}
if (settings.delegationPortal) {
settings.delegationPortal = {
if (_settings.delegationPortal) {
_settings.delegationPortal = {
delegationNetwork: '1',
...settings.delegationPortal
..._settings.delegationPortal
};
}

delete _settings.skinSettings;

return _settings;
}

export async function addOrUpdateSpace(id: string, settings: any) {
if (!settings?.name) return false;

const normalizedSettings = normalizeSettings(settings);

const ts = (Date.now() / 1e3).toFixed();
const query =
'INSERT INTO spaces SET ? ON DUPLICATE KEY UPDATE updated = ?, settings = ?, name = ?, hibernated = 0, domain = ?';

await db.queryAsync(query, [
{
id: space,
id,
name: settings.name,
created: ts,
updated: ts,
settings: JSON.stringify(settings),
domain: settings.domain || null
settings: JSON.stringify(normalizedSettings),
domain: normalizedSettings.domain || null
},
ts,
JSON.stringify(settings),
JSON.stringify(normalizedSettings),
settings.name,
settings.domain || null
normalizedSettings.domain || null
]);

await addOrUpdateSkin(id, settings.skinSettings);
}

export async function addOrUpdateSkin(id: string, skinSettings: Record<string, string>) {
if (!skinSettings) return false;

const COLORS = [
'bg_color',
'link_color',
'text_color',
'content_color',
'border_color',
'heading_color',
'primary_color',
'header_color'
];

await db.queryAsync(
`INSERT INTO skins
SET ?
ON DUPLICATE KEY UPDATE
${COLORS.map(color => `${color} = ?`).join(',')},
theme = COALESCE(VALUES(theme), ?),
logo = ?
`,
[
{
id,
...skinSettings
},
...COLORS.map(color => skinSettings[color]),
skinSettings.theme,
skinSettings.logo
]
);
}

export async function getProposal(space, id) {
Expand Down Expand Up @@ -83,6 +130,7 @@ export async function sxSpaceExists(network: string, spaceId: string): Promise<b
arb1: 'https://api.studio.thegraph.com/query/23545/sx-arbitrum/version/latest',
oeth: 'https://api.studio.thegraph.com/query/23545/sx-optimism/version/latest',
base: 'https://api.studio.thegraph.com/query/23545/sx-base/version/latest',
mantle: 'https://mantle-api.snapshot.box',
sn: 'https://api.snapshot.box',
'sn-sep': 'https://testnet-api.snapshot.box',
'linea-testnet':
Expand Down
54 changes: 0 additions & 54 deletions src/helpers/limits.ts

This file was deleted.

57 changes: 57 additions & 0 deletions src/helpers/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import db from './mysql';

async function getOptions<T>(
keys: string[],
defaultValue: T,
formattingFn: (val: string) => T
): Promise<Record<string, T>> {
const results = keys.reduce((acc, key) => {
acc[key] = defaultValue;
return acc;
}, {});

const options = await db.queryAsync('select name, value from options where name in (?)', [keys]);

options.forEach(result => {
results[result.name] = formattingFn(result.value);
});

return results;
}

export async function getLimit(key: string): Promise<number> {
return (await getLimits([key]))[key];
}

export async function getList(key: string): Promise<string[]> {
return (await getLists([key]))[key];
}

export async function getLimits(keys: string[]): Promise<Record<string, number>> {
return await getOptions<number>(keys, 0, val => Number(val));
}

export async function getLists(keys: string[]): Promise<Record<string, string[]>> {
return await getOptions<string[]>(keys, [], val => val.split(','));
}

export async function getSpaceType(
space: {
verified: boolean;
turbo: boolean;
flagged: boolean;
id: string;
},
withEcosystem = false
) {
let type = 'default';

if (withEcosystem && (await getList('space.ecosystem.list')).includes(space.id)) {
type = 'ecosystem';
}
if (space.flagged) type = 'flagged';
if (space.verified) type = 'verified';
if (space.turbo) type = 'turbo';

return type;
}
2 changes: 1 addition & 1 deletion src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import snapshot from '@snapshot-labs/snapshot.js';
import { Response } from 'express';
import fetch from 'node-fetch';

const MAINNET_NETWORK_ID_WHITELIST = ['s', 'eth', 'matic', 'arb1', 'oeth', 'sn', 'base'];
const MAINNET_NETWORK_ID_WHITELIST = ['s', 'eth', 'matic', 'arb1', 'oeth', 'sn', 'base', 'mantle'];
const TESTNET_NETWORK_ID_WHITELIST = ['s-tn', 'sep', 'linea-testnet', 'sn-sep'];
const broviderUrl = process.env.BROVIDER_URL ?? 'https://rpc.snapshot.org';

Expand Down
7 changes: 4 additions & 3 deletions src/writer/follow.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FOLLOWS_LIMIT_PER_USER } from '../helpers/limits';
import db from '../helpers/mysql';
import { getLimit } from '../helpers/options';
import { DEFAULT_NETWORK_ID, NETWORK_IDS } from '../helpers/utils';

export const getFollowsCount = async (follower: string): Promise<number> => {
Expand All @@ -22,9 +22,10 @@ export async function verify(message): Promise<any> {
if (follows.length !== 0) return Promise.reject('you are already following this space');

const count = await getFollowsCount(message.from);
const limit = await getLimit('user.default.follow_limit');

if (count >= FOLLOWS_LIMIT_PER_USER) {
return Promise.reject(`you can join max ${FOLLOWS_LIMIT_PER_USER} spaces`);
if (count >= limit) {
return Promise.reject(`you can join max ${limit} spaces`);
}

if (message.network && !NETWORK_IDS.includes(message.network)) {
Expand Down
31 changes: 27 additions & 4 deletions src/writer/proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import snapshot from '@snapshot-labs/snapshot.js';
import networks from '@snapshot-labs/snapshot.js/src/networks.json';
import { validateSpaceSettings } from './settings';
import { getSpace } from '../helpers/actions';
import { ACTIVE_PROPOSAL_BY_AUTHOR_LIMIT, getSpaceLimits } from '../helpers/limits';
import log from '../helpers/log';
import { containsFlaggedLinks, flaggedAddresses } from '../helpers/moderation';
import { isMalicious } from '../helpers/monitoring';
import db from '../helpers/mysql';
import { getLimits, getSpaceType } from '../helpers/options';
import { captureError, getQuorum, jsonParse, validateChoices } from '../helpers/utils';

const scoreAPIUrl = process.env.SCORE_API_URL || 'https://score.snapshot.org';
Expand Down Expand Up @@ -57,7 +57,6 @@ export async function verify(body): Promise<any> {
const created = parseInt(msg.timestamp);
const addressLC = body.address.toLowerCase();
const space = await getSpace(msg.space);

try {
await validateSpace(space);
} catch (e) {
Expand All @@ -66,6 +65,17 @@ export async function verify(body): Promise<any> {

space.id = msg.space;

const spaceType = await getSpaceType(space);
const spaceTypeWithEcosystem = await getSpaceType(space, true);

const limits = await getLimits([
`space.${spaceType}.body_limit`,
`space.${spaceType}.choices_limit`,
'space.active_proposal_limit_per_author',
`space.${spaceTypeWithEcosystem}.proposal_limit_per_day`,
`space.${spaceTypeWithEcosystem}.proposal_limit_per_month`
]);

const schemaIsValid = snapshot.utils.validateSchema(snapshot.schemas.proposal, msg.payload, {
spaceType: space.turbo ? 'turbo' : 'default'
});
Expand Down Expand Up @@ -186,16 +196,29 @@ export async function verify(body): Promise<any> {
space.id,
body.address
);
const [dayLimit, monthLimit] = getSpaceLimits(space);

const dayLimit = limits[`space.${spaceTypeWithEcosystem}.proposal_limit_per_day`];
const monthLimit = limits[`space.${spaceTypeWithEcosystem}.proposal_limit_per_month`];

if (dayCount >= dayLimit || monthCount >= monthLimit)
return Promise.reject('proposal limit reached');
if (!isAuthorized && activeProposalsByAuthor >= ACTIVE_PROPOSAL_BY_AUTHOR_LIMIT)
const activeProposalLimitPerAuthor = limits['space.active_proposal_limit_per_author'];
if (!isAuthorized && activeProposalsByAuthor >= activeProposalLimitPerAuthor)
return Promise.reject('active proposal limit reached for author');
} catch (e) {
capture(e);
return Promise.reject('failed to check proposals limit');
}

const bodyLengthLimit = limits[`space.${spaceType}.body_limit`];
if (msg.payload.body.length > bodyLengthLimit) {
return Promise.reject(`proposal body length can not exceed ${bodyLengthLimit} characters`);
}

const choicesLimit = limits[`space.${spaceType}.choices_limit`];
if (msg.payload.choices.length > choicesLimit) {
return Promise.reject(`number of choices can not exceed ${choicesLimit}`);
}
}

export async function action(body, ipfs, receipt, id): Promise<void> {
Expand Down
12 changes: 10 additions & 2 deletions src/writer/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import isEqual from 'lodash/isEqual';
import { addOrUpdateSpace, getSpace } from '../helpers/actions';
import log from '../helpers/log';
import db from '../helpers/mysql';
import { getLimit, getSpaceType } from '../helpers/options';
import {
addToWalletConnectWhitelist,
clearStampCache,
Expand Down Expand Up @@ -79,6 +80,12 @@ export async function verify(body): Promise<any> {
return Promise.reject(e);
}

const strategiesLimit = await getLimit(`space.${await getSpaceType(space)}.strategies_limit`);

if (msg.payload.strategies.length > strategiesLimit) {
return Promise.reject(`max number of strategies is ${strategiesLimit}`);
}

const controller = await snapshot.utils.getSpaceController(msg.space, DEFAULT_NETWORK, {
broviderUrl
});
Expand All @@ -88,8 +95,9 @@ export async function verify(body): Promise<any> {
const isAdmin = admins.includes(body.address.toLowerCase());
const newAdmins = (msg.payload.admins || []).map(admin => admin.toLowerCase());

if (msg.payload.domain && !space?.turbo && !space?.domain) {
return Promise.reject('domain is a turbo feature only');
if (!space?.turbo && !space?.domain) {
if (msg.payload.domain) return Promise.reject('domain is a turbo feature only');
if (msg.payload.skinSettings) return Promise.reject('skin is a turbo feature only');
}

const anotherSpaceWithDomain = (
Expand Down
Loading

0 comments on commit 8e0202c

Please sign in to comment.