Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change donation verification logic for imported and draft donations #1825

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
43 changes: 43 additions & 0 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,6 +1039,29 @@ 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() {
Expand Down
7 changes: 6 additions & 1 deletion 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
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
Loading