Skip to content
Open
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
3 changes: 2 additions & 1 deletion js/stateless.js/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { findProgramAddressSync } from './utils/pda';
import BN from 'bn.js';
import { Buffer } from 'buffer';
import { ConfirmOptions, PublicKey } from '@solana/web3.js';
Expand Down Expand Up @@ -102,7 +103,7 @@ export const getRegisteredProgramPda = () =>
new PublicKey('35hkDgaAKwMCaxRz2ocSZ6NaUrtKkyNqU6c4RV3tYJRh');

export const getAccountCompressionAuthority = () =>
PublicKey.findProgramAddressSync(
findProgramAddressSync(
[Buffer.from('cpi_authority')],
new PublicKey(lightSystemProgram),
)[0];
Expand Down
6 changes: 2 additions & 4 deletions js/stateless.js/src/programs/system/program.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { findProgramAddressSync } from '../../utils/pda';
import BN from 'bn.js';
import {
PublicKey,
Expand Down Expand Up @@ -188,10 +189,7 @@ export class LightSystemProgram {
*/
static deriveCompressedSolPda(): PublicKey {
const seeds = [SOL_POOL_PDA_SEED];
const [address, _] = PublicKey.findProgramAddressSync(
seeds,
this.programId,
);
const [address, _] = findProgramAddressSync(seeds, this.programId);
return address;
}

Expand Down
5 changes: 3 additions & 2 deletions js/stateless.js/src/utils/instruction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { findProgramAddressSync } from './pda';
import { AccountMeta, PublicKey, SystemProgram } from '@solana/web3.js';
import { defaultStaticAccountsStruct, featureFlags } from '../constants';
import { LightSystemProgram } from '../programs';
Expand Down Expand Up @@ -169,7 +170,7 @@ export function getLightSystemAccountMetasLegacy(
config: SystemAccountMetaConfig,
): AccountMeta[] {
let signerSeed = new TextEncoder().encode('cpi_authority');
const cpiSigner = PublicKey.findProgramAddressSync(
const cpiSigner = findProgramAddressSync(
[signerSeed],
config.selfProgram,
)[0];
Expand Down Expand Up @@ -244,7 +245,7 @@ export function getLightSystemAccountMetasV2(
config: SystemAccountMetaConfig,
): AccountMeta[] {
let signerSeed = new TextEncoder().encode('cpi_authority');
const cpiSigner = PublicKey.findProgramAddressSync(
const cpiSigner = findProgramAddressSync(
[signerSeed],
config.selfProgram,
)[0];
Expand Down
63 changes: 63 additions & 0 deletions js/stateless.js/src/utils/pda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { PublicKey } from '@solana/web3.js';
import { sha256 } from '@noble/hashes/sha256';
import { Buffer } from 'buffer';

const MAX_SEED_LENGTH = 32;
const MAX_SEEDS = 16;
const PDA_MARKER_BYTES = new TextEncoder().encode('ProgramDerivedAddress');

export function createProgramAddressSync(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): PublicKey {
if (typeof PublicKey.createProgramAddressSync === 'function') {
return PublicKey.createProgramAddressSync(seeds, programId);
}
if (seeds.length > MAX_SEEDS) {
throw new TypeError('Max number of seeds exceeded');
}
let buffer = Buffer.alloc(0);
for (const seed of seeds) {
if (seed.length > MAX_SEED_LENGTH) {
throw new TypeError('Max seed length exceeded');
}
buffer = Buffer.concat([buffer, Buffer.from(seed)]);
}
const programIdBytes =
typeof programId.toBuffer === 'function'
? programId.toBuffer()
: Buffer.from(programId.toBytes());
buffer = Buffer.concat([
buffer,
programIdBytes,
Buffer.from(PDA_MARKER_BYTES),
]);
const publicKeyBytes = sha256(new Uint8Array(buffer));
if (PublicKey.isOnCurve(publicKeyBytes)) {
throw new Error('Invalid seeds, address must fall off the curve');
}
return new PublicKey(publicKeyBytes);
}

export function findProgramAddressSync(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): [PublicKey, number] {
if (typeof PublicKey.findProgramAddressSync === 'function') {
return PublicKey.findProgramAddressSync(seeds, programId);
}
let nonce = 255;
while (nonce !== 0) {
try {
const seedsWithNonce = seeds.concat(Buffer.from([nonce]));
const address = createProgramAddressSync(seedsWithNonce, programId);
return [address, nonce];
} catch (err) {
if (err instanceof TypeError) {
throw err;
}
nonce--;
}
}
throw new Error('Unable to find a viable program address nonce');
}
45 changes: 44 additions & 1 deletion js/stateless.js/tests/unit/utils/packed-accounts.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { describe, expect, it } from 'vitest';
import { PublicKey } from '@solana/web3.js';
import { PackedAccounts } from '../../../src/utils/instruction';
import {
PackedAccounts,
SystemAccountMetaConfig,
} from '../../../src/utils/instruction';
import { getAccountCompressionAuthority } from '../../../src/constants';
import { LightSystemProgram } from '../../../src/programs/system/program';

describe('PackedAccounts runtime boolean normalization', () => {
it('normalizes numeric AccountMeta flags from addPreAccountsMeta', () => {
Expand Down Expand Up @@ -36,4 +41,42 @@ describe('PackedAccounts runtime boolean normalization', () => {
expect(meta.isSigner).toBe(true);
expect(meta.isWritable).toBe(false);
});

it('derives identical system accounts when PublicKey.findProgramAddressSync is unavailable (@solana/web3.js v3)', () => {
const selfProgram = new PublicKey('11111111111111111111111111111111');
const config = SystemAccountMetaConfig.new(selfProgram);

const expectedPacked = new PackedAccounts();
expectedPacked.addSystemAccounts(config);
const expectedAuthority = getAccountCompressionAuthority();
const expectedSolPda = LightSystemProgram.deriveCompressedSolPda();

const origFind = PublicKey.findProgramAddressSync;
const origCreate = PublicKey.createProgramAddressSync;

try {
(
PublicKey as unknown as Record<string, unknown>
).findProgramAddressSync = undefined;
(
PublicKey as unknown as Record<string, unknown>
).createProgramAddressSync = undefined;

const fallbackPacked = new PackedAccounts();
fallbackPacked.addSystemAccounts(config);

expect(fallbackPacked.toAccountMetas()).toEqual(
expectedPacked.toAccountMetas(),
);
expect(getAccountCompressionAuthority().toBase58()).toBe(
expectedAuthority.toBase58(),
);
expect(LightSystemProgram.deriveCompressedSolPda().toBase58()).toBe(
expectedSolPda.toBase58(),
);
} finally {
PublicKey.findProgramAddressSync = origFind;
PublicKey.createProgramAddressSync = origCreate;
}
});
});