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

Ignore small differences of amounts when matching draft donation for … #1573

Merged
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
1 change: 1 addition & 0 deletions src/resolvers/draftDonationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class DraftDonationResolver {

@Mutation(_returns => Number)
async createDraftDonation(
// TODO we should change it to bigInt in both backend and frontend to not round numbers
@Arg('amount') amount: number,
@Arg('networkId') networkId: number,
@Arg('tokenAddress', { nullable: true }) tokenAddress: string,
Expand Down
38 changes: 36 additions & 2 deletions src/services/chains/evm/draftDonationService.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from 'chai';
import { assert, expect } from 'chai';
import {
saveProjectDirectlyToDb,
createProjectData,
Expand All @@ -10,7 +10,10 @@ import {
} from '../../../entities/draftDonation';
import { NETWORK_IDS } from '../../../provider';
import { ProjectAddress } from '../../../entities/projectAddress';
import { matchDraftDonations } from './draftDonationService';
import {
isAmountWithinTolerance,
matchDraftDonations,
} from './draftDonationService';
import { findUserByWalletAddress } from '../../../repositories/userRepository';
import {
DONATION_ORIGINS,
Expand All @@ -21,6 +24,7 @@ import { Project, ProjectUpdate } from '../../../entities/project';
import { User } from '../../../entities/user';

describe.skip('draftDonationMatching', draftDonationMatchingTests);
describe('isAmountWithinTolerance', isAmountWithinToleranceTests);

const RandomAddress1 = '0xf3ddeb5022a6f06b61488b48c90315087ca2beef';
const RandomAddress2 = '0xc42a4791735ae1253c50c6226832e37ede3669f5';
Expand Down Expand Up @@ -244,3 +248,33 @@ function draftDonationMatchingTests() {
expect(donation2).to.not.be.ok;
});
}

function isAmountWithinToleranceTests() {
it(`should return true for 40.5555 (405555) (0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ad3ec)
and 40.555499 (40555499)(0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ad3eb)
`, () => {
// https://gnosisscan.io/tx/0xfa65ef0a52e2f3b96c5802dcee4783858511989b7235035e8cab4d527fa15a1a
assert.isTrue(
isAmountWithinTolerance(
'0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ad3ec',
'0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ad3eb',
// Tether Decimals is 6
6,
),
);
});

it(`should return false for 40.5555 (405555) (0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ad3ec)
and 40.550571 (40550571)(0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ac0ab)
`, () => {
// https://gnosisscan.io/tx/0xfa65ef0a52e2f3b96c5802dcee4783858511989b7235035e8cab4d527fa15a1a
assert.isFalse(
isAmountWithinTolerance(
'0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ad3ec',
'0xa9059cbb000000000000000000000000b4964e1eca55db36a94e8aeffbfbab48529a2f6c00000000000000000000000000000000000000000000000000000000026ac0ab',
// Tether Decimals is 6
6,
),
);
});
}
55 changes: 49 additions & 6 deletions src/services/chains/evm/draftDonationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,32 @@ import { DonationResolver } from '../../../resolvers/donationResolver';
import { ApolloContext } from '../../../types/ApolloContext';
import { logger } from '../../../utils/logger';
import { DraftDonationWorker } from '../../../workers/draftDonationMatchWorker';
import { normalizeAmount } from '../../../utils/utils';

export const isAmountWithinTolerance = (
callData1,
callData2,
tokenDecimals,
) => {
// Define the tolerance as 0.001 tokens in terms of the full token amount
const tolerance = 0.001; // For a readable number, directly as floating point

// Function to extract and convert the amount part of the callData using BigInt for precision
function extractAmount(callData) {
const amountHex = callData.slice(-64); // Last 64 characters are the amount in hexadecimal
return BigInt('0x' + amountHex);
}

const amount1 = extractAmount(callData1);
const amount2 = extractAmount(callData2);

// Convert BigInt amounts to string then normalize
const normalizedAmount1 = normalizeAmount(amount1.toString(), tokenDecimals);
const normalizedAmount2 = normalizeAmount(amount2.toString(), tokenDecimals);

// Compare within tolerance using normalized floating point numbers
return Math.abs(normalizedAmount1 - normalizedAmount2) <= tolerance;
};

const transferErc20CallData = (to: string, amount: number, decimals = 18) => {
const iface = new ethers.utils.Interface([
Expand Down Expand Up @@ -126,17 +152,17 @@ export async function matchDraftDonations(
}
await submitMatchedDraftDonation(draftDonation, transaction);
} else {
const token = await findTokenByNetworkAndAddress(
networkId,
targetAddress,
);
// ERC20 transfer
let transferCallData = draftDonation.expectedCallData;
logger.debug('matchDraftDonations() transferCallData', {
transferCallData,
transaction,
});
if (!transferCallData) {
const token = await findTokenByNetworkAndAddress(
networkId,
targetAddress,
);
transferCallData = transferErc20CallData(
draftDonation.toWalletAddress,
draftDonation.amount,
Expand All @@ -148,12 +174,29 @@ export async function matchDraftDonations(
draftDonation.expectedCallData = transferCallData;
}

if (transaction.input.toLowerCase() !== transferCallData) {
const isToAddressAreTheSame =
transferCallData.slice(0, 64).toLowerCase() ===
transaction.input.slice(0, 64).toLocaleLowerCase();
if (
// TODO In the future we should compare exact match, but now because we save amount as number not bigInt in our db exact match with return false for some number because of rounding
!isToAddressAreTheSame ||
!isAmountWithinTolerance(
transaction.input,
transferCallData,
token.decimals,
)
) {
logger.debug(
'matchDraftDonations() transaction.input.toLowerCase() !== transferCallData',
'!isToAddressAreTheSame || !isAmountWithinTolerance(transaction.input, transferCallData, token.decimals)',
{
transferCallData,
transaction,
isToAddressAreTheSame,
isAmountWithinTolerance: isAmountWithinTolerance(
transaction.input,
transferCallData,
token.decimals,
),
},
);
continue;
Expand Down
Loading