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

Verify Solana transactions #1209

Merged
merged 5 commits into from
Dec 27, 2023
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 .github/workflows/develop-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
CELO_ALFAJORES_SCAN_API_KEY: ${{ secrets.CELO_ALFAJORES_SCAN_API_KEY }}
MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }}
ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }}
SOLANA_NODE_RPC_URL: ${{ secrets.SOLANA_NODE_RPC_URL }}

publish:
needs: test
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/master-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
CELO_ALFAJORES_SCAN_API_KEY: ${{ secrets.CELO_ALFAJORES_SCAN_API_KEY }}
MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }}
ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }}
SOLANA_NODE_RPC_URL: ${{ secrets.SOLANA_NODE_RPC_URL }}

publish:
needs: test
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/staging-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ jobs:
MORDOR_ETC_TESTNET: ${{ secrets.MORDOR_ETC_TESTNET }}
ETC_NODE_HTTP_URL: ${{ secrets.ETC_NODE_HTTP_URL }}
DROP_DATABASE: ${{ secrets.DROP_DATABASE }}
SOLANA_NODE_RPC_URL: ${{ secrets.SOLANA_NODE_RPC_URL }}

publish:
needs: test
Expand Down
2 changes: 2 additions & 0 deletions config/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,5 @@ QF_ROUND_MAX_REWARD=0.2
# Rate limit config
DISABLE_SERVER_RATE_LIMITER=false


SOLANA_NODE_RPC_URL=
1 change: 1 addition & 0 deletions config/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,4 @@ MATCHING_FUND_DONATIONS_FROM_ADDRESS=0x6e8873085530406995170Da467010565968C7C62
DISABLE_SERVER_RATE_LIMITER=false


SOLANA_NODE_RPC_URL=
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@apollo/server-plugin-landing-page-graphql-playground": "4.0.0",
"@chainvine/sdk": "1.1.10",
"@giveth/monoswap": "^1.3.4",
"@safe-global/api-kit": "2.0.0",
"@safe-global/api-kit": "^2.0.0",
"@sentry/node": "6.16.1",
"@sentry/tracing": "^6.2.0",
"@solana/web3.js": "^1.87.6",
Expand Down Expand Up @@ -168,7 +168,9 @@
"test:userService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/userService.test.ts",
"test:reactionsService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/reactionsService.test.ts",
"test:powerSnapshotService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/powerSnapshotServices.test.ts",
"test:transactionsService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/transactionService.test.ts",
"test:transactionsService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts src/services/chains/index.test.ts",
"test:transactionsService:evm": "NODE_ENV=test mocha ./test/pre-test-scripts.ts src/services/chains/evm/transactionService.test.ts",
"test:transactionsService:solana": "NODE_ENV=test mocha ./test/pre-test-scripts.ts src/services/chains/solana/transactionService.test.ts",
"test:projectUpdatesService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/projectUpdatesService.test.ts",
"test:powerBoostingService": "NODE_ENV=test mocha ./test/pre-test-scripts.ts ./src/services/powerBoostingService.test.ts",
"test:blockByDateService": "NODE_ENV=test mocha ./src/services/blockByDateService.test.ts",
Expand Down
14 changes: 7 additions & 7 deletions src/server/adminJs/tabs/donationTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,10 @@ import {
import { messages } from '../../../utils/messages';
import { logger } from '../../../utils/logger';
import {
NetworkTransactionInfo,
TransactionDetailInput,
} from '../../../types/TransactionInquiry';
import {
findTransactionByHash,
findEvmTransactionByHash,
getCsvAirdropTransactions,
getGnosisSafeTransactions,
} from '../../../services/transactionService';
} from '../../../services/chains/evm/transactionService';
import {
i18n,
translationErrorMessagesKeys,
Expand All @@ -43,6 +39,10 @@ import { SelectQueryBuilder } from 'typeorm';
import { ActionContext } from 'adminjs';
import { extractAdminJsReferrerUrlParams } from '../adminJs';
import { getTwitterDonations } from '../../../services/Idriss/contractDonations';
import {
NetworkTransactionInfo,
TransactionDetailInput,
} from '../../../services/chains';

export const createDonation = async (
request: AdminJsRequestInterface,
Expand Down Expand Up @@ -85,7 +85,7 @@ export const createDonation = async (
i18n.__(translationErrorMessagesKeys.INVALID_TOKEN_SYMBOL),
);
}
const txInfo = await findTransactionByHash({
const txInfo = await findEvmTransactionByHash({
networkId,
txHash,
symbol: currency,
Expand Down
92 changes: 92 additions & 0 deletions src/services/chains/evm/transactionService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { assert } from 'chai';
import 'mocha';
import { getDisperseTransactions } from './transactionService';
import { assertThrowsAsync } from '../../../../test/testUtils';
import { errorMessages } from '../../../utils/errorMessages';
import { NETWORK_IDS } from '../../../provider';
import moment from 'moment';
describe(
'getDisperseTransactions test cases',
getDisperseTransactionsTestCases,
);

function getDisperseTransactionsTestCases() {
it('Should return transactions, for disperseEther on xdai', async () => {
// https://blockscout.com/xdai/mainnet/tx/0xfef76283e0ed4d58e0e7982b5f4ccc6867e7d4ef85b9dc78f37ee202064fd1df
const transactions = await getDisperseTransactions(
'0xfef76283e0ed4d58e0e7982b5f4ccc6867e7d4ef85b9dc78f37ee202064fd1df',
NETWORK_IDS.XDAI,
);
assert.isArray(transactions);
assert.equal(transactions.length, 7);
assert.equal(
transactions[0].from,
'0x839395e20bbb182fa440d08f850e6c7a8f6f0780',
);
assert.equal(
transactions[6].to,
'0xf2f03516e4bf21dadffc69a4c8e858497fe4edbc',
);
assert.equal(transactions[1].amount, 1760);
assert.equal(transactions[3].currency, 'XDAI');
});
it('Should return transactions, for disperseToken USDC on xdai', async () => {
// https://blockscout.com/xdai/mainnet/tx/0x44efb6052ac0496ee96aa6d3dae5e99b5c3896b8db90cad866fc25e8958173a9
const transactions = await getDisperseTransactions(
'0x44efb6052ac0496ee96aa6d3dae5e99b5c3896b8db90cad866fc25e8958173a9',
NETWORK_IDS.XDAI,
);
assert.isArray(transactions);
assert.equal(transactions.length, 9);
assert.equal(
transactions[0].from,
'0x7da9a33d15413f499299687cc9d81de84684e28e',
);
assert.equal(
transactions[8].to,
'0xa8243199049357763784ca3411090dcf3c0cc14d',
);
assert.equal(transactions[2].amount, 7.17);
assert.equal(transactions[3].currency, 'USDC');
});

// it('Should return transactions, for disperseEther on mainnet', async () => {
// // https://etherscan.io/tx/0x716a32c18bd487ea75db1838e7d778a95dfc602dca651beeae65b801cb975c99
// const transactions = await getDisperseTransactions(
// '0x716a32c18bd487ea75db1838e7d778a95dfc602dca651beeae65b801cb975c99',
// NETWORK_IDS.MAIN_NET,
// );
// assert.isArray(transactions);
// assert.equal(transactions.length, 2);
// assert.equal(
// transactions[0].from,
// '0x9adb3bbc174c73c7539cbadc7e33a83ef7bdcb31',
// );
// assert.equal(
// transactions[0].to,
// '0x669dee1a14dca82b917ab2e51110791b9253900f',
// );
// assert.equal(transactions[0].amount, 0.2);
// assert.equal(transactions[1].currency, 'ETH');
// });

// it('Should return transactions, for disperseToken USDC on mainnet', async () => {
// // https://etherscan.io/tx/0x613ab48576971933f8745e867d38fe9ac468e4b893bdd0c71cdaac34c474d18c
// const transactions = await getDisperseTransactions(
// '0x613ab48576971933f8745e867d38fe9ac468e4b893bdd0c71cdaac34c474d18c',
// NETWORK_IDS.MAIN_NET,
// );
// assert.isArray(transactions);
// assert.equal(transactions.length, 4);
// assert.equal(
// transactions[0].from,
// '0xf0fbaaa7ece80ac41508e442929b81a4c8c8543b',
// );
// assert.equal(
// transactions[0].to,
// '0x24dababee6bf5f221b64890e424609ff43d6e148',
// );
// assert.equal(transactions[2].amount, 1000);
// assert.equal(transactions[3].currency, 'USDC');
// });
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,32 @@ import abiDecoder from 'abi-decoder';
import {
findTokenByNetworkAndAddress,
findTokenByNetworkAndSymbol,
} from '../utils/tokenUtils';
} from '../../../utils/tokenUtils';
import {
errorMessages,
i18n,
translationErrorMessagesKeys,
} from '../utils/errorMessages';
import {
NetworkTransactionInfo,
TransactionDetailInput,
} from '../types/TransactionInquiry';
} from '../../../utils/errorMessages';
import axios from 'axios';
import { erc20ABI } from '../assets/erc20ABI';
import { disperseABI } from '../assets/disperseABI';
import { erc20ABI } from '../../../assets/erc20ABI';
import { disperseABI } from '../../../assets/disperseABI';
import {
getBlockExplorerApiUrl,
getNetworkNativeToken,
getProvider,
NETWORK_IDS,
} from '../provider';
import { logger } from '../utils/logger';
import { gnosisSafeL2ABI } from '../assets/gnosisSafeL2ABI';
} from '../../../provider';
import { logger } from '../../../utils/logger';
import { gnosisSafeL2ABI } from '../../../assets/gnosisSafeL2ABI';
import { NetworkTransactionInfo, TransactionDetailInput } from '../index';
import { normalizeAmount } from '../../../utils/utils';
import { ONE_HOUR, validateTransactionWithInputData } from '../index';

// tslint:disable-next-line:no-var-requires
const ethers = require('ethers');
abiDecoder.addABI(erc20ABI);
abiDecoder.addABI(gnosisSafeL2ABI);

const ONE_HOUR = 60 * 60;

export async function getTransactionInfoFromNetwork(
export async function getEvmTransactionInfoFromNetwork(
input: TransactionDetailInput,
): Promise<NetworkTransactionInfo> {
const { networkId, nonce } = input;
Expand All @@ -55,13 +51,12 @@ export async function getTransactionInfoFromNetwork(
),
);
}
let transaction: NetworkTransactionInfo | null = await findTransactionByHash(
input,
);
let transaction: NetworkTransactionInfo | null =
await findEvmTransactionByHash(input);

if (!transaction && nonce) {
// if nonce didn't pass, we can not understand whether is speedup or not
transaction = await findTransactionByNonce({
transaction = await findEvmTransactionByNonce({
input,
});
}
Expand Down Expand Up @@ -91,7 +86,9 @@ export async function getTransactionInfoFromNetwork(
return transaction;
}

export async function findTransactionByHash(input: TransactionDetailInput) {
export async function findEvmTransactionByHash(
input: TransactionDetailInput,
): Promise<NetworkTransactionInfo | null> {
const nativeToken = getNetworkNativeToken(input.networkId);
if (nativeToken.toLowerCase() === input.symbol.toLowerCase()) {
return getTransactionDetailForNormalTransfer(input);
Expand All @@ -100,7 +97,7 @@ export async function findTransactionByHash(input: TransactionDetailInput) {
}
}

async function findTransactionByNonce(data: {
async function findEvmTransactionByNonce(data: {
input: TransactionDetailInput;
page?: number;
}): Promise<NetworkTransactionInfo | null> {
Expand All @@ -116,7 +113,7 @@ async function findTransactionByNonce(data: {
if (isTransactionListEmpty) {
// we know that we reached to end of transactions
logger.debug(
'findTransactionByNonce, no more found donations for address',
'findEvmTransactionByNonce, no more found donations for address',
{
page,
address: input.fromAddress,
Expand All @@ -131,7 +128,10 @@ async function findTransactionByNonce(data: {
);

if (foundTransaction) {
return findTransactionByHash({ ...input, txHash: foundTransaction.hash });
return findEvmTransactionByHash({
...input,
txHash: foundTransaction.hash,
});
}

// userRecentTransactions just includes the transactions that source is our fromAddress
Expand All @@ -156,16 +156,12 @@ async function findTransactionByNonce(data: {
);
}

return findTransactionByNonce({
return findEvmTransactionByNonce({
input,
page: page + 1,
});
}

function normalizeAmount(amount: string, decimals: number): number {
return Number(amount) / 10 ** decimals;
}

async function getListOfTransactionsByAddress(input: {
networkId: number;
address: string;
Expand Down Expand Up @@ -388,52 +384,6 @@ async function getTransactionDetailForTokenTransfer(
};
}

function validateTransactionWithInputData(
transaction: NetworkTransactionInfo,
input: TransactionDetailInput,
): never | void {
if (transaction.to.toLowerCase() !== input.toAddress.toLowerCase()) {
throw new Error(
i18n.__(
translationErrorMessagesKeys.TRANSACTION_TO_ADDRESS_IS_DIFFERENT_FROM_SENT_TO_ADDRESS,
),
);
}

if (transaction.from.toLowerCase() !== input.fromAddress.toLowerCase()) {
throw new Error(
i18n.__(
translationErrorMessagesKeys.TRANSACTION_FROM_ADDRESS_IS_DIFFERENT_FROM_SENT_FROM_ADDRESS,
),
);
}
if (Math.abs(transaction.amount - input.amount) > 0.001) {
// We ignore small conflicts but for bigger amount we throw exception https://github.com/Giveth/impact-graph/issues/289
throw new Error(
i18n.__(
translationErrorMessagesKeys.TRANSACTION_AMOUNT_IS_DIFFERENT_WITH_SENT_AMOUNT,
),
);
}

if (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(
'i18n.__(translationErrorMessagesKeys.TRANSACTION_CANT_BE_OLDER_THAN_DONATION)',
{
transaction,
input,
},
);
throw new Error(
i18n.__(
translationErrorMessagesKeys.TRANSACTION_CANT_BE_OLDER_THAN_DONATION,
),
);
}
}

export const getDisperseTransactions = async (
txHash: string,
networkId: number,
Expand Down
Loading
Loading