Skip to content

Commit

Permalink
merge master into staging (fix conflicts)
Browse files Browse the repository at this point in the history
  • Loading branch information
CarlosQ96 committed Oct 1, 2024
2 parents de13f3c + d0eefa4 commit 0c0aea2
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 16 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

### Build status

- Develop - [![CI/CD](https://github.com/Giveth/impact-graph/actions/workflows/develop-pipeline.yml/badge.svg)](https://github.com/Giveth/impact-graph/actions/workflows/develop-pipeline.yml)
- Develop - [![CI/CD](https://github.com/Giveth/impact-graph/actions/workflows/develop-pipeline.yml/badge.svg)](https://github.com/Giveth/impact-graph/actions/workflows/develop-pipeline.yml) (deprecated)
- Staging - [![CI/CD](https://github.com/Giveth/impact-graph/actions/workflows/staging-pipeline.yml/badge.svg)](https://github.com/Giveth/impact-graph/actions/workflows/staging-pipeline.yml)
- Production - [![CI/CD](https://github.com/Giveth/impact-graph/actions/workflows/master-pipeline.yml/badge.svg)](https://github.com/Giveth/impact-graph/actions/workflows/master-pipeline.yml)

Expand Down
109 changes: 109 additions & 0 deletions migration/1726377580626-add_sponser_donations_to_givback_round_70.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import moment from 'moment';
import { MigrationInterface, QueryRunner } from 'typeorm';
import config from '../src/config';
import { AppDataSource } from '../src/orm';
import { findProjectById } from '../src/repositories/projectRepository';
import { Project } from '../src/entities/project';
import { calculateGivbackFactor } from '../src/services/givbackService';
import {
updateUserTotalDonated,
updateUserTotalReceived,
} from '../src/services/userService';
import { updateProjectStatistics } from '../src/services/projectService';
import {
refreshProjectActualMatchingView,
refreshProjectEstimatedMatchingView,
} from '../src/services/projectViewsService';
import { Donation } from '../src/entities/donation';
import { NETWORK_IDS } from '../src/provider';

const millisecondTimestampToDate = (timestamp: number): Date => {
return new Date(timestamp);
};

// https://github.com/Giveth/giveth-dapps-v2/issues/4595
const transactions: (Partial<Donation> & {
donorName?: string;
donorAddress?: string;
})[] = [
// ARBITRUM
// https://arbiscan.io/tx/0x600f1a9d23538e10e38e8c580248884b6e54ed72ae5225860cd528f10577cd8d
{
donorName: 'Jones DAO',
donorAddress: '0xAa876583c941bEAF68bE800b14f8b35Cb9fA6694',
fromWalletAddress: '0xAa876583c941bEAF68bE800b14f8b35Cb9fA6694',
toWalletAddress: '0x6e8873085530406995170Da467010565968C7C62',
projectId: 1443,
transactionId:
'0x600f1a9d23538e10e38e8c580248884b6e54ed72ae5225860cd528f10577cd8d',
currency: 'USDT',
tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
amount: 2_000,
valueUsd: 2_000,
transactionNetworkId: NETWORK_IDS.ARBITRUM_MAINNET,
createdAt: millisecondTimestampToDate(1725354026000),
},
];

export class AddSponserDonationsToGivbackRound701726377580626
implements MigrationInterface
{
async up(queryRunner: QueryRunner): Promise<void> {
const environment = config.get('ENVIRONMENT') as string;

if (environment !== 'production') {
// eslint-disable-next-line no-console
console.log('We just want to create these donations in production DB');
return;
}

await AppDataSource.initialize();
for (const tx of transactions) {
let user = (
await queryRunner.query(`SELECT * FROM public.user
WHERE lower("walletAddress")=lower('${tx.donorAddress}')`)
)[0];
if (!user) {
// eslint-disable-next-line no-console
console.log('User is not in our DB, creating .... ');
await queryRunner.query(`
INSERT INTO public.user ("walletAddress", role,"loginType", name)
VALUES('${tx?.donorAddress?.toLowerCase()}', 'restricted','wallet', '${tx.donorName}');
`);
user = (
await queryRunner.query(`SELECT * FROM public.user
WHERE lower("walletAddress")=lower('${tx.donorAddress}')`)
)[0];
}

// Set true for isTokenEligibleForGivback, isProjectVerified because Ashley mentioned we want to pay givback for them
const createdAt = moment(tx.createdAt).format('YYYY-MM-DD HH:mm:ss');
const project = (await findProjectById(
tx.projectId as number,
)) as Project;

const { givbackFactor, projectRank, powerRound, bottomRankInRound } =
await calculateGivbackFactor(tx.projectId as number);
await queryRunner.query(`
INSERT INTO donation ("toWalletAddress", "projectId", "fromWalletAddress", "userId", amount, currency, "transactionId", "transactionNetworkId", anonymous, "valueUsd", status,
"segmentNotified", "isTokenEligibleForGivback", "isProjectVerified", "createdAt", "givbackFactor", "powerRound", "projectRank", "bottomRankInRound", "qfRoundId", "tokenAddress")
VALUES ('${tx.toWalletAddress?.toLowerCase()}', ${tx.projectId}, '${tx.fromWalletAddress?.toLowerCase()}', ${user.id}, ${tx.amount}, '${tx.currency}', '${tx.transactionId?.toLowerCase()}', ${
tx.transactionNetworkId
}, false, ${tx.valueUsd}, 'verified',
true, true, true, '${createdAt}', ${givbackFactor}, ${powerRound}, ${projectRank}, ${bottomRankInRound}, ${tx.qfRoundId || null}, '${
tx.tokenAddress
}')
ON CONFLICT DO NOTHING;
`);

await updateUserTotalDonated(user.id);
await updateUserTotalReceived(project.adminUser?.id);
await updateProjectStatistics(tx.projectId as number);
}

await refreshProjectEstimatedMatchingView();
await refreshProjectActualMatchingView();
}

public async down(_queryRunner: QueryRunner): Promise<void> {}
}
10 changes: 10 additions & 0 deletions src/repositories/draftDonationRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ export async function countPendingDraftDonations(): Promise<number> {
return parseInt(res[0].count, 10);
}

export async function findDraftDonationByMatchedDonationId(
matchedDonationId: number,
): Promise<DraftDonation | null> {
return DraftDonation.findOne({
where: {
matchedDonationId,
},
});
}

export const updateDraftDonationStatus = async (params: {
donationId: number;
status: string;
Expand Down
7 changes: 5 additions & 2 deletions src/resolvers/donationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,11 @@ export class DonationResolver {
}
const usdValueSentAmountInPowerRound =
await getSumOfGivbackEligibleDonationsForSpecificRound({});
const givToken = await findTokenByNetworkAndSymbol(NETWORK_IDS.XDAI, 'GIV');
const givPrice = await getTokenPrice(NETWORK_IDS.XDAI, givToken);
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(
Expand Down
54 changes: 50 additions & 4 deletions src/services/chains/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,26 @@ function getTransactionDetailTestCases() {
errorMessages.TRANSACTION_CANT_BE_OLDER_THAN_DONATION,
);
});
it(
'should not return error when transaction time is newer than sent timestamp for HNY token transfer on XDAI,' +
'And donation is imported or relevant to draft donation',
async () => {
// https://blockscout.com/xdai/mainnet/tx/0x99e70642fe1aa03cb2db35c3e3909466e66b233840b7b1e0dd47296c878c16b4
const amount = 0.001;
const txInfo = await getTransactionInfoFromNetwork({
txHash:
'0x99e70642fe1aa03cb2db35c3e3909466e66b233840b7b1e0dd47296c878c16b4',
symbol: 'HNY',
networkId: NETWORK_IDS.XDAI,
fromAddress: '0x826976d7c600d45fb8287ca1d7c76fc8eb732030',
toAddress: '0x5A5a0732c1231D99DB8FFcA38DbEf1c8316fD3E1',
amount,
timestamp: 1617903450 + ONE_DAY,
importedFromDraftOrBackupService: true,
});
assert.isNotNull(txInfo);
},
);
it('should return transaction_not_found when it has not being mined before an hour', async () => {
const amount = 0.001;
const badFunc = async () => {
Expand Down Expand Up @@ -1019,16 +1039,42 @@ function getTransactionDetailTestCases() {
errorMessages.TRANSACTION_CANT_BE_OLDER_THAN_DONATION,
);
});
it(
'should not return error when transaction time is newer than sent timestamp for spl-token transfer on Solana,' +
'but donation is imported or relevant to draft',
async () => {
// https://explorer.solana.com/tx/2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16?cluster=devnet

const amount = 7;
const transactionInfo = await getTransactionInfoFromNetwork({
txHash:
'2tm14GVsDwXpMzxZzpEWyQnfzcUEv1DZQVQb6VdbsHcV8StoMbBtuQTkW1LJ8RhKKrAL18gbm181NgzuusiQfZ16',
symbol: 'TEST-SPL-TOKEN',
chainType: ChainType.SOLANA,
networkId: NETWORK_IDS.SOLANA_DEVNET,
fromAddress: 'BxUK9tDLeMT7AkTR2jBTQQYUxGGw6nuWbQqGtiHHfftn',
toAddress: 'FAMREy7d73N5jPdoKowQ4QFm6DKPWuYxZh6cwjNAbpkY',
timestamp: 1704357745 + ONE_DAY,
amount,
importedFromDraftOrBackupService: true,
});

assert.isOk(transactionInfo);
},
);
}

function closeToTestCases() {
it('should 0.0008436 and 0.0008658 consider as closed amount', function () {
assert.isTrue(closeTo(0.0008436, 0.0008658));
it('should not consider 0.0008436 and 0.0008658 as closed amount', function () {
assert.isFalse(closeTo(0.0008436, 0.0008658));
});
it('should 0.0001 and 0.00011 consider as closed amount', function () {
assert.isTrue(closeTo(0.0001, 0.00011));
it('should not consider 0.0001 and 0.00011 as closed amount', function () {
assert.isFalse(closeTo(0.0001, 0.00011));
});
it('should not consider 0.001 and 0.003 consider as closed amount', function () {
assert.isFalse(closeTo(0.001, 0.003));
});
it('should consider 1000.1 and 1000 consider as closed amount', function () {
assert.isTrue(closeTo(1000.1, 1000));
});
}
9 changes: 7 additions & 2 deletions src/services/chains/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface TransactionDetailInput {
safeTxHash?: string;
nonce?: number;
chainType?: ChainType;
importedFromDraftOrBackupService?: boolean;
}

export const ONE_HOUR = 60 * 60;
Expand Down Expand Up @@ -59,7 +60,11 @@ export function validateTransactionWithInputData(
);
}

if (input.timestamp - transaction.timestamp > ONE_HOUR) {
if (
// We bypass checking tx and donation time for imported donations from backup service or draft donation
!input.importedFromDraftOrBackupService &&
input.timestamp - transaction.timestamp > ONE_HOUR
) {
// because we first create donation, then transaction will be mined, the transaction always should be greater than
// donation created time, but we set one hour because maybe our server time is different with blockchain time server
logger.debug(
Expand Down Expand Up @@ -107,5 +112,5 @@ export function getAppropriateNetworkId(params: {

// This function is used to compare two numbers with a delta as a margin of error
export const closeTo = (a: number, b: number, delta = 0.001) => {
return Math.abs(a - b) < delta;
return Math.abs(1 - a / b) < delta;
};
24 changes: 17 additions & 7 deletions src/services/cronJobs/checkQRTransactionJob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,24 @@ export async function checkTransactions(
if (!transactions.length) return;

for (const transaction of transactions) {
const transactionCreatedAt = new Date(transaction.created_at).getTime();
const transactionAge = Date.now() - transactionCreatedAt;

const TWO_MINUTES = 2 * 60 * 1000;

const isNativePayment =
transaction.asset_type === 'native' &&
transaction.type === 'payment' &&
transaction.to === toWalletAddress &&
Number(transaction.amount) === amount;

const isCreateAccount =
transaction.type === 'create_account' &&
transaction.account === toWalletAddress &&
Number(transaction.starting_balance) === amount;

const isMatchingTransaction =
(transaction.asset_type === 'native' &&
transaction.type === 'payment' &&
transaction.to === toWalletAddress &&
Number(transaction.amount) === amount) ||
(transaction.type === 'create_account' &&
transaction.account === toWalletAddress &&
Number(transaction.starting_balance) === amount);
(isNativePayment || isCreateAccount) && transactionAge < TWO_MINUTES;

if (isMatchingTransaction) {
if (
Expand Down
45 changes: 45 additions & 0 deletions src/services/donationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,51 @@ function syncDonationStatusWithBlockchainNetworkTestCases() {
errorMessages.TRANSACTION_CANT_BE_OLDER_THAN_DONATION,
);
});
it(
'should change status to verified when donation is very newer than transaction' +
' but tx is imported or relevant to draft donation',
async () => {
// https://blockscout.com/xdai/mainnet/tx/0x00aef89fc40cea0cc0cb7ae5ac18c0e586dccb200b230a9caabca0e08ff7a36b
const transactionInfo = {
txHash:
'0x00aef89fc40cea0cc0cb7ae5ac18c0e586dccb200b230a9caabca0e08ff7a36b',
currency: 'USDC',
networkId: NETWORK_IDS.XDAI,
fromAddress: '0x826976d7c600d45fb8287ca1d7c76fc8eb732030',
toAddress: '0x87f1c862c166b0ceb79da7ad8d0864d53468d076',
amount: 1,
};
const user = await saveUserDirectlyToDb(transactionInfo.fromAddress);
const project = await saveProjectDirectlyToDb({
...createProjectData(),
walletAddress: transactionInfo.toAddress,
});
const donation = await saveDonationDirectlyToDb(
{
amount: transactionInfo.amount,
transactionNetworkId: transactionInfo.networkId,
transactionId: transactionInfo.txHash,
currency: transactionInfo.currency,
fromWalletAddress: transactionInfo.fromAddress,
toWalletAddress: transactionInfo.toAddress,
valueUsd: 100,
anonymous: false,
createdAt: new Date(),
status: DONATION_STATUS.PENDING,
},
user.id,
project.id,
);
donation.importDate = new Date();
await donation.save();
const updateDonation = await syncDonationStatusWithBlockchainNetwork({
donationId: donation.id,
});
assert.isOk(updateDonation);
assert.equal(updateDonation.id, donation.id);
assert.equal(updateDonation.status, DONATION_STATUS.VERIFIED);
},
);
}

function isProjectAcceptTokenTestCases() {
Expand Down
6 changes: 6 additions & 0 deletions src/services/donationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { getEvmTransactionTimestamp } from './chains/evm/transactionService';
import { getOrttoPersonAttributes } from '../adapters/notifications/NotificationCenterAdapter';
import { CustomToken, getTokenPrice } from './priceService';
import { updateProjectStatistics } from './projectService';
import { findDraftDonationByMatchedDonationId } from '../repositories/draftDonationRepository';

export const TRANSAK_COMPLETED_STATUS = 'COMPLETED';

Expand Down Expand Up @@ -228,6 +229,8 @@ export const syncDonationStatusWithBlockchainNetwork = async (params: {
if (!donation) {
throw new Error(i18n.__(translationErrorMessagesKeys.DONATION_NOT_FOUND));
}
const relevantDraftDonation =
await findDraftDonationByMatchedDonationId(donationId);

// fetch the transactionId from the safeTransaction Approval
if (!donation.transactionId && donation.safeTransactionId) {
Expand Down Expand Up @@ -261,6 +264,9 @@ export const syncDonationStatusWithBlockchainNetwork = async (params: {
chainType: donation.chainType,
safeTxHash: donation.safeTransactionId,
timestamp: donation.createdAt.getTime() / 1000,
importedFromDraftOrBackupService: Boolean(
donation.importDate || relevantDraftDonation,
),
});
donation.status = DONATION_STATUS.VERIFIED;
if (transaction.hash !== donation.transactionId) {
Expand Down

0 comments on commit 0c0aea2

Please sign in to comment.