Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Program } from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { Cluster } from '@/app/utils/cluster';

import { resetCacheForTesting, useIdlFromAnchorProgramSeed } from '../use-idl-from-anchor-program-seed';

describe('useIdlFromAnchorProgramSeed', () => {
const url = 'https://any.rpc.address';

afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
resetCacheForTesting();
});
Comment thread
rogaldh marked this conversation as resolved.

it('should not fire duplicate /api/anchor requests for the same key while the request is in flight', () => {
const fetchMock = vi.fn(() => new Promise<Response>(() => {}));
vi.stubGlobal('fetch', fetchMock);

const programAddress = PublicKey.unique().toBase58();

let firstThrown: unknown;
let secondThrown: unknown;

try {
useIdlFromAnchorProgramSeed(programAddress, url, Cluster.MainnetBeta);
} catch (e) {
firstThrown = e;
}
try {
useIdlFromAnchorProgramSeed(programAddress, url, Cluster.MainnetBeta);
} catch (e) {
secondThrown = e;
}

expect(firstThrown).toBeInstanceOf(Promise);
expect(secondThrown).toBe(firstThrown);
expect(fetchMock).toHaveBeenCalledTimes(1);
});

it('should not call Program.fetchIdl twice for the same key while the request is in flight on a Custom cluster', () => {
const fetchIdlSpy = vi.spyOn(Program, 'fetchIdl').mockReturnValue(new Promise(() => {}));

const programAddress = PublicKey.unique().toBase58();

let firstThrown: unknown;
let secondThrown: unknown;

try {
useIdlFromAnchorProgramSeed(programAddress, url, Cluster.Custom);
} catch (e) {
firstThrown = e;
}
try {
useIdlFromAnchorProgramSeed(programAddress, url, Cluster.Custom);
} catch (e) {
secondThrown = e;
}

expect(firstThrown).toBeInstanceOf(Promise);
expect(secondThrown).toBe(firstThrown);
expect(fetchIdlSpy).toHaveBeenCalledTimes(1);
});
});
16 changes: 12 additions & 4 deletions app/entities/idl/model/use-idl-from-anchor-program-seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ export function getProvider(url: string) {
return new AnchorProvider(new Connection(url), new NodeWallet(Keypair.generate()), {});
}

function recordInFlight(key: string, promise: Promise<void>) {
cachedAnchorProgramPromises[key] = { __type: 'promise', promise };
}

export function resetCacheForTesting() {
for (const k of Object.keys(cachedAnchorProgramPromises)) {
delete cachedAnchorProgramPromises[k];
}
}

export function useIdlFromAnchorProgramSeed(programAddress: string, url: string, cluster?: Cluster): Idl | null {
const key = `${programAddress}-${url}`;
const cacheEntry = cachedAnchorProgramPromises[key];
Expand Down Expand Up @@ -43,6 +53,7 @@ export function useIdlFromAnchorProgramSeed(programAddress: string, url: string,
.catch(_ => {
cachedAnchorProgramPromises[key] = { __type: 'result', result: null };
});
recordInFlight(key, promise);
} else {
const programId = new PublicKey(programAddress);
promise = Program.fetchIdl<Idl>(programId, getProvider(url))
Expand All @@ -59,10 +70,7 @@ export function useIdlFromAnchorProgramSeed(programAddress: string, url: string,
.catch(_ => {
cachedAnchorProgramPromises[key] = { __type: 'result', result: null };
});
cachedAnchorProgramPromises[key] = {
__type: 'promise',
promise,
};
recordInFlight(key, promise);
}
throw promise;
} else if (cacheEntry.__type === 'promise') {
Expand Down
Loading