Skip to content

Commit

Permalink
Estimate compute budget by simulating transaction (orca-so#79)
Browse files Browse the repository at this point in the history
* Estimate compute budget by simulating transaction

* Tweaks

* Tweaks
  • Loading branch information
wjthieme authored Apr 2, 2024
1 parent 3f0d1fd commit 12e3491
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/common-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@orca-so/common-sdk",
"version": "0.5.4",
"version": "0.5.5",
"description": "Common Typescript components across Orca",
"repository": "https://github.com/orca-so/orca-sdks",
"author": "Orca Foundation",
Expand Down
41 changes: 36 additions & 5 deletions packages/common-sdk/src/web3/transactions/compute-budget.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
import { Connection, PublicKey, RecentPrioritizationFees } from "@solana/web3.js";
import { AddressLookupTableAccount, Connection, PublicKey, RecentPrioritizationFees, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
import { Instruction } from "./types";

export const MICROLAMPORTS_PER_LAMPORT = 1_000_000;
export const DEFAULT_PRIORITY_FEE_PERCENTILE = 0.9;
export const DEFAULT_MAX_PRIORITY_FEE_LAMPORTS = 1000000; // 0.001 SOL
export const DEFAULT_MAX_COMPUTE_UNIT_LIMIT = 1_400_000;

export async function estimateComputeBudgetLimit(
connection: Connection,
instructions: Instruction[],
lookupTableAccounts: AddressLookupTableAccount[] | undefined,
payer: PublicKey,
margin: number,
): Promise<number> {
try {
const txMainInstructions = instructions.flatMap((instruction) => instruction.instructions);
const txCleanupInstruction = instructions.flatMap((instruction) => instruction.cleanupInstructions);
const txMessage = new TransactionMessage({
recentBlockhash: PublicKey.default.toBase58(),
payerKey: payer,
instructions: [...txMainInstructions, ...txCleanupInstruction],
}).compileToV0Message(lookupTableAccounts);

const tx = new VersionedTransaction(txMessage);

const simulation = await connection.simulateTransaction(tx, { sigVerify: false, replaceRecentBlockhash: true });
if (!simulation.value.unitsConsumed) {
return DEFAULT_MAX_COMPUTE_UNIT_LIMIT
}
const marginUnits = Math.max(100_000, margin * simulation.value.unitsConsumed);
const estimatedUnits = Math.ceil(simulation.value.unitsConsumed + marginUnits);
return Math.min(DEFAULT_MAX_COMPUTE_UNIT_LIMIT, estimatedUnits);
} catch {
return DEFAULT_MAX_COMPUTE_UNIT_LIMIT;
}
}

export async function getPriorityFeeInLamports(
connection: Connection,
Expand All @@ -30,9 +61,9 @@ function getPriorityFeeSuggestion(recentPriorityFees: RecentPrioritizationFees[]
}

function getLockWritableAccounts(instructions: Instruction[]): PublicKey[] {
const accountKeys = instructions
return instructions
.flatMap((instruction) => [...instruction.instructions, ...instruction.cleanupInstructions])
.flatMap((instruction) => instruction.keys);
const writableAccounts = accountKeys.filter((key) => key.isWritable).map((key) => key.pubkey);
return Array.from(new Set(writableAccounts));
.flatMap((instruction) => instruction.keys)
.filter((key) => key.isWritable)
.map((key) => key.pubkey);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import {
VersionedTransaction,
} from "@solana/web3.js";
import { Wallet } from "../wallet";
import { DEFAULT_MAX_PRIORITY_FEE_LAMPORTS, DEFAULT_PRIORITY_FEE_PERCENTILE, MICROLAMPORTS_PER_LAMPORT, getPriorityFeeInLamports } from "./compute-budget";
import { DEFAULT_MAX_COMPUTE_UNIT_LIMIT, DEFAULT_MAX_PRIORITY_FEE_LAMPORTS, DEFAULT_PRIORITY_FEE_PERCENTILE, MICROLAMPORTS_PER_LAMPORT, estimateComputeBudgetLimit, getPriorityFeeInLamports } from "./compute-budget";
import { MEASUREMENT_BLOCKHASH } from "./constants";
import { Instruction, TransactionPayload } from "./types";

const DEFAULT_MAX_COMPUTE_UNIT_LIMIT = 1_400_000;

/**
Build options when building a transaction using TransactionBuilder
@param latestBlockhash
Expand Down Expand Up @@ -65,8 +63,8 @@ type ComputeBudgetOption = {
} | {
type: "auto";
maxPriorityFeeLamports?: number;
computeBudgetLimit?: number;
percentile?: number;
computeLimitMargin?: number;
computePricePercentile?: number;
};

type SyncBuildOptions = BuildOptions & Required<BaseBuildOption>;
Expand Down Expand Up @@ -102,7 +100,7 @@ export const defaultTransactionBuilderOptions: TransactionBuilderOptions = {
export class TransactionBuilder {
private instructions: Instruction[];
private signers: Signer[];
private opts: TransactionBuilderOptions;
readonly opts: TransactionBuilderOptions;

constructor(
readonly connection: Connection,
Expand Down Expand Up @@ -252,7 +250,7 @@ export class TransactionBuilder {
// This should only be happening for calucling the tx size so it should be fine.
prependInstructions = [
ComputeBudgetProgram.setComputeUnitLimit({
units: computeBudgetOption.computeBudgetLimit ?? DEFAULT_MAX_COMPUTE_UNIT_LIMIT,
units: DEFAULT_MAX_COMPUTE_UNIT_LIMIT,
}),
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 0,
Expand Down Expand Up @@ -314,8 +312,10 @@ export class TransactionBuilder {
}
let finalComputeBudgetOption = computeBudgetOption ?? { type: "none" };
if (finalComputeBudgetOption.type === "auto") {
const computeBudgetLimit = finalComputeBudgetOption.computeBudgetLimit ?? DEFAULT_MAX_COMPUTE_UNIT_LIMIT;
const percentile = finalComputeBudgetOption.percentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE;
const margin = finalComputeBudgetOption.computeLimitMargin ?? 0.1;
const lookupTableAccounts = finalOptions.maxSupportedTransactionVersion === "legacy" ? undefined : finalOptions.lookupTableAccounts;
const computeBudgetLimit = await estimateComputeBudgetLimit(this.connection, this.instructions, lookupTableAccounts, this.wallet.publicKey, margin);
const percentile = finalComputeBudgetOption.computePricePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE;
const priorityFee = await getPriorityFeeInLamports(this.connection, computeBudgetLimit, this.instructions, percentile);
const maxPriorityFeeLamports = finalComputeBudgetOption.maxPriorityFeeLamports ?? DEFAULT_MAX_PRIORITY_FEE_LAMPORTS;
const priorityFeeLamports = Math.min(priorityFee, maxPriorityFeeLamports);
Expand Down

0 comments on commit 12e3491

Please sign in to comment.