diff --git a/apps/aggregator/src/aggregator.module.ts b/apps/aggregator/src/aggregator.module.ts index 8795d71c..2c13477c 100644 --- a/apps/aggregator/src/aggregator.module.ts +++ b/apps/aggregator/src/aggregator.module.ts @@ -14,6 +14,7 @@ import { TransactionModule } from '@sputnik-v2/transaction'; import { TransactionHandlerModule } from '@sputnik-v2/transaction-handler'; import { HttpCacheModule } from '@sputnik-v2/cache'; import { TokenModule } from '@sputnik-v2/token'; +import { ProposalModule } from '@sputnik-v2/proposal'; import { AggregatorService } from './aggregator.service'; import { DaoAggregatorModule } from './dao-aggregator/dao-aggregator.module'; @@ -35,6 +36,7 @@ import { TokenAggregatorModule } from './token-aggregator/token-aggregator.modul ScheduleModule.forRoot(), NearIndexerModule, DaoModule, + ProposalModule, TransactionHandlerModule, TransactionModule, TokenModule, diff --git a/apps/aggregator/src/aggregator.service.ts b/apps/aggregator/src/aggregator.service.ts index 0649978d..c674da5e 100644 --- a/apps/aggregator/src/aggregator.service.ts +++ b/apps/aggregator/src/aggregator.service.ts @@ -13,6 +13,7 @@ import { DaoService } from '@sputnik-v2/dao'; import { TransactionHandlerService } from '@sputnik-v2/transaction-handler'; import { CacheService } from '@sputnik-v2/cache'; import { TokenService } from '@sputnik-v2/token'; +import { ProposalService } from '@sputnik-v2/proposal'; import { DaoAggregatorService } from './dao-aggregator/dao-aggregator.service'; import { ProposalAggregatorService } from './proposal-aggregator/proposal-aggregator.service'; @@ -31,6 +32,7 @@ export class AggregatorService { private readonly transactionService: TransactionService, private readonly tokenService: TokenService, private readonly daoService: DaoService, + private readonly proposalService: ProposalService, private readonly transactionHandlerService: TransactionHandlerService, private readonly daoAggregatorService: DaoAggregatorService, private readonly proposalAggregatorService: ProposalAggregatorService, @@ -128,6 +130,8 @@ export class AggregatorService { this.state.startAggregation('dao'); + await this.proposalService.updateExpiredProposals(); + const { contractName } = this.configService.get('near'); const lastTx = await this.transactionService.lastTransaction(); diff --git a/apps/aggregator/src/proposal-aggregator/types/proposal.ts b/apps/aggregator/src/proposal-aggregator/types/proposal.ts index 965e4d5d..8ab79864 100644 --- a/apps/aggregator/src/proposal-aggregator/types/proposal.ts +++ b/apps/aggregator/src/proposal-aggregator/types/proposal.ts @@ -11,6 +11,7 @@ import { castProposalKind, ProposalActionDto, ProposalDto, + ProposalVoteStatus, } from '@sputnik-v2/proposal'; import { Transaction } from '@sputnik-v2/near-indexer'; @@ -94,6 +95,7 @@ export function castProposal( { submissionTime: proposal.submissionTime }, dao, ), + voteStatus: ProposalVoteStatus.Active, actions, transactionHash: txData?.transactionHash, createTimestamp: txData?.blockTimestamp, diff --git a/libs/dao/src/dao.service.ts b/libs/dao/src/dao.service.ts index a26f2197..4a2ad53a 100644 --- a/libs/dao/src/dao.service.ts +++ b/libs/dao/src/dao.service.ts @@ -40,7 +40,10 @@ export class DaoService extends TypeOrmCrudService { return this.daoRepository.save(daoDto); } - async search(req: CrudRequest, query: string): Promise { + async search( + req: CrudRequest, + query: string, + ): Promise { const likeQuery = `%${query.toLowerCase()}%`; const daos = await this.daoRepository .createQueryBuilder('dao') @@ -63,7 +66,10 @@ export class DaoService extends TypeOrmCrudService { ), ) .getMany(); - return paginate(daos, req.parsed.limit, req.parsed.offset); + + const daoFeed: DaoFeed[] = await this.getDaosFeed(daos); + + return paginate(daoFeed, req.parsed.limit, req.parsed.offset); } async getFeed(req: CrudRequest): Promise { @@ -76,22 +82,7 @@ export class DaoService extends TypeOrmCrudService { return daoFeedResponse as DaoFeedResponse; } - const daoIds: string[] = daos.map(({ id }) => id); - - // TODO: accelerate querying - const proposals = await this.proposalService.findProposalsByDaoIds(daoIds); - - const proposalsByDao = proposals?.reduce( - (acc, cur) => ({ - ...acc, - [cur.daoId]: [...(acc[cur.daoId] || []), cur], - }), - {}, - ); - - const daoFeed: DaoFeed[] = daos.map((dao) => - this.buildFeedFromDao(dao, proposalsByDao?.[dao.id]), - ); + const daoFeed: DaoFeed[] = await this.getDaosFeed(daos); if (daoFeedResponse instanceof Array) { return daoFeed; @@ -111,6 +102,23 @@ export class DaoService extends TypeOrmCrudService { return this.buildFeedFromDao(dao, proposals); } + async getDaosFeed(daos: Dao[]): Promise { + const daoIds: string[] = daos.map(({ id }) => id); + const proposals = await this.proposalService.findProposalsByDaoIds(daoIds); + + const proposalsByDao = proposals?.reduce( + (acc, cur) => ({ + ...acc, + [cur.daoId]: [...(acc[cur.daoId] || []), cur], + }), + {}, + ); + + return daos.map((dao) => + this.buildFeedFromDao(dao, proposalsByDao?.[dao.id]), + ); + } + private buildFeedFromDao(dao: Dao, proposals: Proposal[]): DaoFeed { return { ...dao, diff --git a/libs/migrations/src/scripts/index.ts b/libs/migrations/src/scripts/index.ts index 7f5049a6..867cb157 100644 --- a/libs/migrations/src/scripts/index.ts +++ b/libs/migrations/src/scripts/index.ts @@ -3,6 +3,7 @@ import { ProposalActionsMigration } from './proposal-actions.migration'; import { TokenIdsMigration } from './token-ids.migration'; import { DaoMetadataMigration } from './dao-metadata.migration'; import { BountyClaimEndTimeMigration } from './bounty-claim-end-time'; +import { ProposalVoteMigration } from './proposal-vote.migration'; export default [ ProposalPurgeMigration, @@ -10,4 +11,5 @@ export default [ TokenIdsMigration, DaoMetadataMigration, BountyClaimEndTimeMigration, + ProposalVoteMigration, ]; diff --git a/libs/migrations/src/scripts/proposal-vote.migration.ts b/libs/migrations/src/scripts/proposal-vote.migration.ts new file mode 100644 index 00000000..fc33064b --- /dev/null +++ b/libs/migrations/src/scripts/proposal-vote.migration.ts @@ -0,0 +1,47 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { calcProposalVotePeriodEnd } from '@sputnik-v2/utils'; +import PromisePool from '@supercharge/promise-pool'; +import { ProposalService, ProposalVoteStatus } from '@sputnik-v2/proposal'; +import { Migration } from '..'; + +@Injectable() +export class ProposalVoteMigration implements Migration { + private readonly logger = new Logger(ProposalVoteMigration.name); + + constructor(private readonly proposalService: ProposalService) {} + + public async migrate(): Promise { + this.logger.log('Starting Proposal Vote migration...'); + + this.logger.log('Collecting proposals...'); + const proposals = await this.proposalService.find(); + + const currentTimestamp = new Date().getTime() * 1000 * 1000; // nanoseconds + const migratedProposals = proposals.map((proposal) => { + const votePeriodEnd = calcProposalVotePeriodEnd(proposal, proposal.dao); + return { + ...proposal, + votePeriodEnd, + voteStatus: + votePeriodEnd < currentTimestamp + ? ProposalVoteStatus.Active + : ProposalVoteStatus.Expired, + }; + }); + + this.logger.log(`Updating migrated Proposals...`); + const { results, errors: proposalErrors } = + await PromisePool.withConcurrency(500) + .for(migratedProposals) + .process( + async (proposal) => await this.proposalService.update(proposal), + ); + + this.logger.log( + `Successfully updated Proposals: ${results.flat().length}. Errors: ${ + proposalErrors.length + }`, + ); + this.logger.log('Proposal Vote migration finished.'); + } +} diff --git a/libs/proposal/src/proposal.service.ts b/libs/proposal/src/proposal.service.ts index c02326a7..76c9825a 100644 --- a/libs/proposal/src/proposal.service.ts +++ b/libs/proposal/src/proposal.service.ts @@ -217,7 +217,6 @@ export class ProposalService extends TypeOrmCrudService { async updateExpiredProposals(): Promise { const currentTimestamp = new Date().getTime() * 1000 * 1000; // nanoseconds - return this.connection .createQueryBuilder() .update(Proposal) @@ -225,7 +224,7 @@ export class ProposalService extends TypeOrmCrudService { voteStatus: ProposalVoteStatus.Expired, }) .andWhere('status = :status', { status: ProposalStatus.InProgress }) - .andWhere('votePeriodEnd > :date', { + .andWhere('votePeriodEnd < :date', { date: currentTimestamp, }) .set({ voteStatus: ProposalVoteStatus.Expired }) diff --git a/libs/transaction-handler/src/types/proposal.ts b/libs/transaction-handler/src/types/proposal.ts index 0bd4960a..8e7db0b1 100644 --- a/libs/transaction-handler/src/types/proposal.ts +++ b/libs/transaction-handler/src/types/proposal.ts @@ -4,7 +4,11 @@ import { castProposalKind, ProposalDto, } from '@sputnik-v2/proposal/dto'; -import { Action, ProposalStatus } from '@sputnik-v2/proposal/types'; +import { + Action, + ProposalStatus, + ProposalVoteStatus, +} from '@sputnik-v2/proposal/types'; import { buildProposalId, @@ -30,6 +34,7 @@ export function castCreateProposal({ kind, type: kind.kind.type, status: ProposalStatus.InProgress, + voteStatus: ProposalVoteStatus.Active, voteCounts: {}, votes: {}, votePeriodEnd: calcProposalVotePeriodEnd(