feat(solana_utils): support account lookup table (#1424)

* feat: support account lookup table

* remove console log

* Support ALT

* Commas

* feat: support lta

* Go

* Bump
This commit is contained in:
guibescos 2024-04-09 13:51:29 +01:00 committed by GitHub
parent bb830e1760
commit 62d189e3b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 90 additions and 22 deletions

2
package-lock.json generated
View File

@ -59582,7 +59582,7 @@
},
"target_chains/solana/sdk/js/solana_utils": {
"name": "@pythnetwork/solana-utils",
"version": "0.2.0",
"version": "0.3.0",
"license": "Apache-2.0",
"dependencies": {
"@coral-xyz/anchor": "^0.29.0",

View File

@ -1,6 +1,6 @@
{
"name": "@pythnetwork/solana-utils",
"version": "0.2.0",
"version": "0.3.0",
"description": "Utility functions for Solana",
"homepage": "https://pyth.network",
"main": "lib/index.js",

View File

@ -1,4 +1,5 @@
import {
AddressLookupTableAccount,
ComputeBudgetProgram,
Keypair,
PublicKey,
@ -81,4 +82,33 @@ it("Unit test for getSizeOfTransaction", async () => {
expect(versionedTransaction.serialize().length).toBe(
getSizeOfTransaction(ixsToSend)
);
const addressLookupTable: AddressLookupTableAccount =
new AddressLookupTableAccount({
key: PublicKey.unique(),
state: {
lastExtendedSlot: 0,
lastExtendedSlotStartIndex: 0,
deactivationSlot: BigInt(0),
addresses: [
SystemProgram.programId,
ComputeBudgetProgram.programId,
...ixsToSend[0].keys.map((key) => key.pubkey),
...ixsToSend[1].keys.map((key) => key.pubkey),
...ixsToSend[2].keys.map((key) => key.pubkey),
],
},
});
const versionedTransactionWithAlt = new VersionedTransaction(
new TransactionMessage({
recentBlockhash: transaction.recentBlockhash,
payerKey: payer.publicKey,
instructions: ixsToSend,
}).compileToV0Message([addressLookupTable])
);
expect(versionedTransactionWithAlt.serialize().length).toBe(
getSizeOfTransaction(ixsToSend, true, addressLookupTable)
);
});

View File

@ -1,5 +1,6 @@
import { Wallet } from "@coral-xyz/anchor";
import {
AddressLookupTableAccount,
ComputeBudgetProgram,
Connection,
PACKET_DATA_SIZE,
@ -63,7 +64,7 @@ export const DEFAULT_PRIORITY_FEE_CONFIG: PriorityFeeConfig = {
* - A compact array of instructions
*
* If the transaction is a `VersionedTransaction`, it also contains an extra byte at the beginning, indicating the version and an array of `MessageAddressTableLookup` at the end.
* We don't support Account Lookup Tables, so that array has a size of 0.
* After this field there is an array of indexes into the address lookup table that represents the accounts from the address lookup table used in the transaction.
*
* Each instruction has the following layout :
* - One byte indicating the index of the program in the account addresses array
@ -72,19 +73,22 @@ export const DEFAULT_PRIORITY_FEE_CONFIG: PriorityFeeConfig = {
*/
export function getSizeOfTransaction(
instructions: TransactionInstruction[],
versionedTransaction = true
versionedTransaction = true,
addressLookupTable?: AddressLookupTableAccount
): number {
const programs = new Set<string>();
const signers = new Set<string>();
const accounts = new Set<string>();
let accounts = new Set<string>();
instructions.map((ix) => {
accounts.add(ix.programId.toBase58()),
ix.keys.map((key) => {
if (key.isSigner) {
signers.add(key.pubkey.toBase58());
}
accounts.add(key.pubkey.toBase58());
});
programs.add(ix.programId.toBase58());
accounts.add(ix.programId.toBase58());
ix.keys.map((key) => {
if (key.isSigner) {
signers.add(key.pubkey.toBase58());
}
accounts.add(key.pubkey.toBase58());
});
});
const instruction_sizes: number = instructions
@ -98,6 +102,19 @@ export function getSizeOfTransaction(
)
.reduce((a, b) => a + b, 0);
let numberOfAddressLookups = 0;
if (addressLookupTable) {
const lookupTableAddresses = addressLookupTable.state.addresses.map(
(address) => address.toBase58()
);
const totalNumberOfAccounts = accounts.size;
accounts = new Set(
[...accounts].filter((account) => !lookupTableAddresses.includes(account))
);
accounts = new Set([...accounts, ...programs, ...signers]);
numberOfAddressLookups = totalNumberOfAccounts - accounts.size; // This number is equal to the number of accounts that are in the lookup table and are neither signers nor programs
}
return (
getSizeOfCompressedU16(signers.size) +
signers.size * 64 + // array of signatures
@ -107,7 +124,10 @@ export function getSizeOfTransaction(
32 + // recent blockhash
getSizeOfCompressedU16(instructions.length) +
instruction_sizes + // array of instructions
(versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) // we don't support Account Lookup Tables
(versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) + // transaction version and number of address lookup tables
(versionedTransaction && addressLookupTable ? 32 : 0) + // address lookup table address (we only support 1 address lookup table)
(versionedTransaction && addressLookupTable ? 2 : 0) + // number of address lookup indexes
numberOfAddressLookups // address lookup indexes
);
}
@ -130,11 +150,17 @@ export class TransactionBuilder {
}[] = [];
readonly payer: PublicKey;
readonly connection: Connection;
readonly addressLookupTable: AddressLookupTableAccount | undefined;
/** Make a new `TransactionBuilder`. It requires a `payer` to populate the `payerKey` field and a connection to populate `recentBlockhash` in the versioned transactions. */
constructor(payer: PublicKey, connection: Connection) {
constructor(
payer: PublicKey,
connection: Connection,
accountLookupTable?: AddressLookupTableAccount
) {
this.payer = payer;
this.connection = connection;
this.addressLookupTable = accountLookupTable;
}
/**
@ -149,11 +175,16 @@ export class TransactionBuilder {
computeUnits: computeUnits ?? 0,
});
} else if (
getSizeOfTransaction([
...this.transactionInstructions[this.transactionInstructions.length - 1]
.instructions,
instruction,
]) <= PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET
getSizeOfTransaction(
[
...this.transactionInstructions[
this.transactionInstructions.length - 1
].instructions,
instruction,
],
true,
this.addressLookupTable
) <= PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET
) {
this.transactionInstructions[
this.transactionInstructions.length - 1
@ -218,7 +249,9 @@ export class TransactionBuilder {
recentBlockhash: blockhash,
instructions: instructionsWithComputeBudget,
payerKey: this.payer,
}).compileToV0Message()
}).compileToV0Message(
this.addressLookupTable ? [this.addressLookupTable] : []
)
),
signers: signers,
};
@ -289,9 +322,14 @@ export class TransactionBuilder {
payer: PublicKey,
connection: Connection,
instructions: InstructionWithEphemeralSigners[],
priorityFeeConfig: PriorityFeeConfig
priorityFeeConfig: PriorityFeeConfig,
addressLookupTable?: AddressLookupTableAccount
): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> {
const transactionBuilder = new TransactionBuilder(payer, connection);
const transactionBuilder = new TransactionBuilder(
payer,
connection,
addressLookupTable
);
transactionBuilder.addInstructions(instructions);
return transactionBuilder.buildVersionedTransactions(priorityFeeConfig);
}