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: get turbo limits from database #494

Merged
merged 13 commits into from
Feb 6, 2025
54 changes: 0 additions & 54 deletions src/helpers/limits.ts

This file was deleted.

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

type Space = { verified: boolean; turbo: boolean; flagged: boolean; id: string };
type SpaceType = 'space' | 'flagged_space' | 'verified_space' | 'turbo_space' | 'ecosystem_space';

const lists = new Map<string, string[]>();
const limits = new Map<string, number>();

async function loadOptions() {
const results = await db.queryAsync('SELECT name, value FROM options');

results.forEach(row => {
if (row.name.includes('limit')) {
limits.set(row.name, Number(row.value));
} else {
lists.set(row.name, row.value.split(','));
}
});
}

loadOptions();
wa0x6e marked this conversation as resolved.
Show resolved Hide resolved

function getSpaceType(space: Space): SpaceType {
let type: null | string = null;

if (getLists('ecosystem_spaces').includes(space.id)) {
type = 'ecosystem';
}
if (space.flagged) type = 'flagged';
if (space.verified) type = 'verified';
if (space.turbo) type = 'turbo';

return [type, 'space'].join('_') as SpaceType;
}

export function getLimits(key: string): number {
return limits.get(key) || 0;
}

export function getLists(key: string): string[] {
return lists.get(key) || [];
}

export function getSpaceProposalsLimits(space: Space, interval: 'day' | 'month'): number {
wa0x6e marked this conversation as resolved.
Show resolved Hide resolved
return getLimits(`limit.${getSpaceType(space)}.proposal.${interval}`);
}
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 { getLimits } 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 = getLimits('limit.follows_per_user');

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
10 changes: 6 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, getSpaceProposalsLimits } 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 @@ -186,11 +186,13 @@ export async function verify(body): Promise<any> {
space.id,
body.address
);
const [dayLimit, monthLimit] = getSpaceLimits(space);

if (dayCount >= dayLimit || monthCount >= monthLimit)
if (
dayCount >= getSpaceProposalsLimits(space, 'day') ||
monthCount >= getSpaceProposalsLimits(space, 'month')
)
return Promise.reject('proposal limit reached');
if (!isAuthorized && activeProposalsByAuthor >= ACTIVE_PROPOSAL_BY_AUTHOR_LIMIT)
if (!isAuthorized && activeProposalsByAuthor >= getLimits('limit.active_proposals_per_author'))
return Promise.reject('active proposal limit reached for author');
} catch (e) {
capture(e);
Expand Down
19 changes: 15 additions & 4 deletions test/integration/ingestor.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import ingestor from '../../src/ingestor';
import proposalInput from '../fixtures/ingestor-payload/proposal.json';
import { spacesGetSpaceFixtures } from '../fixtures/space';
import voteInput from '../fixtures/ingestor-payload/vote.json';
import cloneDeep from 'lodash/cloneDeep';
import omit from 'lodash/omit';
import db, { sequencerDB } from '../../src/helpers/mysql';
import relayer from '../../src/helpers/relayer';
import ingestor from '../../src/ingestor';
import proposalInput from '../fixtures/ingestor-payload/proposal.json';
import voteInput from '../fixtures/ingestor-payload/vote.json';
import { spacesGetSpaceFixtures } from '../fixtures/space';

jest.mock('../../src/helpers/options', () => {
const originalModule = jest.requireActual('../../src/helpers/options');

return {
__esModule: true,
...originalModule,
getSpaceProposalsLimits: () => 100,
getLimits: () => 100
};
});

jest.mock('../../src/helpers/moderation', () => {
const originalModule = jest.requireActual('../../src/helpers/moderation');
Expand Down
17 changes: 14 additions & 3 deletions test/integration/writer/follows.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { FOLLOWS_LIMIT_PER_USER } from '../../../src/helpers/limits';
import db, { sequencerDB } from '../../../src/helpers/mysql';
import { action, verify } from '../../../src/writer/follow';
import { spacesSqlFixtures } from '../../fixtures/space';

const LIMIT = 25;

jest.mock('../../../src/helpers/options', () => {
const originalModule = jest.requireActual('../../../src/helpers/options');

return {
__esModule: true,
...originalModule,
getLimits: () => LIMIT
};
});

describe('writer/follow', () => {
const TEST_PREFIX = 'test-follow-';
const space = spacesSqlFixtures[1];
Expand All @@ -21,7 +32,7 @@ describe('writer/follow', () => {
let i = 1;
const promises: Promise<any>[] = [];

while (i <= FOLLOWS_LIMIT_PER_USER) {
while (i <= LIMIT) {
promises.push(
db.queryAsync('INSERT INTO snapshot_sequencer_test.spaces SET ?', {
...space,
Expand All @@ -45,7 +56,7 @@ describe('writer/follow', () => {

it('rejects when the user has followed too much spaces', () => {
return expect(verify({ from: followerId })).rejects.toEqual(
`you can join max ${FOLLOWS_LIMIT_PER_USER} spaces`
`you can join max ${LIMIT} spaces`
);
});

Expand Down
73 changes: 48 additions & 25 deletions test/unit/writer/proposal.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
import omit from 'lodash/omit';
import {
ACTIVE_PROPOSAL_BY_AUTHOR_LIMIT,
ECOSYSTEM_SPACE_PROPOSAL_DAY_LIMIT,
ECOSYSTEM_SPACE_PROPOSAL_MONTH_LIMIT,
FLAGGED_SPACE_PROPOSAL_DAY_LIMIT,
FLAGGED_SPACE_PROPOSAL_MONTH_LIMIT,
MAINNET_ECOSYSTEM_SPACES,
SPACE_PROPOSAL_DAY_LIMIT,
SPACE_PROPOSAL_MONTH_LIMIT,
TURBO_SPACE_PROPOSAL_DAY_LIMIT,
TURBO_SPACE_PROPOSAL_MONTH_LIMIT,
VERIFIED_SPACE_PROPOSAL_DAY_LIMIT,
VERIFIED_SPACE_PROPOSAL_MONTH_LIMIT
} from '../../../src/helpers/limits';
import * as writer from '../../../src/writer/proposal';
import { spacesGetSpaceFixtures } from '../../fixtures/space';
import input from '../../fixtures/writer-payload/proposal.json';

const FLAGGED_ADDRESSES = ['0x0'];

const LIMITS = {
'limit.active_proposals_per_author': 20,
'limit.ecosystem_space.proposal.day': 150,
'limit.ecosystem_space.proposal.month': 750,
'limit.flagged_space.proposal.day': 5,
'limit.flagged_space.proposal.month': 7,
'limit.space.proposal.day': 10,
'limit.space.proposal.month': 150,
'limit.turbo_space.proposal.day': 40,
'limit.turbo_space.proposal.month': 200,
'limit.verified_space.proposal.day': 20,
'limit.verified_space.proposal.month': 100,
limit_follows_per_user: 25
};
const ECOSYSTEM_LIST = ['test.eth', 'snapshot.eth'];

const mockGetSpaceProposalsLimits = jest.fn((): any => {
return 1;
});
jest.mock('../../../src/helpers/options', () => {
const originalModule = jest.requireActual('../../../src/helpers/options');

return {
__esModule: true,
...originalModule,
getLists: () => {
return ECOSYSTEM_LIST;
},
getLimits: (key: string) => {
return LIMITS[key];
},
getSpaceProposalsLimits: () => mockGetSpaceProposalsLimits()
};
});

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const mockGetSpace = jest.fn((_): any => {
return spacesGetSpaceFixtures;
Expand Down Expand Up @@ -443,15 +464,16 @@ describe('writer/proposal', () => {
});

it.each([
['flagged', FLAGGED_SPACE_PROPOSAL_DAY_LIMIT, 'flagged', true],
['verified', VERIFIED_SPACE_PROPOSAL_DAY_LIMIT, 'verified', true],
['ecosystem', ECOSYSTEM_SPACE_PROPOSAL_DAY_LIMIT, 'id', MAINNET_ECOSYSTEM_SPACES[0]],
['turbo', TURBO_SPACE_PROPOSAL_DAY_LIMIT, 'turbo', true],
['normal', SPACE_PROPOSAL_DAY_LIMIT, null, null]
['flagged', LIMITS['limit.flagged_space.proposal.day'], 'flagged', true],
['verified', LIMITS['limit.verified_space.proposal.day'], 'verified', true],
['ecosystem', LIMITS['limit.ecosystem_space.proposal.day'], 'id', ECOSYSTEM_LIST[0]],
['turbo', LIMITS['limit.turbo_space.proposal.day'], 'turbo', true],
['normal', LIMITS['limit.space.proposal.day'], null, null]
])(
'rejects if the %s space has exceeded the proposal daily post limit',
async (category, limit, key, value) => {
expect.assertions(3);
mockGetSpaceProposalsLimits.mockReturnValueOnce(limit);
mockGetProposalsCount.mockResolvedValueOnce([
{ dayCount: limit + 1, monthCount: 0, activeProposalsByAuthor: 1 }
]);
Expand All @@ -468,15 +490,16 @@ describe('writer/proposal', () => {
);

it.each([
['flagged', FLAGGED_SPACE_PROPOSAL_MONTH_LIMIT, 'flagged', true],
['verified', VERIFIED_SPACE_PROPOSAL_MONTH_LIMIT, 'verified', true],
['ecosystem', ECOSYSTEM_SPACE_PROPOSAL_MONTH_LIMIT, 'id', MAINNET_ECOSYSTEM_SPACES[0]],
['turbo', TURBO_SPACE_PROPOSAL_MONTH_LIMIT, 'turbo', true],
['normal', SPACE_PROPOSAL_MONTH_LIMIT, null, null]
['flagged', LIMITS['limit.flagged_space.proposal.month'], 'flagged', true],
['verified', LIMITS['limit.verified_space.proposal.month'], 'verified', true],
['ecosystem', LIMITS['limit.ecosystem_space.proposal.month'], 'id', ECOSYSTEM_LIST[0]],
['turbo', LIMITS['limit.turbo_space.proposal.month'], 'turbo', true],
['normal', LIMITS['limit.space.proposal.month'], null, null]
])(
'rejects if the %s space has exceeded the proposal monthly post limit',
async (category, limit, key, value) => {
expect.assertions(3);
mockGetSpaceProposalsLimits.mockReturnValueOnce(limit);
mockGetProposalsCount.mockResolvedValueOnce([
{ dayCount: 0, monthCount: limit + 1, activeProposalsByAuthor: 1 }
]);
Expand All @@ -498,7 +521,7 @@ describe('writer/proposal', () => {
{
dayCount: 0,
monthCount: 0,
activeProposalsByAuthor: ACTIVE_PROPOSAL_BY_AUTHOR_LIMIT + 1
activeProposalsByAuthor: LIMITS['limit.active_proposals_per_author'] + 1
}
]);

Expand Down