Skip to content

Commit

Permalink
feat: add filters to getProgramAccounts and getParsedProgramAccounts …
Browse files Browse the repository at this point in the history
…(#16448)

* feat: add filters to getProgramAccounts and getParsedProgramAccounts

* fix: documentation edits

* fix: make connection interface match existing interface
  • Loading branch information
oJshua committed Apr 16, 2021
1 parent 2059f14 commit 8dfdf6b
Show file tree
Hide file tree
Showing 2 changed files with 334 additions and 4 deletions.
104 changes: 101 additions & 3 deletions src/connection.ts
Expand Up @@ -1368,6 +1368,63 @@ export type StakeActivationData = {
inactive: number;
};

/**
* Data slice argument for getProgramAccounts
*/
export type DataSlice = {
/** offset of data slice */
offset: number;
/** length of data slice */
length: number;
};

/**
* Memory comparison filter for getProgramAccounts
*/
export type MemcmpFilter = {
memcmp: {
/** offset into program account data to start comparison */
offset: number;
/** data to match, as base-58 encoded string and limited to less than 129 bytes */
bytes: string;
};
};

/**
* Data size comparison filter for getProgramAccounts
*/
export type DataSizeFilter = {
/** Size of data for program account data length comparison */
dataSize: number;
};

/**
* A filter object for getProgramAccounts
*/
export type GetProgramAccountsFilter = MemcmpFilter | DataSizeFilter;

/**
* Configuration object for getProgramAccounts requests
*/
export type GetProgramAccountsConfig = {
/** Optional commitment level */
commitment?: Commitment;
/** Optional encoding for account data (default base64) */
encoding?: 'base64' | 'jsonParsed';
/** Optional data slice to limit the returned account data */
dataSlice?: DataSlice;
/** Optional array of filters to apply to accounts */
filters?: GetProgramAccountsFilter[];
};

/**
* Configuration object for getParsedProgramAccounts
*/
export type GetParsedProgramAccountsConfig = Exclude<
GetProgramAccountsConfig,
'encoding' | 'dataSlice'
>;

/**
* Information describing an account
*/
Expand Down Expand Up @@ -2080,9 +2137,34 @@ export class Connection {
*/
async getProgramAccounts(
programId: PublicKey,
commitment?: Commitment,
configOrCommitment?: GetProgramAccountsConfig | Commitment,
): Promise<Array<{pubkey: PublicKey; account: AccountInfo<Buffer>}>> {
const args = this._buildArgs([programId.toBase58()], commitment, 'base64');
const extra: Pick<GetProgramAccountsConfig, 'dataSlice' | 'filters'> = {};

let commitment;
let encoding;
if (configOrCommitment) {
if (typeof configOrCommitment === 'string') {
commitment = configOrCommitment;
} else {
commitment = configOrCommitment.commitment;
encoding = configOrCommitment.encoding;

if (configOrCommitment.dataSlice) {
extra.dataSlice = configOrCommitment.dataSlice;
}
if (configOrCommitment.filters) {
extra.filters = configOrCommitment.filters;
}
}
}

const args = this._buildArgs(
[programId.toBase58()],
commitment,
encoding || 'base64',
extra,
);
const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
const res = create(unsafeRes, jsonRpcResult(array(KeyedAccountInfoResult)));
if ('error' in res) {
Expand All @@ -2103,17 +2185,33 @@ export class Connection {
*/
async getParsedProgramAccounts(
programId: PublicKey,
commitment?: Commitment,
configOrCommitment?: GetParsedProgramAccountsConfig | Commitment,
): Promise<
Array<{
pubkey: PublicKey;
account: AccountInfo<Buffer | ParsedAccountData>;
}>
> {
const extra: Pick<GetParsedProgramAccountsConfig, 'filters'> = {};

let commitment;
if (configOrCommitment) {
if (typeof configOrCommitment === 'string') {
commitment = configOrCommitment;
} else {
commitment = configOrCommitment.commitment;

if (configOrCommitment.filters) {
extra.filters = configOrCommitment.filters;
}
}
}

const args = this._buildArgs(
[programId.toBase58()],
commitment,
'jsonParsed',
extra,
);
const unsafeRes = await this._rpcRequest('getProgramAccounts', args);
const res = create(
Expand Down
234 changes: 233 additions & 1 deletion test/connection.test.ts
Expand Up @@ -163,6 +163,59 @@ describe('Connection', () => {
const feeCalculator = (await helpers.recentBlockhash({connection}))
.feeCalculator;

{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{commitment: 'confirmed', encoding: 'base64'},
],
value: [
{
account: {
data: ['', 'base64'],
executable: false,
lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account0.publicKey.toBase58(),
},
{
account: {
data: ['', 'base64'],
executable: false,
lamports:
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account1.publicKey.toBase58(),
},
],
});

const programAccounts = await connection.getProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
},
);
expect(programAccounts).to.have.length(2);
programAccounts.forEach(function (keyedAccount) {
if (keyedAccount.pubkey.equals(account0.publicKey)) {
expect(keyedAccount.account.lamports).to.eq(
LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
);
} else {
expect(keyedAccount.pubkey).to.eql(account1.publicKey);
expect(keyedAccount.account.lamports).to.eq(
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
);
}
});
}

{
await mockRpcResponse({
method: 'getProgramAccounts',
Expand Down Expand Up @@ -214,6 +267,95 @@ describe('Connection', () => {
});
}

{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'base64',
filters: [
{
dataSize: 0,
},
],
},
],
value: [
{
account: {
data: ['', 'base64'],
executable: false,
lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account0.publicKey.toBase58(),
},
{
account: {
data: ['', 'base64'],
executable: false,
lamports:
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account1.publicKey.toBase58(),
},
],
});

const programAccountsDoMatchFilter = await connection.getProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
encoding: 'base64',
filters: [{dataSize: 0}],
},
);
expect(programAccountsDoMatchFilter).to.have.length(2);
}

{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'base64',
filters: [
{
memcmp: {
offset: 0,
bytes: 'XzdZ3w',
},
},
],
},
],
value: [],
});

const programAccountsDontMatchFilter = await connection.getProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
filters: [
{
memcmp: {
offset: 0,
bytes: 'XzdZ3w',
},
},
],
},
);
expect(programAccountsDontMatchFilter).to.have.length(0);
}

{
await mockRpcResponse({
method: 'getProgramAccounts',
Expand Down Expand Up @@ -248,7 +390,9 @@ describe('Connection', () => {

const programAccounts = await connection.getParsedProgramAccounts(
programId.publicKey,
'confirmed',
{
commitment: 'confirmed',
},
);
expect(programAccounts).to.have.length(2);

Expand All @@ -265,6 +409,94 @@ describe('Connection', () => {
}
});
}

{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'jsonParsed',
filters: [
{
dataSize: 2,
},
],
},
],
value: [],
});

const programAccountsDontMatchFilter = await connection.getParsedProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
filters: [{dataSize: 2}],
},
);
expect(programAccountsDontMatchFilter).to.have.length(0);
}

{
await mockRpcResponse({
method: 'getProgramAccounts',
params: [
programId.publicKey.toBase58(),
{
commitment: 'confirmed',
encoding: 'jsonParsed',
filters: [
{
memcmp: {
offset: 0,
bytes: '',
},
},
],
},
],
value: [
{
account: {
data: ['', 'base64'],
executable: false,
lamports: LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account0.publicKey.toBase58(),
},
{
account: {
data: ['', 'base64'],
executable: false,
lamports:
0.5 * LAMPORTS_PER_SOL - feeCalculator.lamportsPerSignature,
owner: programId.publicKey.toBase58(),
rentEpoch: 20,
},
pubkey: account1.publicKey.toBase58(),
},
],
});

const programAccountsDoMatchFilter = await connection.getParsedProgramAccounts(
programId.publicKey,
{
commitment: 'confirmed',
filters: [
{
memcmp: {
offset: 0,
bytes: '',
},
},
],
},
);
expect(programAccountsDoMatchFilter).to.have.length(2);
}
});

it('get balance', async () => {
Expand Down

0 comments on commit 8dfdf6b

Please sign in to comment.