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

feat: add account lookup tables #420

Merged
merged 11 commits into from
Mar 18, 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
6 changes: 5 additions & 1 deletion frontend/hooks/useStakeConnection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Wallet } from '@coral-xyz/anchor'
import { STAKING_ADDRESS, StakeConnection } from '@pythnetwork/staking'
import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react'
import { PublicKey } from '@solana/web3.js'
import toast from 'react-hot-toast'
import { useQuery } from 'react-query'
import { capitalizeFirstLetter } from 'utils/capitalizeFirstLetter'
Expand All @@ -17,7 +18,10 @@ export function useStakeConnection() {
connection,
// anchor wallet is defined, as we have used enabled below
anchorWallet as Wallet,
STAKING_ADDRESS
STAKING_ADDRESS,
process.env.ADDRESS_LOOKUP_TABLE
? new PublicKey(process.env.ADDRESS_LOOKUP_TABLE)
: undefined
)
},
{
Expand Down
1 change: 1 addition & 0 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
env: {
ENDPOINT: process.env.ENDPOINT,
CLUSTER: process.env.CLUSTER,
ADDRESS_LOOKUP_TABLE: process.env.ADDRESS_LOOKUP_TABLE,
},
webpack(config, { isServer }) {
config.experiments = { asyncWebAssembly: true, layers: true }
Expand Down
68 changes: 54 additions & 14 deletions staking/app/StakeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
SYSVAR_CLOCK_PUBKEY,
ComputeBudgetProgram,
SystemProgram,
TransactionMessage,
VersionedTransaction,
AddressLookupTableAccount,
} from "@solana/web3.js";
import * as wasm2 from "@pythnetwork/staking-wasm";
import {
Expand Down Expand Up @@ -76,14 +79,16 @@ export class StakeConnection {
votingProduct = { voting: {} };
votingAccountMetadataWasm: any;
governanceAddress: PublicKey;
addressLookupTable: PublicKey | undefined;

private constructor(
program: Program<Staking>,
provider: AnchorProvider,
config: GlobalConfig,
configAddress: PublicKey,
votingProductMetadataAccount: PublicKey,
votingAccountMetadataWasm: any
votingAccountMetadataWasm: any,
addressLookupTable: PublicKey | undefined
) {
this.program = program;
this.provider = provider;
Expand All @@ -92,6 +97,7 @@ export class StakeConnection {
this.votingProductMetadataAccount = votingProductMetadataAccount;
this.governanceAddress = GOVERNANCE_ADDRESS();
this.votingAccountMetadataWasm = votingAccountMetadataWasm;
this.addressLookupTable = addressLookupTable;
}

public static async connect(
Expand All @@ -110,7 +116,8 @@ export class StakeConnection {
public static async createStakeConnection(
connection: Connection,
wallet: Wallet,
stakingProgramAddress: PublicKey
stakingProgramAddress: PublicKey,
addressLookupTable?: PublicKey
): Promise<StakeConnection> {
const provider = new AnchorProvider(connection, wallet, {});
const program = new Program(
Expand Down Expand Up @@ -157,10 +164,39 @@ export class StakeConnection {
config,
configAddress,
votingProductMetadataAccount,
votingAccountMetadataWasm
votingAccountMetadataWasm,
addressLookupTable
);
}

private async sendAndConfirmAsVersionedTransaction(
instructions: TransactionInstruction[],
signers: Signer[]
) {
const addressLookupTables: AddressLookupTableAccount[] | undefined = this
.addressLookupTable
? [
(
await this.program.provider.connection.getAddressLookupTable(
new PublicKey(this.addressLookupTable)
)
).value,
]
: [];
const message = new TransactionMessage({
instructions: instructions,
recentBlockhash: (await this.provider.connection.getLatestBlockhash())
.blockhash,
payerKey: this.provider.wallet.publicKey,
});
const versionedTransaction = new VersionedTransaction(
message.compileToV0Message(addressLookupTables)
);
return await this.provider.sendAndConfirm(versionedTransaction, signers, {
skipPreflight: true,
});
}

/** The public key of the user of the staking program. This connection sends transactions as this user. */
public userPublicKey(): PublicKey {
return this.provider.wallet.publicKey;
Expand Down Expand Up @@ -634,9 +670,11 @@ export class StakeConnection {
.instruction()
);

await this.provider.sendAndConfirm(transaction);
await this.sendAndConfirmAsVersionedTransaction(
transaction.instructions,
[]
);
}

public async setupVestingAccount(
amount: PythBalance,
owner: PublicKey,
Expand Down Expand Up @@ -816,15 +854,17 @@ export class StakeConnection {
// Each of these instructions is 27 bytes (<< 1232) so we don't cap how many of them we fit in the transaction
ixs.push(...(await this.buildCleanupUnlockedPositions(stakeAccount))); // Try to make room by closing unlocked positions
}
await this.program.methods
.createPosition(this.votingProduct, amount.toBN())
.preInstructions(ixs)
.accounts({
stakeAccountPositions: stakeAccountAddress,
targetAccount: this.votingProductMetadataAccount,
})
.signers(signers)
.rpc({ skipPreflight: true });
ixs.push(
await this.program.methods
.createPosition(this.votingProduct, amount.toBN())
.accounts({
stakeAccountPositions: stakeAccountAddress,
targetAccount: this.votingProductMetadataAccount,
})
.instruction()
);

await this.sendAndConfirmAsVersionedTransaction(ixs, signers);
}

public async buildCleanupUnlockedPositions(
Expand Down
16 changes: 16 additions & 0 deletions staking/app/deploy/create_account_lookup_table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Wallet, AnchorProvider } from "@coral-xyz/anchor";
import { Connection } from "@solana/web3.js";
import { AUTHORITY_KEYPAIR, PYTH_TOKEN, RPC_NODE } from "./devnet";
import { initAddressLookupTable } from "../../tests/utils/utils";
async function main() {
const client = new Connection(RPC_NODE);
const provider = new AnchorProvider(
client,
new Wallet(AUTHORITY_KEYPAIR),
{}
);
const lookupTableAddress = await initAddressLookupTable(provider, PYTH_TOKEN);
console.log("Lookup table address: ", lookupTableAddress.toBase58());
}

main();
12 changes: 11 additions & 1 deletion staking/tests/utils/before.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ import path from "path";
import os from "os";
import { StakeConnection, PythBalance, PYTH_DECIMALS } from "../../app";
import { GlobalConfig } from "../../app/StakeConnection";
import { createMint, getTargetAccount as getTargetAccount } from "./utils";
import {
createMint,
getTargetAccount as getTargetAccount,
initAddressLookupTable,
} from "./utils";
import { loadKeypair } from "./keys";

export const ANCHOR_CONFIG_PATH = "./Anchor.toml";
Expand Down Expand Up @@ -503,6 +507,12 @@ export async function standardSetup(

await initGovernanceProduct(program, user);

const lookupTableAddress = await initAddressLookupTable(
provider,
pythMintAccount.publicKey
);
console.log("Lookup table address: ", lookupTableAddress.toBase58());

// Give the power back to the people
await program.methods
.updateGovernanceAuthority(globalConfig.governanceAuthority)
Expand Down
61 changes: 59 additions & 2 deletions staking/tests/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import { Token, MintLayout } from "@solana/spl-token";
import { Token, MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import {
PublicKey,
Keypair,
Transaction,
SystemProgram,
AddressLookupTableProgram,
ComputeBudgetProgram,
SYSVAR_RENT_PUBKEY,
VersionedTransaction,
TransactionMessage,
} from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";
import { AnchorError, ProgramError } from "@coral-xyz/anchor";
import { AnchorError } from "@coral-xyz/anchor";
import assert from "assert";
import * as wasm from "@pythnetwork/staking-wasm";
import { Staking } from "../../target/types/staking";
import { GOVERNANCE_ADDRESS, REALM_ID, STAKING_ADDRESS } from "../../app";

type StakeTarget = anchor.IdlTypes<Staking>["Target"];

export function getConfigAccount(programId: PublicKey): PublicKey {
return PublicKey.findProgramAddressSync(
[anchor.utils.bytes.utf8.encode(wasm.Constants.CONFIG_SEED())],
programId
)[0];
}

export async function getTargetAccount(
stakeTarget: StakeTarget,
programId: PublicKey
Expand Down Expand Up @@ -76,6 +89,50 @@ export async function createMint(
});
}

export async function initAddressLookupTable(
provider: anchor.AnchorProvider,
mint: PublicKey
) {
const configAccount = getConfigAccount(STAKING_ADDRESS);
const targetAccount = await getTargetAccount({ voting: {} }, STAKING_ADDRESS);

const [loookupTableInstruction, lookupTableAddress] =
AddressLookupTableProgram.createLookupTable({
authority: provider.publicKey,
payer: provider.publicKey,
recentSlot: await provider.connection.getSlot(),
});
const extendInstruction = AddressLookupTableProgram.extendLookupTable({
payer: provider.publicKey,
authority: provider.publicKey,
lookupTable: lookupTableAddress,
addresses: [
ComputeBudgetProgram.programId,
SystemProgram.programId,
STAKING_ADDRESS,
REALM_ID,
mint,
configAccount,
SYSVAR_RENT_PUBKEY,
TOKEN_PROGRAM_ID,
GOVERNANCE_ADDRESS(),
targetAccount,
],
});
const createLookupTableTx = new VersionedTransaction(
new TransactionMessage({
instructions: [loookupTableInstruction, extendInstruction],
payerKey: provider.publicKey,
recentBlockhash: (await provider.connection.getLatestBlockhash())
.blockhash,
}).compileToV0Message()
);
await provider.sendAndConfirm(createLookupTableTx, [], {
skipPreflight: true,
});
return lookupTableAddress;
}

/**
* Sends the rpc call and check whether the error message matches the provided string
* @param rpcCall : anchor rpc call
Expand Down
Loading