From f13e646a1df4b1e155122e7df9d34a95c9da2f74 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 5 Nov 2024 13:35:25 +0100 Subject: [PATCH] feat: assert index canister id --- .../transactions/IcTransactions.svelte | 7 +++++++ .../src/icp/services/ic-transactions.services.ts | 6 +++--- src/frontend/src/icp/types/ic-token.ts | 4 +++- src/frontend/src/icp/utils/ic-token.utils.ts | 13 +++++++++++++ .../icp/validation/ic-token.validation.spec.ts | 16 ++++++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/frontend/src/icp/utils/ic-token.utils.ts diff --git a/src/frontend/src/icp/components/transactions/IcTransactions.svelte b/src/frontend/src/icp/components/transactions/IcTransactions.svelte index e8a68bc21c..f600ced922 100644 --- a/src/frontend/src/icp/components/transactions/IcTransactions.svelte +++ b/src/frontend/src/icp/components/transactions/IcTransactions.svelte @@ -32,6 +32,7 @@ import { modalStore } from '$lib/stores/modal.store'; import { token } from '$lib/stores/token.store'; import { last } from '$lib/utils/array.utils'; + import { isIcToken, isIcTokenCanistersStrict } from '$icp/utils/ic-token.utils'; let ckEthereum: boolean; $: ckEthereum = $tokenCkEthLedger || $tokenCkErc20Ledger; @@ -69,6 +70,12 @@ return; } + if (!isIcToken($tokenAsIcToken) || !isIcTokenCanistersStrict($tokenAsIcToken)) { + // On one hand, we assume that the parent component does not mount this component if no transactions can be fetched; on the other hand, we want to avoid displaying an error toast that could potentially appear multiple times. + // Therefore, we do not particularly display a visual error. In any case, we cannot load transactions without an Index canister. + return; + } + await loadNextTransactions({ owner: $authIdentity.getPrincipal(), identity: $authIdentity, diff --git a/src/frontend/src/icp/services/ic-transactions.services.ts b/src/frontend/src/icp/services/ic-transactions.services.ts index 09bd1c945f..88ded76762 100644 --- a/src/frontend/src/icp/services/ic-transactions.services.ts +++ b/src/frontend/src/icp/services/ic-transactions.services.ts @@ -1,7 +1,7 @@ import { getTransactions as getTransactionsIcp } from '$icp/api/icp-index.api'; import { getTransactions as getTransactionsIcrc } from '$icp/api/icrc-index-ng.api'; import { icTransactionsStore } from '$icp/stores/ic-transactions.store'; -import type { IcToken } from '$icp/types/ic-token'; +import type { IcCanistersStrict, IcToken } from '$icp/types/ic-token'; import type { IcTransaction } from '$icp/types/ic-transaction'; import { mapIcTransaction } from '$icp/utils/ic-transactions.utils'; import { mapTransactionIcpToSelf } from '$icp/utils/icp-transactions.utils'; @@ -23,7 +23,7 @@ const getTransactions = async ({ identity: OptionIdentity; start?: bigint; maxResults?: bigint; - token: IcToken; + token: IcToken & IcCanistersStrict; }): Promise => { if (standard === 'icrc') { const { transactions } = await getTransactionsIcrc({ @@ -49,7 +49,7 @@ export const loadNextTransactions = ({ identity: OptionIdentity; start?: bigint; maxResults?: bigint; - token: IcToken; + token: IcToken & IcCanistersStrict; signalEnd: () => void; }): Promise => queryAndUpdate({ diff --git a/src/frontend/src/icp/types/ic-token.ts b/src/frontend/src/icp/types/ic-token.ts index ef285fde50..1308ec82b3 100644 --- a/src/frontend/src/icp/types/ic-token.ts +++ b/src/frontend/src/icp/types/ic-token.ts @@ -1,6 +1,6 @@ import { IcAppMetadataSchema, - IcCanistersSchema, + IcCanistersSchema, IcCanistersStrictSchema, IcCkInterfaceSchema, IcCkLinkedAssetsSchema, IcCkMetadataSchema, @@ -19,6 +19,8 @@ export type IcAppMetadata = z.infer; export type IcCanisters = z.infer; +export type IcCanistersStrict = z.infer; + export type IcCkLinkedAssets = z.infer; export type IcCkMetadata = z.infer; diff --git a/src/frontend/src/icp/utils/ic-token.utils.ts b/src/frontend/src/icp/utils/ic-token.utils.ts new file mode 100644 index 0000000000..72f136a4a1 --- /dev/null +++ b/src/frontend/src/icp/utils/ic-token.utils.ts @@ -0,0 +1,13 @@ +import type { IcCanistersStrict, IcToken } from '$icp/types/ic-token'; +import { IcCanistersStrictSchema, IcTokenSchema } from '$icp/validation/ic-token.validation'; +import type { Token } from '$lib/types/token'; + +export const isIcToken = (token: Token): token is IcToken => { + const { success } = IcTokenSchema.safeParse(token); + return success; +}; + +export const isIcTokenCanistersStrict = (token: IcToken): token is IcToken & IcCanistersStrict => { + const { success } = IcCanistersStrictSchema.safeParse(token); + return success; +}; diff --git a/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts b/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts index 4f8a94a566..d2bd2bd249 100644 --- a/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts +++ b/src/frontend/src/tests/icp/validation/ic-token.validation.spec.ts @@ -131,12 +131,28 @@ describe('Schema Validation Tests', () => { }); describe('IcCanistersStrictSchema', () => { + const validToken = { + ...mockToken, + ...mockFee, + ...mockCanisters, + ...mockApp + }; + + it('should validate with correct data', () => { + expect(IcCanistersSchema.parse(validToken)).toEqual(validToken); + }); + it('should fail with missing index canister field', () => { const invalidData = { ledgerCanisterId: IC_CKBTC_LEDGER_CANISTER_ID }; expect(() => IcCanistersStrictSchema.parse(invalidData)).toThrow(); }); + + it('should fail for token with missing index canister field', () => { + const { indexCanisterId: _, ...tokenWithoutIndexCanisterId } = validToken; + expect(() => IcCanistersStrictSchema.parse(tokenWithoutIndexCanisterId)).toThrow(); + }); }); describe('IcCkLinkedAssetsSchema', () => {