From a3f8cd25b0ad418d9c46b9913b354f1604891155 Mon Sep 17 00:00:00 2001 From: ali ebrahimi Date: Tue, 22 Oct 2024 02:37:00 +0330 Subject: [PATCH] Sync data with reports after batch minting execution --- src/scripts/configs.ts | 4 +- src/scripts/helpers.ts | 60 +++++++++++++++ src/scripts/runFundingPotService.ts | 104 +++++++------------------- src/scripts/syncDataWithJsonReport.ts | 87 ++++----------------- 4 files changed, 102 insertions(+), 153 deletions(-) diff --git a/src/scripts/configs.ts b/src/scripts/configs.ts index 86f55685d..0d8a2d55c 100644 --- a/src/scripts/configs.ts +++ b/src/scripts/configs.ts @@ -1,7 +1,7 @@ import path from 'path'; -// Path to the local reports directory inside the repo -export const reportsDir = path.join(__dirname, 'reportFiles/output'); +export const streamStartDate = 1729500000; // should be timestamp of deploying funding pot contract in secs + // The URL of the GitHub repository containing the reports export const repoUrl = 'https://github.com/InverterNetwork/funding-pot.git'; // Local directory for cloning or pulling the latest reports diff --git a/src/scripts/helpers.ts b/src/scripts/helpers.ts index abb69a4a8..564cb3d62 100644 --- a/src/scripts/helpers.ts +++ b/src/scripts/helpers.ts @@ -1,5 +1,13 @@ /* eslint-disable no-console */ import fs from 'fs-extra'; +import { streamStartDate } from './configs'; +import { AppDataSource } from '../orm'; +import { EarlyAccessRound } from '../entities/earlyAccessRound'; +import { QfRound } from '../entities/qfRound'; + +const SIX_MONTH_IN_SEC = 15768000; +const ONE_YEAR_IN_SEC = 31536000; +const TWO_YEARS_IN_SEC = 63072000; // Function to ensure directory exists or create it export function ensureDirectoryExists(dirPath: string) { @@ -15,3 +23,55 @@ export function toScreamingSnakeCase(str: string): string { .replace(/[a-z]/g, letter => letter.toUpperCase()) // Convert lowercase letters to uppercase .replace(/[^A-Z0-9_]/g, ''); // Remove non-alphanumeric characters except underscores } + +export function getStreamDetails(isEarlyAccess: boolean) { + return { + START: streamStartDate, + CLIFF: isEarlyAccess ? ONE_YEAR_IN_SEC : SIX_MONTH_IN_SEC, + END: streamStartDate + (isEarlyAccess ? TWO_YEARS_IN_SEC : ONE_YEAR_IN_SEC), + }; +} + +export async function getRoundByBatchNumber(batchNumber: number) { + const datasource = AppDataSource.getDataSource(); + const earlyAccessRoundRepository = datasource.getRepository(EarlyAccessRound); + const qfRoundRepository = datasource.getRepository(QfRound); + + // Step 1: Check if an Early Access Round exists for the given batchNumber + let round: any; + let isEarlyAccess = true; // Set this to true if it's an Early Access Round by default + + round = await earlyAccessRoundRepository.findOne({ + where: { roundNumber: batchNumber }, + }); + + if (!round) { + // No Early Access Round found, fallback to QF Round + isEarlyAccess = false; + + // Step 2: Get the last Early Access Round to adjust the round number + const lastEarlyAccessRound = await earlyAccessRoundRepository + .createQueryBuilder('eaRound') + .orderBy('eaRound.roundNumber', 'DESC') + .getOne(); + + const lastEarlyAccessRoundNumber = lastEarlyAccessRound + ? lastEarlyAccessRound.roundNumber + : 0; + + // Step 3: Find the QF Round, add it to the number of the last Early Access Round + const qfRound = await qfRoundRepository.findOne({ + where: { roundNumber: batchNumber - lastEarlyAccessRoundNumber }, + }); + + // Step 4: If no QF round is found, throw an error + if (!qfRound) { + throw new Error( + `No Early Access or QF round found for batch number ${batchNumber}`, + ); + } + round = qfRound; + } + + return { round, isEarlyAccess }; +} diff --git a/src/scripts/runFundingPotService.ts b/src/scripts/runFundingPotService.ts index ab8961099..0b06a12d8 100644 --- a/src/scripts/runFundingPotService.ts +++ b/src/scripts/runFundingPotService.ts @@ -7,11 +7,16 @@ import { repoLocalDir, repoUrl } from './configs'; import config from '../config'; import { Project } from '../entities/project'; import { AppDataSource } from '../orm'; -import { toScreamingSnakeCase, ensureDirectoryExists } from './helpers'; +import { + toScreamingSnakeCase, + ensureDirectoryExists, + getStreamDetails, + getRoundByBatchNumber, +} from './helpers'; import { EarlyAccessRound } from '../entities/earlyAccessRound'; -import { QfRound } from '../entities/qfRound'; import { findAllEarlyAccessRounds } from '../repositories/earlyAccessRoundRepository'; import { findQfRounds } from '../repositories/qfRoundRepository'; +import { updateRewardsForDonations } from './syncDataWithJsonReport'; // Attention: the configs of batches should be saved in the funding pot repo // this script pulls the latest version of funding pot service, @@ -29,77 +34,19 @@ async function pullLatestVersionOfFundingPot() { } } -async function getRoundByBatchNumber(batchNumber: number) { - const datasource = AppDataSource.getDataSource(); - const earlyAccessRoundRepository = datasource.getRepository(EarlyAccessRound); - const qfRoundRepository = datasource.getRepository(QfRound); - - // Step 1: Check if an Early Access Round exists for the given batchNumber - let round: any; - let isEarlyAccess = true; // Set this to true if it's an Early Access Round by default - - round = await earlyAccessRoundRepository.findOne({ - where: { roundNumber: batchNumber }, - }); - - if (!round) { - // No Early Access Round found, fallback to QF Round - isEarlyAccess = false; - - // Step 2: Get the last Early Access Round to adjust the round number - const lastEarlyAccessRound = await earlyAccessRoundRepository - .createQueryBuilder('eaRound') - .orderBy('eaRound.roundNumber', 'DESC') - .getOne(); - - const lastEarlyAccessRoundNumber = lastEarlyAccessRound - ? lastEarlyAccessRound.roundNumber - : 0; - - // Step 3: Find the QF Round, add it to the number of the last Early Access Round - const qfRound = await qfRoundRepository.findOne({ - where: { roundNumber: batchNumber - lastEarlyAccessRoundNumber }, - }); - - // Step 4: If no QF round is found, throw an error - if (!qfRound) { - throw new Error( - `No Early Access or QF round found for batch number ${batchNumber}`, - ); - } - round = qfRound; - } - - return { round, isEarlyAccess }; -} - -async function generateBatchFile(batchNumber: number, onlyReport?: boolean) { +async function generateBatchFile(batchNumber: number) { console.info(`Generating batch config for batch number: ${batchNumber}`); const { round, isEarlyAccess } = await getRoundByBatchNumber(batchNumber); if (!isEarlyAccess) { round.startDate = round.beginDate; } - const datasource = AppDataSource.getDataSource(); - const earlyAccessRoundRepository = datasource.getRepository(EarlyAccessRound); - const EA1Round = await earlyAccessRoundRepository.findOne({ - where: { roundNumber: 1 }, - }); - const streamStartDate = Math.floor( - new Date(EA1Round ? EA1Round.startDate : round.startDate).getTime() / 1000, - ); // stream start date should be equal to EA1 round start date for every rounds - - // Step 5: Format the data based on the round type const batchConfig = { TIMEFRAME: { FROM_TIMESTAMP: Math.floor(new Date(round.startDate).getTime() / 1000), // Convert to timestamp TO_TIMESTAMP: Math.floor(new Date(round.endDate).getTime() / 1000), }, - VESTING_DETAILS: { - START: streamStartDate, - CLIFF: 31536000, // 1 year in sec - END: streamStartDate + 63072000, // 2 years after start - }, + VESTING_DETAILS: getStreamDetails(isEarlyAccess), LIMITS: { INDIVIDUAL: (round.roundUSDCapPerUserPerProject || '5000').toString(), // Default to 5000 for individual cap INDIVIDUAL_2: isEarlyAccess ? '0' : '250', // Only required for QACC rounds @@ -110,10 +57,9 @@ async function generateBatchFile(batchNumber: number, onlyReport?: boolean) { }, IS_EARLY_ACCESS: isEarlyAccess, // Set based on the round type PRICE: (round.tokenPrice || '0.1').toString(), // Default price to "0.1" if not provided - ONLY_REPORT: onlyReport, // If we set this flag, only report will be generated and no transactions propose to the safes + // ONLY_REPORT: onlyReport, // If we set this flag, only report will be generated and no transactions propose to the safes }; - // Step 6: Define the path to the {batchNumber}.json file inside the funding pot repo const batchFilePath = path.join( repoLocalDir, 'data', @@ -333,14 +279,13 @@ async function setBatchMintingExecutionFlag(batchNumber: number) { async function main() { try { - let batchDetails; - const batchNumberFromArg = Number(process.argv[2]); - const onlyReportFromArg = Boolean(process.argv[3]); - if (!batchNumberFromArg) { - batchDetails = await getFirstRoundThatNeedExecuteBatchMinting(); - } - const batchNumber = batchNumberFromArg || batchDetails?.batchNumber; - const onlyReport = onlyReportFromArg || batchDetails?.isExecutedBefore; + // Step 0 + console.info('Get batch number from args or calculating it...'); + const batchNumber = + Number(process.argv[2]) || + (await getFirstRoundThatNeedExecuteBatchMinting()).batchNumber; + console.info('Batch number is:', batchNumber); + // Step 1 console.info('Start pulling latest version of funding pot service...'); await pullLatestVersionOfFundingPot(); @@ -358,7 +303,7 @@ async function main() { // Step 5 console.info('Create batch config in the funding pot service...'); - await generateBatchFile(batchNumber, onlyReport); + await generateBatchFile(batchNumber); console.info('Batch config created successfully.'); // Step 4 @@ -372,11 +317,14 @@ async function main() { console.info('Funding pot service executed successfully!'); // Step 6 - if (!onlyReport) { - console.info('Setting batch minting execution flag in round data...'); - await setBatchMintingExecutionFlag(batchNumber); - console.info('Batch minting execution flag set successfully.'); - } + console.info('Setting batch minting execution flag in round data...'); + await setBatchMintingExecutionFlag(batchNumber); + console.info('Batch minting execution flag set successfully.'); + + // Step 7 + console.info('Start Syncing reward data in donations...'); + await updateRewardsForDonations(batchNumber); + console.info('Rewards data synced successfully.'); console.info('Done!'); process.exit(); } catch (error) { diff --git a/src/scripts/syncDataWithJsonReport.ts b/src/scripts/syncDataWithJsonReport.ts index 06f59b767..26b853b05 100644 --- a/src/scripts/syncDataWithJsonReport.ts +++ b/src/scripts/syncDataWithJsonReport.ts @@ -1,19 +1,13 @@ /* eslint-disable no-console */ -import fs from 'fs'; import path from 'path'; import _ from 'lodash'; import { ethers } from 'ethers'; -import { FindOptionsWhere } from 'typeorm'; +import fs from 'fs-extra'; import { Donation } from '../entities/donation'; import { Project } from '../entities/project'; import { AppDataSource } from '../orm'; -import { - InverterAdapter, - StreamingPaymentProcessorResponse, -} from '../adapters/inverter/inverterAdapter'; -import { getProvider, QACC_NETWORK_ID } from '../provider'; - -const adapter = new InverterAdapter(getProvider(QACC_NETWORK_ID)); +import { getStreamDetails } from './helpers'; +import { repoLocalDir, getReportsSubDir } from './configs'; async function loadReportFile(filePath: string) { try { @@ -40,13 +34,10 @@ function getAllReportFiles(dirPath: string) { } async function processReportForDonations( - projectOrchestratorAddress: string, donations: Donation[], reportData: any, ) { try { - const rewardInfo: StreamingPaymentProcessorResponse = - await adapter.getProjectRewardInfo(projectOrchestratorAddress); const participants = reportData.batch.data.participants; const lowerCasedParticipants = Object.keys(participants).reduce( (acc, key) => { @@ -103,32 +94,12 @@ async function processReportForDonations( } donation.rewardTokenAmount = rewardAmount || 0; - // Fetch the cliff, reward start, and end dates from the InverterAdapter - const vestingInfo = rewardInfo[0]?.vestings.find( - v => - v.recipient.toLowerCase() === - donation.fromWalletAddress.toLowerCase(), - ); + const isEarlyAccessRound = reportData.batch.config.isEarlyAccess; + const vestingInfo = getStreamDetails(isEarlyAccessRound); - if (vestingInfo) { - donation.cliff = parseFloat(vestingInfo.cliff); - donation.rewardStreamStart = new Date( - parseInt(vestingInfo.start) * 1000, - ); - donation.rewardStreamEnd = new Date(parseInt(vestingInfo.end) * 1000); - if ( - String(vestingInfo.amountRaw) !== '0' && - String(vestingInfo.amountRaw) !== - String(participantData.issuanceAllocation) - ) { - console.warn(`The reward amount and issuance allocation for project ${donation.projectId} is not match!\n - the reward raw amount is: ${vestingInfo.amountRaw} and the issuance allocation in report is: ${participantData.issuanceAllocation}`); - } - } else { - console.error( - `No vesting information found for donation ${donation.id}`, - ); - } + donation.cliff = vestingInfo.CLIFF * 1000; + donation.rewardStreamStart = new Date(vestingInfo.START * 1000); + donation.rewardStreamEnd = new Date(vestingInfo.END * 1000); await donation.save(); console.debug( @@ -142,30 +113,7 @@ async function processReportForDonations( } } -// function getRoundNumberByDonations(donations: Donation[]): number { -// // todo: we need to find round number in a better way, because maybe there left some donations from previous rounds -// if (!donations.length) { -// return 0; // Return 0 if there are no donations -// } -// -// const firstDonation = donations[0]; // Assuming all donations belong to the same round -// -// // Check if the project is in an Early Access Round or QF Round -// if (firstDonation.earlyAccessRound) { -// return firstDonation.earlyAccessRound.roundNumber; // Return the round number directly for Early Access -// } else if (firstDonation.qfRound.roundNumber) { -// return firstDonation.qfRound.roundNumber + 4; // Add 4 to the round number for QF Rounds -// } else { -// console.error( -// `No round information found for donation ${firstDonation.id}`, -// ); -// return 0; // Return 0 if no round information is found -// } -// } - -export async function updateRewardsForDonations( - donationFilter: FindOptionsWhere, -) { +export async function updateRewardsForDonations(batchNumber: number) { try { const datasource = AppDataSource.getDataSource(); const donationRepository = datasource.getRepository(Donation); @@ -174,14 +122,13 @@ export async function updateRewardsForDonations( { rewardStreamEnd: undefined }, { rewardStreamStart: undefined }, { rewardTokenAmount: undefined }, - donationFilter, ], }); const donationsByProjectId = _.groupBy(donations, 'projectId'); - const allReportFiles = getAllReportFiles( - path.join(__dirname, '/reportFiles/output'), - ); + + const reportFilesDir = path.join(repoLocalDir, getReportsSubDir()); + const allReportFiles = getAllReportFiles(reportFilesDir); for (const projectId of Object.keys(donationsByProjectId)) { console.debug(`Start processing project ${projectId} for donations.`); @@ -196,17 +143,12 @@ export async function updateRewardsForDonations( continue; } - // const roundNumber = getRoundNumberByDonations( - // donationsByProjectId[projectId], - // ); - const roundNumber = Number(process.argv[2]); - // Look for matching report files based on orchestrator address let matchedReportFile = null; for (const reportFilePath of allReportFiles) { const fileName = path.basename(reportFilePath); - if (fileName.endsWith(`${roundNumber}.json`)) { + if (fileName.endsWith(`${batchNumber}.json`)) { const reportData = await loadReportFile(reportFilePath); if (!reportData) continue; @@ -224,13 +166,12 @@ export async function updateRewardsForDonations( if (!matchedReportFile) { console.error( - `No matching report found for project with orchestrator address ${project.abc.orchestratorAddress}, for round number ${roundNumber}`, + `No matching report found for project with orchestrator address ${project.abc.orchestratorAddress}, for batch number ${batchNumber}`, ); continue; } await processReportForDonations( - project.abc.orchestratorAddress, donationsByProjectId[projectId], matchedReportFile, );