From b8a47ba44c210ed723215516e01e2fb64413906b Mon Sep 17 00:00:00 2001 From: mohammadranjbarz Date: Tue, 17 Sep 2024 17:26:34 +0300 Subject: [PATCH] Release 17 Sep (#1826) * WIP Implement allocatedGivbacks function related to https://github.com/Giveth/giveth-dapps-v2/issues/4678 https://github.com/Giveth/giveth-dapps-v2/issues/4679 * allocatedGivbacks() endpoint implemented and works related to https://github.com/Giveth/giveth-dapps-v2/issues/4678 https://github.com/Giveth/giveth-dapps-v2/issues/4679 * Fix allocatedGivbacksQuery test cases --- src/repositories/donationRepository.test.ts | 366 ++++++++++++++++++++ src/repositories/donationRepository.ts | 42 +++ src/resolvers/donationResolver.test.ts | 200 +++++++++++ src/resolvers/donationResolver.ts | 64 ++++ src/resolvers/projectResolver.ts | 8 +- src/utils/tokenUtils.ts | 2 + test/graphqlQueries.ts | 15 + test/testUtils.ts | 3 + 8 files changed, 694 insertions(+), 6 deletions(-) diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index bdbe07598..7d7ff7143 100644 --- a/src/repositories/donationRepository.test.ts +++ b/src/repositories/donationRepository.test.ts @@ -21,6 +21,7 @@ import { findDonationsByTransactionId, getPendingDonationsIds, getProjectQfRoundStats, + getSumOfGivbackEligibleDonationsForSpecificRound, isVerifiedDonationExistsInQfRound, } from './donationRepository'; import { Donation, DONATION_STATUS } from '../entities/donation'; @@ -28,6 +29,7 @@ import { QfRound } from '../entities/qfRound'; import { Project } from '../entities/project'; import { refreshProjectEstimatedMatchingView } from '../services/projectViewsService'; import { calculateEstimateMatchingForProjectById } from '../utils/qfUtils'; +import { setPowerRound } from './powerRoundRepository'; describe('createDonation test cases', createDonationTestCases); @@ -61,6 +63,10 @@ describe( isVerifiedDonationExistsInQfRoundTestCases, ); describe('findDonationsToGiveth() test cases', findDonationsToGivethTestCases); +describe( + 'getSumOfGivbackEligibleDonationsForSpecificRound() test cases', + getSumOfGivbackEligibleDonationsForSpecificRoundTestCases, +); function fillQfRoundDonationsUserScoresTestCases() { let qfRound: QfRound; @@ -1456,3 +1462,363 @@ function findDonationsToGivethTestCases() { await User.remove(user); }); } + +function getSumOfGivbackEligibleDonationsForSpecificRoundTestCases() { + it('should return correct value for specific round', async () => { + // 3 donations with 2 different donor + const project = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor1 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const donor2 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const valueUsd1 = 100; + const givbackFactor1 = 0.5; + + const valueUsd2 = 200; + const givbackFactor2 = 0.65; + + const valueUsd3 = 300; + const givbackFactor3 = 0.7; + + const powerRound = 3232; + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd1, + powerRound, + givbackFactor: givbackFactor1, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd2, + powerRound, + givbackFactor: givbackFactor2, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd3, + powerRound, + givbackFactor: givbackFactor3, + isProjectVerified: true, + }, + donor2.id, + project.id, + ); + + const sumOfGivbackEligibleDonations = + await getSumOfGivbackEligibleDonationsForSpecificRound({ + powerRound, + }); + + assert.equal( + sumOfGivbackEligibleDonations, + valueUsd1 * givbackFactor1 + + valueUsd2 * givbackFactor2 + + valueUsd3 * givbackFactor3, + ); + }); + it('should return correct value for specific round, exclude donations with isProjectVerified:false', async () => { + // 3 donations with 2 different donor + const project = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor1 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const donor2 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const valueUsd1 = 100; + const givbackFactor1 = 0.5; + + const valueUsd2 = 200; + const givbackFactor2 = 0.65; + + const valueUsd3 = 300; + const givbackFactor3 = 0.7; + + const powerRound = 24234; + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd1, + powerRound, + givbackFactor: givbackFactor1, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd2, + powerRound, + givbackFactor: givbackFactor2, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd3, + powerRound, + givbackFactor: givbackFactor3, + isProjectVerified: false, + }, + donor2.id, + project.id, + ); + + const sumOfGivbackEligibleDonations = + await getSumOfGivbackEligibleDonationsForSpecificRound({ + powerRound, + }); + + assert.equal( + sumOfGivbackEligibleDonations, + valueUsd1 * givbackFactor1 + valueUsd2 * givbackFactor2, + ); + }); + it('should return correct value for specific round, exclude donations of other power Rounds', async () => { + // 3 donations with 2 different donor + const project = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor1 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const donor2 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const valueUsd1 = 100; + const givbackFactor1 = 0.5; + + const valueUsd2 = 200; + const givbackFactor2 = 0.65; + + const valueUsd3 = 300; + const givbackFactor3 = 0.7; + + const powerRound = 12321; + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd1, + powerRound, + givbackFactor: givbackFactor1, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd2, + powerRound, + givbackFactor: givbackFactor2, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd3, + powerRound: 31234231, + givbackFactor: givbackFactor3, + isProjectVerified: true, + }, + donor2.id, + project.id, + ); + + const sumOfGivbackEligibleDonations = + await getSumOfGivbackEligibleDonationsForSpecificRound({ + powerRound, + }); + + assert.equal( + sumOfGivbackEligibleDonations, + valueUsd1 * givbackFactor1 + valueUsd2 * givbackFactor2, + ); + }); + it('should return correct value for specific round, exclude donations of purple list address', async () => { + // 3 donations with 2 different donor + const project = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor1 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const valueUsd1 = 100; + const givbackFactor1 = 0.5; + + const valueUsd2 = 200; + const givbackFactor2 = 0.65; + + const valueUsd3 = 300; + const givbackFactor3 = 0.7; + + const powerRound = 1324123; + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd1, + powerRound, + givbackFactor: givbackFactor1, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd2, + powerRound, + givbackFactor: givbackFactor2, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + const verifiedProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + verified: true, + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor3 = await saveUserDirectlyToDb( + verifiedProject!.walletAddress as string, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd3, + powerRound, + givbackFactor: givbackFactor3, + isProjectVerified: true, + }, + donor3.id, + project.id, + ); + + const sumOfGivbackEligibleDonations = + await getSumOfGivbackEligibleDonationsForSpecificRound({ + powerRound, + }); + + assert.equal( + sumOfGivbackEligibleDonations, + valueUsd1 * givbackFactor1 + valueUsd2 * givbackFactor2, + ); + }); + it('should return correct value for existing powerRound in DB if we dont pass it', async () => { + // 3 donations with 2 different donor + const project = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor1 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const donor2 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const valueUsd1 = 100; + const givbackFactor1 = 0.5; + + const valueUsd2 = 200; + const givbackFactor2 = 0.65; + + const valueUsd3 = 300; + const givbackFactor3 = 0.7; + + const powerRound = 321425; + await setPowerRound(powerRound); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd1, + powerRound, + givbackFactor: givbackFactor1, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd2, + powerRound, + givbackFactor: givbackFactor2, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd3, + powerRound: 1231, + givbackFactor: givbackFactor3, + isProjectVerified: true, + }, + donor2.id, + project.id, + ); + + const sumOfGivbackEligibleDonations = + await getSumOfGivbackEligibleDonationsForSpecificRound({}); + + assert.equal( + sumOfGivbackEligibleDonations, + valueUsd1 * givbackFactor1 + valueUsd2 * givbackFactor2, + ); + }); +} diff --git a/src/repositories/donationRepository.ts b/src/repositories/donationRepository.ts index 398c735a8..4094b531b 100644 --- a/src/repositories/donationRepository.ts +++ b/src/repositories/donationRepository.ts @@ -6,6 +6,8 @@ import { ResourcesTotalPerMonthAndYear } from '../resolvers/donationResolver'; import { logger } from '../utils/logger'; import { QfRound } from '../entities/qfRound'; import { ChainType } from '../types/network'; +import { AppDataSource } from '../orm'; +import { getPowerRound } from './powerRoundRepository'; export const fillQfRoundDonationsUserScores = async (): Promise => { await Donation.query(` @@ -480,6 +482,46 @@ export const getRecentDonations = async (take: number): Promise => { .getMany(); }; +export const getSumOfGivbackEligibleDonationsForSpecificRound = async (params: { + powerRound?: number; +}): Promise => { + // This function calculates the total USD value of all donations that are eligible for Givback in a specific PowerRound + + try { + // If no powerRound is passed, get the latest one from the PowerRound table + const powerRound = params.powerRound || (await getPowerRound())?.round; + if (!powerRound) { + throw new Error('No powerRound found in the database.'); + } + + // Execute the main raw SQL query with the powerRound + const result = await AppDataSource.getDataSource().query( + ` + SELECT + SUM("donation"."valueUsd" * "donation"."givbackFactor") AS "totalUsdWithGivbackFactor" + FROM "donation" + WHERE "donation"."status" = 'verified' + AND "donation"."isProjectVerified" = true + AND "donation"."powerRound" = $1 + AND NOT EXISTS ( + SELECT 1 + FROM "project_address" "pa" + INNER JOIN "project" "p" ON "p"."id" = "pa"."projectId" + WHERE "pa"."address" = "donation"."fromWalletAddress" + AND "pa"."isRecipient" = true + AND "p"."verified" = true + ); + `, + [powerRound], + ); + + return result?.[0]?.totalUsdWithGivbackFactor ?? 0; + } catch (e) { + logger.error('getSumOfGivbackEligibleDonationsForSpecificRound() error', e); + return 0; + } +}; + export const getPendingDonationsIds = (): Promise<{ id: number }[]> => { const date = moment() .subtract({ diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 8f8f879ad..270703001 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -37,6 +37,7 @@ import { fetchNewDonorsDonationTotalUsd, fetchDonationMetricsQuery, getDonationByIdQuery, + allocatedGivbacksQuery, } from '../../test/graphqlQueries'; import { NETWORK_IDS } from '../provider'; import { User } from '../entities/user'; @@ -100,6 +101,10 @@ describe( ); describe('recentDonations() test cases', recentDonationsTestCases); describe('donationMetrics() test cases', donationMetricsTestCases); +describe( + 'allocatedGivbacksQuery() test cases', + allocatedGivbacksQueryTestCases, +); describe('getDonationById() test cases', getDonationByIdTestCases); // // describe('tokens() test cases', tokensTestCases); @@ -4964,6 +4969,201 @@ async function donationMetricsTestCases() { }); } +async function allocatedGivbacksQueryTestCases() { + it('should return allocated givbacks correctly', async () => { + // 3 donations with 2 different donor + const project = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor1 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const donor2 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const valueUsd1 = 100; + const givbackFactor1 = 0.5; + + const valueUsd2 = 200; + const givbackFactor2 = 0.65; + + const valueUsd3 = 300; + const givbackFactor3 = 0.7; + + const powerRound = 321425; + await setPowerRound(powerRound); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd1, + powerRound, + givbackFactor: givbackFactor1, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd2, + powerRound, + givbackFactor: givbackFactor2, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd3, + powerRound: 1231, + givbackFactor: givbackFactor3, + isProjectVerified: true, + }, + donor2.id, + project.id, + ); + + const result = await axios.post( + graphqlUrl, + { + query: allocatedGivbacksQuery, + }, + {}, + ); + + const allocatedGivbacks = result.data.data?.allocatedGivbacks; + assert.isNotNull(allocatedGivbacks); + assert.equal( + allocatedGivbacks.usdValueSentAmountInPowerRound, + valueUsd1 * givbackFactor1 + valueUsd2 * givbackFactor2, + ); + }); + it('should return allocated givbacks correctly when pass refreshCache variable', async () => { + // 3 donations with 2 different donor + const project = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const donor1 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const donor2 = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const valueUsd1 = 100; + const givbackFactor1 = 0.55; + + const valueUsd2 = 200; + const givbackFactor2 = 0.65; + + const valueUsd3 = 300; + const givbackFactor3 = 0.7; + + const powerRound = 438127; + await setPowerRound(powerRound); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd1, + powerRound, + givbackFactor: givbackFactor1, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd2, + powerRound, + givbackFactor: givbackFactor2, + isProjectVerified: true, + }, + donor1.id, + project.id, + ); + + const result1 = await axios.post( + graphqlUrl, + { + query: allocatedGivbacksQuery, + variables: { + refreshCache: true, + }, + }, + {}, + ); + + const allocatedGivbacks1 = result1.data.data?.allocatedGivbacks; + assert.isNotNull(allocatedGivbacks1); + assert.equal( + allocatedGivbacks1.usdValueSentAmountInPowerRound, + valueUsd1 * givbackFactor1 + valueUsd2 * givbackFactor2, + ); + + await saveDonationDirectlyToDb( + { + ...createDonationData(), + status: 'verified', + valueUsd: valueUsd3, + powerRound, + givbackFactor: givbackFactor3, + isProjectVerified: true, + }, + donor2.id, + project.id, + ); + + const result2 = await axios.post( + graphqlUrl, + { + query: allocatedGivbacksQuery, + }, + {}, + ); + + const allocatedGivbacks2 = result2.data.data?.allocatedGivbacks; + // should use the previous result because of cache + assert.equal( + allocatedGivbacks2.usdValueSentAmountInPowerRound, + allocatedGivbacks1.usdValueSentAmountInPowerRound, + ); + assert.equal(allocatedGivbacks2.date, allocatedGivbacks1.date); + + const result3 = await axios.post( + graphqlUrl, + { + query: allocatedGivbacksQuery, + variables: { + refreshCache: true, + }, + }, + {}, + ); + + const allocatedGivbacks3 = result3.data.data?.allocatedGivbacks; + + // should refresh the cache when pass refreshCache variable + assert.equal( + allocatedGivbacks3.usdValueSentAmountInPowerRound, + allocatedGivbacks1.usdValueSentAmountInPowerRound + + valueUsd3 * givbackFactor3, + ); + assert.notEqual(allocatedGivbacks3.date, allocatedGivbacks1.date); + }); +} + async function getDonationByIdTestCases() { it('should return donation by id', async () => { const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index f972f47a3..4214a5e72 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -51,6 +51,7 @@ import { donorsCountPerDateByMonthAndYear, findDonationById, getRecentDonations, + getSumOfGivbackEligibleDonationsForSpecificRound, isVerifiedDonationExistsInQfRound, newDonorsCount, newDonorsDonationTotalUsd, @@ -71,9 +72,28 @@ import { DraftDonation, } from '../entities/draftDonation'; import { nonZeroRecurringDonationsByProjectId } from '../repositories/recurringDonationRepository'; +import { getTokenPrice } from '../services/priceService'; +import { findTokenByNetworkAndSymbol } from '../utils/tokenUtils'; const draftDonationEnabled = process.env.ENABLE_DRAFT_DONATION === 'true'; +@ObjectType() +export class AllocatedGivbacks { + @Field() + usdValueSentAmountInPowerRound: number; + + @Field() + allocatedGivTokens: number; + + @Field() + givPrice: number; + + @Field() + date: Date; +} + +let allocatedGivbacksCache: AllocatedGivbacks | null; + @ObjectType() class PaginateDonations { @Field(_type => [Donation], { nullable: true }) @@ -428,6 +448,50 @@ export class DonationResolver { return getRecentDonations(take); } + @Query(_returns => AllocatedGivbacks, { nullable: true }) + async allocatedGivbacks( + @Arg('refreshCache', { nullable: true }) refreshCache?: boolean, + ): Promise { + if (allocatedGivbacksCache && !refreshCache) { + return allocatedGivbacksCache; + } + const usdValueSentAmountInPowerRound = + await getSumOfGivbackEligibleDonationsForSpecificRound({}); + const givToken = await findTokenByNetworkAndSymbol( + NETWORK_IDS.MAIN_NET, + 'GIV', + ); + const givPrice = await getTokenPrice(NETWORK_IDS.MAIN_NET, givToken); + + const maxSentGivInRound = 1_000_000; + const allocatedGivTokens = Math.ceil( + Math.min(maxSentGivInRound, usdValueSentAmountInPowerRound / givPrice), + ); + const date = new Date(); + if (givPrice) { + allocatedGivbacksCache = { + allocatedGivTokens, + givPrice, + usdValueSentAmountInPowerRound, + date, + }; + + // 60 minute cache + const sentGivbackCacheTimeout = 1000 * 60 * 60; + + setTimeout(() => { + allocatedGivbacksCache = null; + }, sentGivbackCacheTimeout); + } + + return { + usdValueSentAmountInPowerRound, + allocatedGivTokens, + givPrice, + date, + }; + } + @Query(_returns => ResourcePerDateRange, { nullable: true }) async totalDonorsCountPerDate( // fromDate and toDate should be in this format YYYYMMDD HH:mm:ss diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index 482220e0d..bafb581a1 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -1785,15 +1785,11 @@ export class ProjectResolver { // return isWalletAddressSmartContract(address); // } - /** - * Can a project use this wallet? - * @param address wallet address - * @returns - */ @Query(_returns => Boolean) async walletAddressIsValid( @Arg('address') address: string, - @Arg('chainType', { nullable: true }) chainType?: ChainType, + @Arg('chainType', () => ChainType, { nullable: true }) + chainType?: ChainType, // Explicitly set the type of chainType @Arg('memo', { nullable: true }) memo?: string, ) { return validateProjectWalletAddress(address, undefined, chainType, memo); diff --git a/src/utils/tokenUtils.ts b/src/utils/tokenUtils.ts index 35e603290..27ef09637 100644 --- a/src/utils/tokenUtils.ts +++ b/src/utils/tokenUtils.ts @@ -8,6 +8,7 @@ export const findTokenByNetworkAndSymbol = async ( address: string; symbol: string; name: string; + coingeckoId?: string; decimals: number; chainId: number; isGivbackEligible: boolean; @@ -27,6 +28,7 @@ export const findTokenByNetworkAndSymbol = async ( address: token.address, name: token.name, decimals: token.decimals, + coingeckoId: token.coingeckoId, isGivbackEligible: token.isGivbackEligible, symbol, }; diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index c05b84047..b3c4e8d14 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -2249,6 +2249,21 @@ export const getUserProjectPowerQuery = ` } `; +export const allocatedGivbacksQuery = ` + query ( + $refreshCache: Boolean + ) { + allocatedGivbacks( + refreshCache: $refreshCache + ) { + usdValueSentAmountInPowerRound + allocatedGivTokens + givPrice + date + } + } +`; + export const getBottomPowerRankQuery = ` query { getTopPowerRank diff --git a/test/testUtils.ts b/test/testUtils.ts index 5799975a5..38587eb5f 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -1952,6 +1952,9 @@ export interface CreateDonationData { useDonationBox?: boolean; relevantDonationTxHash?: string; donationPercentage?: number; + powerRound?: number; + givbackFactor?: number; + isProjectVerified?: boolean; } export interface CategoryData {