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

refactor(experimental): GraphQL: Refactor program accounts filters #3098

Merged
merged 4 commits into from
Aug 14, 2024
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
5 changes: 5 additions & 0 deletions .changeset/sixty-months-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solana/rpc-graphql': minor
---

Update program accounts filters for `programAccounts` query
170 changes: 170 additions & 0 deletions packages/rpc-api/src/__tests__/get-program-accounts-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2290,4 +2290,174 @@ describe('getProgramAccounts', () => {
expect(accountInfo[0].account.data).toStrictEqual(['dGVzdCA=', 'base64']);
});
});

describe('when called with a data size filter', () => {
it('returns the matching accounts', async () => {
expect.assertions(3);
const program =
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>;

const programAccounts = await rpc
.getProgramAccounts(program, {
encoding: 'jsonParsed',
filters: [
{
dataSize: 165n, // Token account size
},
],
})
.send();

programAccounts.forEach(item => {
expect(item).toMatchObject({
account: {
data: {
parsed: {
info: {
isNative: expect.any(Boolean),
mint: expect.any(String),
owner: expect.any(String),
state: expect.any(String),
tokenAmount: {
amount: expect.any(String),
decimals: expect.any(Number),
uiAmount: expect.any(Number),
uiAmountString: expect.any(String),
},
},
type: 'account',
},
program: 'spl-token',
space: 165n, // Token account space
},
executable: false,
lamports: expect.any(BigInt),
owner: expect.any(String),
rentEpoch: expect.any(BigInt),
space: 165n, // Token account space
},
pubkey: expect.any(String),
});
});
});
});

describe('when called with a memcmpy filter', () => {
it('returns the matching accounts', async () => {
expect.assertions(1);
// See scripts/fixtures/spl-token-mint-account.json
const mint =
'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr' as Address<'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'>;
const program =
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>;

const programAccounts = await rpc
.getProgramAccounts(program, {
encoding: 'jsonParsed',
filters: [
{
memcmp: {
bytes: mint,
encoding: 'base58',
offset: 0n,
},
},
],
})
.send();

programAccounts.forEach(item => {
expect(item).toMatchObject({
account: {
data: {
parsed: {
info: {
isNative: expect.any(Boolean),
mint, // Matches mint address provided in filter.
owner: expect.any(String),
state: expect.any(String),
tokenAmount: {
amount: expect.any(String),
decimals: expect.any(Number),
uiAmount: expect.any(Number),
uiAmountString: expect.any(String),
},
},
type: 'account',
},
program: 'spl-token',
space: 165n, // Token account space
},
executable: false,
lamports: expect.any(BigInt),
owner: expect.any(String),
rentEpoch: expect.any(BigInt),
space: 165n, // Token account space
},
pubkey: expect.any(String),
});
});
});
});

describe('when called with both a data size and a memcmpy filter', () => {
it('returns the matching accounts', async () => {
expect.assertions(1);
// See scripts/fixtures/spl-token-mint-account.json
const mint =
'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr' as Address<'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'>;
const program =
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>;

const programAccounts = await rpc
.getProgramAccounts(program, {
encoding: 'jsonParsed',
filters: [
{
dataSize: 165n, // Token account size
},
{
memcmp: {
bytes: mint,
encoding: 'base58',
offset: 0n,
},
},
],
})
.send();

programAccounts.forEach(item => {
expect(item).toMatchObject({
account: {
data: {
parsed: {
info: {
isNative: expect.any(Boolean),
mint, // Matches mint address provided in filter.
owner: expect.any(String),
state: expect.any(String),
tokenAmount: {
amount: expect.any(String),
decimals: expect.any(Number),
uiAmount: expect.any(Number),
uiAmountString: expect.any(String),
},
},
type: 'account',
},
program: 'spl-token',
space: 165n, // Token account space
},
executable: false,
lamports: expect.any(BigInt),
owner: expect.any(String),
rentEpoch: expect.any(BigInt),
space: 165n, // Token account space
},
pubkey: expect.any(String),
});
});
});
});
});
161 changes: 161 additions & 0 deletions packages/rpc-graphql/src/__tests__/program-accounts-test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Address } from '@solana/addresses';
import {
GetAccountInfoApi,
GetBlockApi,
Expand Down Expand Up @@ -617,4 +618,164 @@ describe('programAccounts', () => {
});
});
});
describe('when called with a data size filter', () => {
const programAddress =
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>;

describe('when using memcmp filter', () => {
it('returns the matching accounts', async () => {
expect.assertions(1);
const variableValues = {
dataSizeFilters: [
{
dataSize: 165n, // Token account size
},
],
programAddress,
};
const source = /* GraphQL */ `
query testQuery($programAddress: Address!, $dataSizeFilters: [ProgramAccountsDataSizeFilter!]!) {
programAccounts(
programAddress: $programAddress
commitment: null
dataSizeFilters: $dataSizeFilters
) {
... on TokenAccount {
mint {
address
}
}
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
console.log(result);
expect(result).toMatchObject({
data: {
programAccounts: expect.arrayContaining([
{
mint: {
address: expect.any(String),
},
},
]),
},
});
});
});
});
describe('when called with a memcmp filter', () => {
// See scripts/fixtures/spl-token-mint-account.json
const mintAddress =
'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr' as Address<'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'>;
const programAddress =
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>;

describe('when using memcmp filter', () => {
it('returns the matching accounts', async () => {
expect.assertions(1);
const variableValues = {
memcmpFilters: [
{
bytes: mintAddress, // Mint address in data.
encoding: 'BASE_58', // Base58-encoded address.
offset: 0, // Offset 0 for mint address.
},
],
programAddress,
};
const source = /* GraphQL */ `
query testQuery($programAddress: Address!, $memcmpFilters: [ProgramAccountsMemcmpFilter!]!) {
programAccounts(
programAddress: $programAddress
commitment: null
dataSizeFilters: null
memcmpFilters: $memcmpFilters
) {
... on TokenAccount {
mint {
address
}
}
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
console.log(result);
expect(result).toMatchObject({
data: {
programAccounts: expect.arrayContaining([
{
mint: {
address: mintAddress,
},
},
]),
},
});
});
});
});

describe('when called with both a data size and a memcmpy filter', () => {
// See scripts/fixtures/spl-token-mint-account.json
const mintAddress =
'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr' as Address<'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'>;
const programAddress =
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>;

describe('when using memcmp filter', () => {
it('returns the matching accounts', async () => {
expect.assertions(1);
const variableValues = {
dataSizeFilters: [
{
dataSize: 165n, // Token account size
},
],
memcmpFilters: [
{
bytes: mintAddress, // Mint address in data.
encoding: 'BASE_58', // Base58-encoded address.
offset: 0, // Offset 0 for mint address.
},
],
programAddress,
};
const source = /* GraphQL */ `
query testQuery(
$programAddress: Address!
$dataSizeFilters: [ProgramAccountsDataSizeFilter!]!
$memcmpFilters: [ProgramAccountsMemcmpFilter!]!
) {
programAccounts(
programAddress: $programAddress
commitment: null
dataSizeFilters: $dataSizeFilters
memcmpFilters: $memcmpFilters
) {
... on TokenAccount {
mint {
address
}
}
}
}
`;
const result = await rpcGraphQL.query(source, variableValues);
console.log(result);
expect(result).toMatchObject({
data: {
programAccounts: expect.arrayContaining([
{
mint: {
address: mintAddress,
},
},
]),
},
});
});
});
});
});
13 changes: 12 additions & 1 deletion packages/rpc-graphql/src/loaders/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,18 @@ export type ProgramAccountsLoaderArgsBase = {
commitment?: Commitment;
dataSlice?: { length: number; offset: number };
encoding?: 'base58' | 'base64' | 'base64+zstd' | 'jsonParsed';
filters?: readonly { memcmp: { bytes: string; offset: number } }[];
filters?: (
| {
dataSize: bigint;
}
| {
memcmp: {
bytes: string;
encoding: 'base58' | 'base64';
offset: bigint;
};
}
)[];
minContextSlot?: Slot;
};
export type ProgramAccountsLoaderArgs = ProgramAccountsLoaderArgsBase & { programAddress: Address };
Expand Down
9 changes: 1 addition & 8 deletions packages/rpc-graphql/src/loaders/program-accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ async function loadProgramAccounts(
rpc: Rpc<GetProgramAccountsApi>,
{ programAddress, ...config }: ProgramAccountsLoaderArgs,
): Promise<ProgramAccountsLoaderValue> {
// @ts-expect-error FIX ME: https://github.com/microsoft/TypeScript/issues/43187
return await rpc
.getProgramAccounts(
programAddress,
// @ts-expect-error FIX ME: https://github.com/microsoft/TypeScript/issues/43187
config,
)
.send();
return await rpc.getProgramAccounts(programAddress, config).send();
mcintyre94 marked this conversation as resolved.
Show resolved Hide resolved
}

function createProgramAccountsBatchLoadFn(rpc: Rpc<GetProgramAccountsApi>, config: Config) {
Expand Down
Loading