Skip to content

Commit

Permalink
Ignore small differences of amounts when matching draft donation for … (
Browse files Browse the repository at this point in the history
#1573)

* Ignore small differences of amounts when matching draft donation for erc20

* Fix eslint errors
  • Loading branch information
mohammadranjbarz authored May 20, 2024
1 parent 9ae78db commit c42bd24
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 8 deletions.
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

0 comments on commit c42bd24

Please sign in to comment.