[xc-admin] Batch instructions (#612)
* Checkpoint * Working * Remove console log * Restore send all * Fix tests
This commit is contained in:
parent
8e11caa1ee
commit
58db641ddd
|
@ -0,0 +1,138 @@
|
|||
import { AnchorProvider, Wallet } from "@project-serum/anchor";
|
||||
import { pythOracleProgram } from "@pythnetwork/client";
|
||||
import {
|
||||
getPythClusterApiUrl,
|
||||
getPythProgramKeyForCluster,
|
||||
PythCluster,
|
||||
} from "@pythnetwork/client/lib/cluster";
|
||||
import {
|
||||
Connection,
|
||||
Keypair,
|
||||
PACKET_DATA_SIZE,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
batchIntoTransactions,
|
||||
getSizeOfCompressedU16,
|
||||
getSizeOfTransaction,
|
||||
MultisigInstructionProgram,
|
||||
MultisigParser,
|
||||
} from "..";
|
||||
import { PythMultisigInstruction } from "../multisig_transaction/PythMultisigInstruction";
|
||||
|
||||
it("Unit test compressed u16 size", async () => {
|
||||
expect(getSizeOfCompressedU16(127)).toBe(1);
|
||||
expect(getSizeOfCompressedU16(128)).toBe(2);
|
||||
expect(getSizeOfCompressedU16(16383)).toBe(2);
|
||||
expect(getSizeOfCompressedU16(16384)).toBe(3);
|
||||
});
|
||||
|
||||
it("Unit test for getSizeOfTransaction", async () => {
|
||||
jest.setTimeout(60000);
|
||||
|
||||
const cluster: PythCluster = "devnet";
|
||||
const pythProgram = pythOracleProgram(
|
||||
getPythProgramKeyForCluster(cluster),
|
||||
new AnchorProvider(
|
||||
new Connection(getPythClusterApiUrl(cluster)),
|
||||
new Wallet(new Keypair()),
|
||||
AnchorProvider.defaultOptions()
|
||||
)
|
||||
);
|
||||
|
||||
const payer = new Keypair();
|
||||
const productAccount = PublicKey.unique();
|
||||
|
||||
const ixsToSend: TransactionInstruction[] = [];
|
||||
|
||||
ixsToSend.push(
|
||||
await pythProgram.methods
|
||||
.addProduct({
|
||||
asset_type: "Crypto",
|
||||
base: "ETH",
|
||||
description: "ETH/USD",
|
||||
quote_currency: "USD",
|
||||
symbol: "Crypto.ETH/USD",
|
||||
generic_symbol: "ETHUSD",
|
||||
})
|
||||
.accounts({
|
||||
fundingAccount: payer.publicKey,
|
||||
productAccount,
|
||||
tailMappingAccount: PublicKey.unique(),
|
||||
})
|
||||
.instruction()
|
||||
);
|
||||
|
||||
ixsToSend.push(
|
||||
await pythProgram.methods
|
||||
.addPrice(-8, 1)
|
||||
.accounts({
|
||||
fundingAccount: payer.publicKey,
|
||||
productAccount,
|
||||
priceAccount: PublicKey.unique(),
|
||||
})
|
||||
.instruction()
|
||||
);
|
||||
|
||||
const transaction = new Transaction();
|
||||
for (let ix of ixsToSend) {
|
||||
transaction.add(ix);
|
||||
}
|
||||
|
||||
transaction.recentBlockhash = "GqdFtdM7zzWw33YyHtBNwPhyBsdYKcfm9gT47bWnbHvs"; // Mock blockhash from devnet
|
||||
transaction.feePayer = payer.publicKey;
|
||||
expect(transaction.serialize({ requireAllSignatures: false }).length).toBe(
|
||||
getSizeOfTransaction(ixsToSend)
|
||||
);
|
||||
});
|
||||
|
||||
it("Unit test for getSizeOfTransaction", async () => {
|
||||
jest.setTimeout(60000);
|
||||
|
||||
const cluster: PythCluster = "devnet";
|
||||
const pythProgram = pythOracleProgram(
|
||||
getPythProgramKeyForCluster(cluster),
|
||||
new AnchorProvider(
|
||||
new Connection(getPythClusterApiUrl(cluster)),
|
||||
new Wallet(new Keypair()),
|
||||
AnchorProvider.defaultOptions()
|
||||
)
|
||||
);
|
||||
const ixsToSend: TransactionInstruction[] = [];
|
||||
const payer = new Keypair();
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
ixsToSend.push(
|
||||
await pythProgram.methods
|
||||
.addPublisher(PublicKey.unique())
|
||||
.accounts({
|
||||
fundingAccount: payer.publicKey,
|
||||
priceAccount: PublicKey.unique(),
|
||||
})
|
||||
.instruction()
|
||||
);
|
||||
}
|
||||
|
||||
const txToSend: Transaction[] = batchIntoTransactions(
|
||||
ixsToSend,
|
||||
payer.publicKey
|
||||
);
|
||||
expect(
|
||||
txToSend.map((tx) => tx.instructions.length).reduce((a, b) => a + b)
|
||||
).toBe(ixsToSend.length);
|
||||
expect(
|
||||
txToSend.every(
|
||||
(tx) => getSizeOfTransaction(tx.instructions) <= PACKET_DATA_SIZE
|
||||
)
|
||||
).toBeTruthy();
|
||||
|
||||
for (let tx of txToSend) {
|
||||
tx.recentBlockhash = "GqdFtdM7zzWw33YyHtBNwPhyBsdYKcfm9gT47bWnbHvs"; // Mock blockhash from devnet
|
||||
tx.feePayer = payer.publicKey;
|
||||
expect(tx.serialize({ requireAllSignatures: false }).length).toBe(
|
||||
getSizeOfTransaction(tx.instructions)
|
||||
);
|
||||
}
|
||||
});
|
|
@ -6,6 +6,7 @@ import {
|
|||
SYSVAR_RENT_PUBKEY,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
SystemProgram,
|
||||
PACKET_DATA_SIZE,
|
||||
} from "@solana/web3.js";
|
||||
import { BN } from "bn.js";
|
||||
import { AnchorProvider } from "@project-serum/anchor";
|
||||
|
@ -41,8 +42,8 @@ export async function proposeInstructions(
|
|||
wormholeAddress?: PublicKey
|
||||
): Promise<PublicKey> {
|
||||
const msAccount = await squad.getMultisig(vault);
|
||||
let txToSend: Transaction[] = [];
|
||||
const createProposal = new Transaction().add(
|
||||
let ixToSend: TransactionInstruction[] = [];
|
||||
const createProposal = ixToSend.push(
|
||||
await squad.buildCreateTransaction(
|
||||
msAccount.publicKey,
|
||||
msAccount.authorityIndex,
|
||||
|
@ -54,7 +55,6 @@ export async function proposeInstructions(
|
|||
new BN(msAccount.transactionIndex + 1),
|
||||
squad.multisigProgramId
|
||||
)[0];
|
||||
txToSend.push(createProposal);
|
||||
|
||||
if (remote) {
|
||||
if (!wormholeAddress) {
|
||||
|
@ -69,8 +69,7 @@ export async function proposeInstructions(
|
|||
i + 1,
|
||||
wormholeAddress
|
||||
);
|
||||
txToSend.push(
|
||||
new Transaction().add(
|
||||
ixToSend.push(
|
||||
await squad.buildAddInstruction(
|
||||
vault,
|
||||
newProposalAddress,
|
||||
|
@ -80,36 +79,28 @@ export async function proposeInstructions(
|
|||
squadIx.authorityBump,
|
||||
squadIx.authorityType
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
txToSend.push(
|
||||
new Transaction().add(
|
||||
ixToSend.push(
|
||||
await squad.buildAddInstruction(
|
||||
vault,
|
||||
newProposalAddress,
|
||||
instructions[i],
|
||||
i + 1
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
txToSend.push(
|
||||
new Transaction().add(
|
||||
ixToSend.push(
|
||||
await squad.buildActivateTransaction(vault, newProposalAddress)
|
||||
)
|
||||
);
|
||||
|
||||
txToSend.push(
|
||||
new Transaction().add(
|
||||
await squad.buildApproveTransaction(vault, newProposalAddress)
|
||||
)
|
||||
);
|
||||
ixToSend.push(await squad.buildApproveTransaction(vault, newProposalAddress));
|
||||
|
||||
const txToSend = batchIntoTransactions(ixToSend, squad.wallet.publicKey);
|
||||
await new AnchorProvider(
|
||||
squad.connection,
|
||||
squad.wallet,
|
||||
|
@ -122,6 +113,82 @@ export async function proposeInstructions(
|
|||
return newProposalAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch instructions into transactions
|
||||
*/
|
||||
export function batchIntoTransactions(
|
||||
instructions: TransactionInstruction[],
|
||||
feePayer: PublicKey
|
||||
): Transaction[] {
|
||||
let i = 0;
|
||||
const txToSend: Transaction[] = [];
|
||||
while (i < instructions.length) {
|
||||
let j = i + 2;
|
||||
while (
|
||||
j < instructions.length &&
|
||||
getSizeOfTransaction(instructions.slice(i, j)) <= PACKET_DATA_SIZE
|
||||
) {
|
||||
j += 1;
|
||||
}
|
||||
const tx = new Transaction();
|
||||
tx.feePayer = feePayer;
|
||||
for (let k = i; k < j - 1; k += 1) {
|
||||
tx.add(instructions[k]);
|
||||
}
|
||||
i = j - 1;
|
||||
txToSend.push(tx);
|
||||
}
|
||||
return txToSend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a transaction that would contain the provided array of instructions
|
||||
*/
|
||||
export function getSizeOfTransaction(
|
||||
instructions: TransactionInstruction[]
|
||||
): number {
|
||||
const signers = new Set<string>();
|
||||
const 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());
|
||||
});
|
||||
});
|
||||
|
||||
const instruction_sizes: number = instructions
|
||||
.map(
|
||||
(ix) =>
|
||||
1 +
|
||||
getSizeOfCompressedU16(ix.keys.length) +
|
||||
ix.keys.length +
|
||||
getSizeOfCompressedU16(ix.data.length) +
|
||||
ix.data.length
|
||||
)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
return (
|
||||
1 +
|
||||
signers.size * 64 +
|
||||
3 +
|
||||
getSizeOfCompressedU16(accounts.size) +
|
||||
32 * accounts.size +
|
||||
32 +
|
||||
getSizeOfCompressedU16(instructions.length) +
|
||||
instruction_sizes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of n in bytes when serialized as a CompressedU16
|
||||
*/
|
||||
export function getSizeOfCompressedU16(n: number) {
|
||||
return 1 + Number(n >= 128) + Number(n >= 16384);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap `instruction` in a Wormhole message for remote execution
|
||||
* @param squad Squads client
|
||||
|
|
|
@ -85694,8 +85694,8 @@
|
|||
"@coral-xyz/anchor": "^0.26.0",
|
||||
"@headlessui/react": "^1.7.7",
|
||||
"@pythnetwork/client": "^2.15.0",
|
||||
"@solana/spl-token": "*",
|
||||
"@radix-ui/react-tooltip": "*",
|
||||
"@radix-ui/react-tooltip": "^1.0.3",
|
||||
"@solana/spl-token": "^0.3.7",
|
||||
"@solana/wallet-adapter-base": "^0.9.20",
|
||||
"@solana/wallet-adapter-react": "^0.15.28",
|
||||
"@solana/wallet-adapter-react-ui": "^0.9.27",
|
||||
|
|
Loading…
Reference in New Issue