Skip to content

Commit

Permalink
Merge pull request #1190 from Giveth/hotfix_1186_add_matching_funds_a…
Browse files Browse the repository at this point in the history
…s_donations

Hotfix add matching funds as donations
  • Loading branch information
jainkrati authored Dec 5, 2023
2 parents b86fbf3 + 5de0225 commit ef27246
Show file tree
Hide file tree
Showing 13 changed files with 645 additions and 12 deletions.
3 changes: 3 additions & 0 deletions config/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,7 @@ MORDOR_ETC_TESTNET_SCAN_API_URL=https://etc-mordor.blockscout.com/api/v1
# https://chainlist.org/chain/63
MORDOR_ETC_TESTNET_NODE_HTTP_URL=https://rpc.mordor.etccooperative.org


# This is the address behind donation.eth
MATCHING_FUND_DONATIONS_FROM_ADDRESS=0x6e8873085530406995170Da467010565968C7C62
QF_ROUND_MAX_REWARD=0.2
2 changes: 2 additions & 0 deletions config/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,7 @@ MORDOR_ETC_TESTNET_NODE_HTTP_URL=https://rpc.mordor.etccooperative.org
# MORDOR ETC TESTNET Node URL
MORDOR_ETC_TESTNET_SCAN_API_URL=https://etc-mordor.blockscout.com/api/v1

# This is the address behind donation.eth
MATCHING_FUND_DONATIONS_FROM_ADDRESS=0x6e8873085530406995170Da467010565968C7C62


69 changes: 69 additions & 0 deletions migration/1701689359018-add_fields_to_qf_round_history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class addFieldsToQfRoundHistory1701689359018
implements MigrationInterface
{
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "qf_round_history"
ADD COLUMN "matchingFundAmount" real ,
ADD COLUMN "matchingFundPriceUsd" real ,
ADD COLUMN "matchingFundCurrency" text ;
`);

await queryRunner.query(`
ALTER TABLE "donation"
ADD COLUMN "distributedFundQfRoundId" integer;
-- If you have a foreign key constraint to enforce the relationship
ALTER TABLE "donation"
ADD CONSTRAINT "FK_donation_qfRound"
FOREIGN KEY ("distributedFundQfRoundId") REFERENCES "qf_round"("id");
`);

// These rounds are in Production but I didnt set any condition for that
// because I want this part of code be executed in staging ENV

// Alpha round in production
await queryRunner.query(`
UPDATE qf_round_history
SET
"matchingFundAmount" = "matchingFund",
"matchingFundPriceUsd" = 1,
"matchingFundCurrency" = 'WXDAI'
WHERE
id = 2 AND "matchingFund" IS NOT NULL;
`);

// Optimism round in production
await queryRunner.query(`
UPDATE qf_round_history
SET
"matchingFundAmount" = "matchingFund",
"matchingFundPriceUsd" = 1,
"matchingFundCurrency" = 'DAI'
WHERE
id = 4 AND "matchingFund" IS NOT NULL;
`);
}

async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "qf_round_history"
DROP COLUMN "matchingFundAmount",
DROP COLUMN "matchingFundPriceUsd",
DROP COLUMN "matchingFundCurrency";
`);

await queryRunner.query(`
-- If you added a foreign key constraint, remove it first
ALTER TABLE "donation"
DROP CONSTRAINT IF EXISTS "FK_donation_qfRound";
ALTER TABLE "donation"
DROP COLUMN "distributedFundQfRoundId";
`);
}
}
25 changes: 25 additions & 0 deletions migration/1701756190381-create_donationeth_user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
const donationDotEthAddress = '0x6e8873085530406995170Da467010565968C7C62'; // Address behind donation.eth ENS address;
const matchingFundDonationsFromAddress =
(process.env.MATCHING_FUND_DONATIONS_FROM_ADDRESS as string) ||
donationDotEthAddress;

export class createDonationethUser1701756190381 implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
INSERT INTO public."user" ("walletAddress", "name", "loginType", "role")
VALUES ('${matchingFundDonationsFromAddress}', 'Donation.eth', 'wallet', 'restricted')
ON CONFLICT ("walletAddress") DO NOTHING
`);
}

async down(queryRunner: QueryRunner): Promise<void> {
if (!matchingFundDonationsFromAddress) {
throw new Error('Wallet address is not defined in the configuration.');
}

await queryRunner.query(
`DELETE FROM public."user" WHERE "walletAddress" = '${matchingFundDonationsFromAddress}'`,
);
}
}
9 changes: 9 additions & 0 deletions src/entities/donation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,15 @@ export class Donation extends BaseEntity {
@Column({ nullable: true })
qfRoundId: number;

@Index()
@Field(type => QfRound, { nullable: true })
@ManyToOne(type => QfRound, { eager: true })
distributedFundQfRound: QfRound;

@RelationId((donation: Donation) => donation.distributedFundQfRound)
@Column({ nullable: true })
distributedFundQfRoundId: number;

@Index()
@Field(type => User, { nullable: true })
@ManyToOne(type => User, { eager: true, nullable: true })
Expand Down
13 changes: 13 additions & 0 deletions src/entities/qfRoundHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,23 @@ export class QfRoundHistory extends BaseEntity {
@Column({ type: 'real', nullable: true, default: 0 })
raisedFundInUsd: number;

// usd value of matching fund
@Field(type => Float, { nullable: true })
@Column({ type: 'real', nullable: true, default: 0 })
matchingFund: number;

@Field(type => Float, { nullable: true })
@Column({ type: 'real', nullable: true })
matchingFundAmount?: number;

@Field(type => Float, { nullable: true })
@Column({ type: 'real', nullable: true })
matchingFundPriceUsd?: number;

@Field(type => String, { nullable: true })
@Column({ nullable: true })
matchingFundCurrency?: string;

@Field(type => String, { nullable: true })
@Column({ nullable: true })
distributedFundTxHash: string;
Expand Down
184 changes: 176 additions & 8 deletions src/repositories/qfRoundHistoryRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,27 @@ import {
createDonationData,
createProjectData,
generateRandomEtheriumAddress,
generateRandomTxHash,
saveDonationDirectlyToDb,
saveProjectDirectlyToDb,
saveUserDirectlyToDb,
} from '../../test/testUtils';
import { QfRound } from '../entities/qfRound';
import { assert, expect } from 'chai';
import {
getProjectDonationsSqrtRootSum,
getQfRoundTotalProjectsDonationsSum,
} from './qfRoundRepository';
import { Project } from '../entities/project';
import moment from 'moment';
import {
refreshProjectDonationSummaryView,
refreshProjectEstimatedMatchingView,
} from '../services/projectViewsService';

import {
fillQfRoundHistory,
getQfRoundHistoriesThatDontHaveRelatedDonations,
getQfRoundHistory,
} from './qfRoundHistoryRepository';

describe('fillQfRoundHistory test cases', fillQfRoundHistoryTestCases);
describe(
'getQfRoundHistoriesThatDontHaveRelatedDonations test cases',
getQfRoundHistoriesThatDontHaveRelatedDonationsTestCases,
);

// TODO need to have more test cases for this conditions:
// when there is no donation for a project
Expand Down Expand Up @@ -326,3 +325,172 @@ function fillQfRoundHistoryTestCases() {
assert.equal(foundQfRoundHistory?.raisedFundInUsd, 10);
});
}

function getQfRoundHistoriesThatDontHaveRelatedDonationsTestCases() {
let qfRound: QfRound;
let firstProject: Project;
let secondProject: Project;
beforeEach(async () => {
await QfRound.update({}, { isActive: false });
qfRound = QfRound.create({
isActive: true,
name: 'test',
allocatedFund: 100,
minimumPassportScore: 8,
beginDate: new Date(),
endDate: moment().add(10, 'days').toDate(),
});
await qfRound.save();
firstProject = await saveProjectDirectlyToDb(createProjectData());
secondProject = await saveProjectDirectlyToDb(createProjectData());

firstProject.qfRounds = [qfRound];
secondProject.qfRounds = [qfRound];

await firstProject.save();
await secondProject.save();
});

afterEach(async () => {
qfRound.isActive = false;
await qfRound.save();
});

it('should return correct value for single project', async () => {
const inCompleteQfRoundHistories =
await getQfRoundHistoriesThatDontHaveRelatedDonations();
const usersDonations: number[][] = [
[1, 3], // 4
[2, 23], // 25
[3, 97], // 100
];

await Promise.all(
usersDonations.map(async valuesUsd => {
const user = await saveUserDirectlyToDb(
generateRandomEtheriumAddress(),
);
user.passportScore = 10;
await user.save();
return Promise.all(
valuesUsd.map(valueUsd => {
return saveDonationDirectlyToDb(
{
...createDonationData(),
valueUsd,
qfRoundId: qfRound.id,
status: 'verified',
},
user.id,
firstProject.id,
);
}),
);
}),
);

// if want to fill history round end date should be passed and be inactive
qfRound.endDate = moment().subtract(1, 'days').toDate();
qfRound.isActive = false;
await qfRound.save();

await fillQfRoundHistory();
const qfRoundHistory = await getQfRoundHistory({
projectId: firstProject.id,
qfRoundId: qfRound.id,
});
assert.isNotNull(qfRoundHistory);
qfRoundHistory!.distributedFundTxHash = generateRandomTxHash();
qfRoundHistory!.distributedFundNetwork = '100';
qfRoundHistory!.matchingFundAmount = 1000;
qfRoundHistory!.matchingFundCurrency = 'DAI';
qfRoundHistory!.matchingFund = 1000;
await qfRoundHistory?.save();

const inCompleteQfRoundHistories2 =
await getQfRoundHistoriesThatDontHaveRelatedDonations();
assert.equal(
inCompleteQfRoundHistories2.length - inCompleteQfRoundHistories.length,
1,
);
});
it('should return correct value for two projects', async () => {
const usersDonations: number[][] = [
[1, 3], // 4
[2, 23], // 25
[3, 97], // 100
];

await Promise.all(
usersDonations.map(async valuesUsd => {
const user = await saveUserDirectlyToDb(
generateRandomEtheriumAddress(),
);
user.passportScore = 10;
await user.save();
return Promise.all(
valuesUsd.map(async valueUsd => {
await saveDonationDirectlyToDb(
{
...createDonationData(),
valueUsd,
qfRoundId: qfRound.id,
status: 'verified',
},
user.id,
firstProject.id,
);
await saveDonationDirectlyToDb(
{
...createDonationData(),
valueUsd,
qfRoundId: qfRound.id,
status: 'verified',
},
user.id,
secondProject.id,
);
}),
);
}),
);

// if want to fill history round end date should be passed and be inactive
qfRound.endDate = moment().subtract(1, 'days').toDate();
qfRound.isActive = false;
await qfRound.save();
const inCompleteQfRoundHistories =
await getQfRoundHistoriesThatDontHaveRelatedDonations();

await fillQfRoundHistory();
const qfRoundHistory = await getQfRoundHistory({
projectId: firstProject.id,
qfRoundId: qfRound.id,
});
assert.isNotNull(qfRoundHistory);
qfRoundHistory!.distributedFundTxHash = generateRandomTxHash();
qfRoundHistory!.distributedFundNetwork = '100';
qfRoundHistory!.matchingFundAmount = 1000;
qfRoundHistory!.matchingFundCurrency = 'DAI';
qfRoundHistory!.matchingFund = 1000;
await qfRoundHistory?.save();

const qfRoundHistory2 = await getQfRoundHistory({
projectId: secondProject.id,
qfRoundId: qfRound.id,
});
assert.isNotNull(qfRoundHistory);
qfRoundHistory2!.distributedFundTxHash = generateRandomTxHash();
qfRoundHistory2!.distributedFundNetwork = '100';
qfRoundHistory2!.matchingFundAmount = 1000;
qfRoundHistory2!.matchingFundCurrency = 'DAI';
qfRoundHistory2!.matchingFund = 1000;
await qfRoundHistory2?.save();
const inCompleteQfRoundHistories2 =
await getQfRoundHistoriesThatDontHaveRelatedDonations();
assert.equal(
inCompleteQfRoundHistories2.length - inCompleteQfRoundHistories.length,
2,
);
});
}
25 changes: 25 additions & 0 deletions src/repositories/qfRoundHistoryRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,28 @@ export const getQfRoundHistory = async (params: {
const { projectId, qfRoundId } = params;
return QfRoundHistory.findOne({ where: { projectId, qfRoundId } });
};

export const getQfRoundHistoriesThatDontHaveRelatedDonations =
async (): Promise<QfRoundHistory[]> => {
try {
return QfRoundHistory.createQueryBuilder('q')
.innerJoin('qf_round', 'qr', 'qr.id = q.qfRoundId')
.innerJoin('project', 'p', 'p.id = q.projectId')
.leftJoin(
'donation',
'd',
'q.distributedFundTxHash = d.transactionId AND q.projectId = d.projectId AND d.distributedFundQfRoundId IS NOT NULL',
)
.where('d.id IS NULL')
.andWhere('q.matchingFund IS NOT NULL')
.andWhere('q.matchingFund != 0')
.andWhere('q.distributedFundTxHash IS NOT NULL')
.andWhere('q.distributedFundNetwork IS NOT NULL')
.andWhere('q.matchingFundCurrency IS NOT NULL')
.andWhere('q.matchingFundAmount IS NOT NULL')
.getMany();
} catch (e) {
logger.error('getQfRoundHistoriesThatDontHaveRelatedDonations error', e);
throw e;
}
};
Loading

0 comments on commit ef27246

Please sign in to comment.