import {expect} from 'chai'; import {CompiledKeyMeta, CompiledKeys} from '../../src/message/compiled-keys'; import {AddressLookupTableAccount} from '../../src/programs'; import {PublicKey} from '../../src/publickey'; import {AccountMeta, TransactionInstruction} from '../../src/transaction'; function createTestKeys(count: number): Array { return new Array(count).fill(0).map(() => PublicKey.unique()); } function createTestLookupTable( addresses: Array, ): AddressLookupTableAccount { const U64_MAX = BigInt('0xffffffffffffffff'); return new AddressLookupTableAccount({ key: PublicKey.unique(), state: { lastExtendedSlot: 0, lastExtendedSlotStartIndex: 0, deactivationSlot: U64_MAX, authority: PublicKey.unique(), addresses, }, }); } describe('CompiledKeys', () => { it('compile', () => { const payer = PublicKey.unique(); const keys = createTestKeys(4); const programIds = createTestKeys(4); const compiledKeys = CompiledKeys.compile( [ new TransactionInstruction({ programId: programIds[0], keys: [ createAccountMeta(keys[0], false, false), createAccountMeta(keys[1], true, false), createAccountMeta(keys[2], false, true), createAccountMeta(keys[3], true, true), // duplicate the account metas createAccountMeta(keys[0], false, false), createAccountMeta(keys[1], true, false), createAccountMeta(keys[2], false, true), createAccountMeta(keys[3], true, true), // reference program ids createAccountMeta(programIds[0], false, false), createAccountMeta(programIds[1], true, false), createAccountMeta(programIds[2], false, true), createAccountMeta(programIds[3], true, true), ], }), new TransactionInstruction({programId: programIds[1], keys: []}), new TransactionInstruction({programId: programIds[2], keys: []}), new TransactionInstruction({programId: programIds[3], keys: []}), ], payer, ); const map = new Map(); setMapEntry(map, payer, true, true, false); setMapEntry(map, keys[0], false, false, false); setMapEntry(map, keys[1], true, false, false); setMapEntry(map, keys[2], false, true, false); setMapEntry(map, keys[3], true, true, false); setMapEntry(map, programIds[0], false, false, true); setMapEntry(map, programIds[1], true, false, true); setMapEntry(map, programIds[2], false, true, true); setMapEntry(map, programIds[3], true, true, true); expect(compiledKeys.keyMetaMap).to.eql(map); expect(compiledKeys.payer).to.eq(payer); }); it('compile with dup payer', () => { const [payer, programId] = createTestKeys(2); const compiledKeys = CompiledKeys.compile( [ new TransactionInstruction({ programId: programId, keys: [createAccountMeta(payer, false, false)], }), ], payer, ); const map = new Map(); setMapEntry(map, payer, true, true, false); setMapEntry(map, programId, false, false, true); expect(compiledKeys.keyMetaMap).to.eql(map); expect(compiledKeys.payer).to.eq(payer); }); it('compile with dup key', () => { const [payer, key, programId] = createTestKeys(3); const compiledKeys = CompiledKeys.compile( [ new TransactionInstruction({ programId: programId, keys: [ createAccountMeta(key, false, false), createAccountMeta(key, true, true), ], }), ], payer, ); const map = new Map(); setMapEntry(map, payer, true, true, false); setMapEntry(map, key, true, true, false); setMapEntry(map, programId, false, false, true); expect(compiledKeys.keyMetaMap).to.eql(map); expect(compiledKeys.payer).to.eq(payer); }); it('getMessageComponents', () => { const keys = createTestKeys(4); const payer = keys[0]; const map = new Map(); setMapEntry(map, payer, true, true, false); setMapEntry(map, keys[1], true, false, false); setMapEntry(map, keys[2], false, true, false); setMapEntry(map, keys[3], false, false, false); const compiledKeys = new CompiledKeys(payer, map); const [header, staticAccountKeys] = compiledKeys.getMessageComponents(); expect(staticAccountKeys).to.eql(keys); expect(header).to.eql({ numRequiredSignatures: 2, numReadonlySignedAccounts: 1, numReadonlyUnsignedAccounts: 1, }); }); it('getMessageComponents with overflow', () => { const keys = createTestKeys(257); const map = new Map(); for (const key of keys) { setMapEntry(map, key, true, true, false); } const compiledKeys = new CompiledKeys(keys[0], map); expect(() => compiledKeys.getMessageComponents()).to.throw( 'Max static account keys length exceeded', ); }); it('extractTableLookup', () => { const keys = createTestKeys(6); const map = new Map(); setMapEntry(map, keys[0], true, true, false); setMapEntry(map, keys[1], true, false, false); setMapEntry(map, keys[2], false, true, false); setMapEntry(map, keys[3], false, false, false); setMapEntry(map, keys[4], true, false, true); setMapEntry(map, keys[5], false, false, true); const lookupTable = createTestLookupTable([...keys, ...keys]); const compiledKeys = new CompiledKeys(keys[0], map); const extractResult = compiledKeys.extractTableLookup(lookupTable); if (extractResult === undefined) { expect(extractResult).to.not.be.undefined; return; } const [tableLookup, extractedAddresses] = extractResult; expect(tableLookup).to.eql({ accountKey: lookupTable.key, writableIndexes: [2], readonlyIndexes: [3], }); expect(extractedAddresses).to.eql({ writable: [keys[2]], readonly: [keys[3]], }); }); it('extractTableLookup no extractable keys found', () => { const keys = createTestKeys(6); const map = new Map(); setMapEntry(map, keys[0], true, true, false); setMapEntry(map, keys[1], true, false, false); setMapEntry(map, keys[2], true, true, true); setMapEntry(map, keys[3], true, false, true); setMapEntry(map, keys[4], false, true, true); setMapEntry(map, keys[5], false, false, true); const lookupTable = createTestLookupTable(keys); const compiledKeys = new CompiledKeys(keys[0], map); const extractResult = compiledKeys.extractTableLookup(lookupTable); expect(extractResult).to.be.undefined; }); it('extractTableLookup with empty lookup table', () => { const keys = createTestKeys(2); const map = new Map(); setMapEntry(map, keys[0], true, true, false); setMapEntry(map, keys[1], false, false, false); const lookupTable = createTestLookupTable([]); const compiledKeys = new CompiledKeys(keys[0], map); const extractResult = compiledKeys.extractTableLookup(lookupTable); expect(extractResult).to.be.undefined; }); it('extractTableLookup with invalid lookup table', () => { const keys = createTestKeys(257); const map = new Map(); setMapEntry(map, keys[0], true, true, false); setMapEntry(map, keys[256], false, false, false); const lookupTable = createTestLookupTable(keys); const compiledKeys = new CompiledKeys(keys[0], map); expect(() => compiledKeys.extractTableLookup(lookupTable)).to.throw( 'Max lookup table index exceeded', ); }); }); function setMapEntry( map: Map, pubkey: PublicKey, isSigner: boolean, isWritable: boolean, isInvoked: boolean, ) { map.set(pubkey.toBase58(), { isSigner, isWritable, isInvoked, }); } function createAccountMeta( pubkey: PublicKey, isSigner: boolean, isWritable: boolean, ): AccountMeta { return { pubkey, isSigner, isWritable, }; }