Skip to content

Commit

Permalink
move compute budget logic into util
Browse files Browse the repository at this point in the history
  • Loading branch information
artursapek committed Mar 18, 2024
1 parent 8007fbe commit 2bac879
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 101 deletions.
138 changes: 44 additions & 94 deletions sdk/src/contexts/solana/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
import {
clusterApiUrl,
Commitment,
ComputeBudgetProgram,
Connection,
Keypair,
PublicKey,
Expand Down Expand Up @@ -66,6 +65,7 @@ import {
getClaim,
getPostedMessage,
} from './utils/wormhole';
import { addComputeBudget } from './utils/computeBudget';
import { ForeignAssetCache } from '../../utils';
import { RelayerAbstract } from '../abstracts/relayer';
import {
Expand All @@ -78,9 +78,6 @@ import {
const SOLANA_SEQ_LOG = 'Program log: Sequence: ';
const SOLANA_CHAIN_NAME = MAINNET_CONFIG.chains.solana!.key;

// Add priority fee according to 75th percentile of recent fees paid
const SOLANA_FEE_PERCENTILE = 0.5;

const SOLANA_MAINNET_EMMITER_ID =
'ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5';
const SOLANA_TESTNET_EMITTER_ID =
Expand Down Expand Up @@ -277,18 +274,15 @@ export class SolanaContext<
payerPublicKey,
tokenPublicKey,
);
const transaction = new Transaction();
transaction.add(
...(await this.determineComputeBudget([
tokenPublicKey,
associatedPublicKey,
])),
const transaction = new Transaction(
await this.connection?.getLatestBlockhash(commitment),
);
transaction.add(createAccountInst);

const { blockhash } = await this.connection.getLatestBlockhash(commitment);
transaction.recentBlockhash = blockhash;
transaction.feePayer = payerPublicKey;
await addComputeBudget(this.connection!, transaction, [
tokenPublicKey,
associatedPublicKey,
]);
return transaction;
}

Expand Down Expand Up @@ -397,12 +391,9 @@ export class SolanaContext<
payerPublicKey, //authority
);

const { blockhash } = await this.connection.getLatestBlockhash(commitment);
const transaction = new Transaction();
transaction.add(...(await this.determineComputeBudget([NATIVE_MINT])));

transaction.recentBlockhash = blockhash;
transaction.feePayer = payerPublicKey;
const transaction = new Transaction(
await this.connection?.getLatestBlockhash(commitment),
);
transaction.add(
createAncillaryAccountIx,
initialBalanceTransferIx,
Expand All @@ -411,6 +402,9 @@ export class SolanaContext<
tokenBridgeTransferIx,
closeAccountIx,
);

transaction.feePayer = payerPublicKey;
await addComputeBudget(this.connection!, transaction, [NATIVE_MINT]);
transaction.partialSign(message, ancillaryKeypair);
return transaction;
}
Expand Down Expand Up @@ -531,17 +525,16 @@ export class SolanaContext<
recipientAddress,
recipientChainId,
);
const transaction = new Transaction();
transaction.add(
...(await this.determineComputeBudget([
new PublicKey(fromAddress),
new PublicKey(mintAddress),
])),

const transaction = new Transaction(
await this.connection?.getLatestBlockhash(commitment),
);
transaction.add(approvalIx, tokenBridgeTransferIx);
const { blockhash } = await this.connection.getLatestBlockhash(commitment);
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(senderAddress);
await addComputeBudget(this.connection!, transaction, [
new PublicKey(fromAddress),
new PublicKey(mintAddress),
]);
transaction.partialSign(message);
return transaction;
}
Expand Down Expand Up @@ -905,29 +898,25 @@ export class SolanaContext<
const isNativeSol =
parsed.tokenChain === MAINNET_CHAINS.solana &&
tokenKey.equals(NATIVE_MINT);
const transaction = new Transaction();
transaction.add(...(await this.determineComputeBudget([tokenKey])));
if (isNativeSol) {
transaction.add(
await redeemAndUnwrapOnSolana(

const transaction = isNativeSol
? await redeemAndUnwrapOnSolana(
this.connection,
contracts.core,
contracts.token_bridge,
payerAddr,
signedVAA,
),
);
} else {
transaction.add(
await redeemOnSolana(
)
: await redeemOnSolana(
this.connection,
contracts.core,
contracts.token_bridge,
payerAddr,
signedVAA,
),
);
}
);

await addComputeBudget(this.connection!, transaction, [tokenKey]);

return transaction;
}

Expand Down Expand Up @@ -961,8 +950,10 @@ export class SolanaContext<
parsed.tokenChain,
parsed.tokenAddress,
);
const transaction = new Transaction();
transaction.add(...(await this.determineComputeBudget([mint])));

const transaction = new Transaction(
await this.connection?.getLatestBlockhash('finalized'),
);
const recipientTokenAccount = getAssociatedTokenAddressSync(
mint,
recipient,
Expand Down Expand Up @@ -1004,9 +995,8 @@ export class SolanaContext<
);
}
transaction.add(redeemIx);
const { blockhash } = await this.connection.getLatestBlockhash('finalized');
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(recipient);
await addComputeBudget(this.connection!, transaction, [mint]);
return transaction;
}

Expand Down Expand Up @@ -1073,7 +1063,10 @@ export class SolanaContext<
);
const recipientChainId = this.context.toChainId(recipientChain);
const nonce = createNonce().readUint32LE();
const transaction = new Transaction();
const transaction = new Transaction(
await this.connection?.getLatestBlockhash('finalized'),
);
transaction.feePayer = new PublicKey(senderAddress);

if (token === NATIVE || token.chain === SOLANA_CHAIN_NAME) {
const mint = token === NATIVE ? NATIVE_MINT : token.address;
Expand Down Expand Up @@ -1107,9 +1100,6 @@ export class SolanaContext<
}
}

transaction.add(
...(await this.determineComputeBudget(writableAddresses)),
);
transaction.add(
await createTransferNativeTokensWithRelayInstruction(
this.connection,
Expand All @@ -1126,12 +1116,11 @@ export class SolanaContext<
wrapToken,
),
);

await addComputeBudget(this.connection!, transaction, writableAddresses);
} else {
const mint = await this.mustGetForeignAsset(token, sendingChain);

transaction.add(
...(await this.determineComputeBudget([new PublicKey(mint)])),
);
transaction.add(
await createTransferWrappedTokensWithRelayInstruction(
this.connection,
Expand All @@ -1147,11 +1136,11 @@ export class SolanaContext<
nonce,
),
);
}

const { blockhash } = await this.connection.getLatestBlockhash('finalized');
transaction.recentBlockhash = blockhash;
transaction.feePayer = new PublicKey(senderAddress);
await addComputeBudget(this.connection!, transaction, [
new PublicKey(mint),
]);
}
return transaction;
}

Expand Down Expand Up @@ -1221,43 +1210,4 @@ export class SolanaContext<
chain: 'solana',
};
}

async determineComputeBudget(
lockedWritableAccounts: PublicKey[] = [],
): Promise<TransactionInstruction[]> {
// https://twitter.com/0xMert_/status/1768669928825962706

let fee = 1; // Set fee to 100,000 microlamport by default

try {
const recentFeesResponse =
await this.connection?.getRecentPrioritizationFees({
lockedWritableAccounts,
});

if (recentFeesResponse) {
// Get 75th percentile fee paid in recent slots
const recentFees = recentFeesResponse
.map((dp) => dp.prioritizationFee)
.filter((dp) => dp > 0)
.sort((a, b) => a - b);

if (recentFees.length > 0) {
const medianFee =
recentFees[Math.floor(recentFees.length * SOLANA_FEE_PERCENTILE)];
fee = Math.max(fee, medianFee);
}
}
} catch (e) {
console.error('Error fetching Solana recent fees', e);
}

console.info(`Setting Solana compute unit price to ${fee} microLamports`);

return [
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: fee,
}),
];
}
}
98 changes: 98 additions & 0 deletions sdk/src/contexts/solana/utils/computeBudget/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
Connection,
Transaction,
TransactionInstruction,
PublicKey,
ComputeBudgetProgram,
} from '@solana/web3.js';

// Add priority fee according to 50th percentile of recent fees paid
const DEFAULT_FEE_PERCENTILE = 0.5;

export async function addComputeBudget(
connection: Connection,
transaction: Transaction,
lockedWritableAccounts: PublicKey[] = [],
): Promise<void> {
const ixs = await determineComputeBudget(
connection,
transaction,
lockedWritableAccounts,
);
transaction.add(...ixs);
}

export async function determineComputeBudget(
connection: Connection,
transaction: Transaction,
lockedWritableAccounts: PublicKey[] = [],
feePercentile: number = DEFAULT_FEE_PERCENTILE,
): Promise<TransactionInstruction[]> {
let computeBudget = 250_000;
let priorityFee = 1;

try {
const simulateResponse = await connection.simulateTransaction(transaction);

if (simulateResponse?.value?.unitsConsumed) {
// Set compute budget to 120% of the units used in the simulated transaction
computeBudget = Math.round(simulateResponse.value.unitsConsumed * 1.2);
}

priorityFee = await determinePriorityFee(
connection,
lockedWritableAccounts,
feePercentile,
);
} catch (e) {
console.error(`Failed to get compute budget for Solana transaction: ${e}`);
}

console.info(`Setting Solana compute unit budget to ${computeBudget} units`);
console.info(
`Setting Solana compute unit price to ${priorityFee} microLamports`,
);

return [
ComputeBudgetProgram.setComputeUnitLimit({
units: computeBudget,
}),
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: priorityFee,
}),
];
}

async function determinePriorityFee(
connection: Connection,
lockedWritableAccounts: PublicKey[] = [],
percentile: number,
): Promise<number> {
// https://twitter.com/0xMert_/status/1768669928825962706

let fee = 1; // Set fee to 100,000 microlamport by default

try {
const recentFeesResponse = await connection.getRecentPrioritizationFees({
lockedWritableAccounts,
});

if (recentFeesResponse) {
// Get 75th percentile fee paid in recent slots
const recentFees = recentFeesResponse
.map((dp) => dp.prioritizationFee)
.filter((dp) => dp > 0)
.sort((a, b) => a - b);

if (recentFees.length > 0) {
const medianFee =
recentFees[Math.floor(recentFees.length * percentile)];
fee = Math.max(fee, medianFee);
}
}
} catch (e) {
console.error('Error fetching Solana recent fees', e);
}

return fee;
}
5 changes: 2 additions & 3 deletions sdk/src/contexts/solana/utils/sendAndConfirmPostVaa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Keypair,
PublicKeyInitData,
Transaction,
TransactionInstruction,
} from '@solana/web3.js';
import {
signSendAndConfirmTransaction,
Expand All @@ -15,6 +14,7 @@ import {
TransactionSignatureAndResponse,
PreparedTransactions,
} from './utils';
import { addComputeBudget } from './computeBudget';
import {
createPostVaaInstruction,
createVerifySignaturesInstructions,
Expand All @@ -32,7 +32,6 @@ export async function postVaaWithRetry(
vaa: Buffer,
maxRetries?: number,
commitment?: Commitment,
computeBudgetInstructions?: TransactionInstruction[],
): Promise<TransactionSignatureAndResponse[]> {
const { unsignedTransactions, signers } =
await createPostSignedVaaTransactions(
Expand All @@ -43,7 +42,7 @@ export async function postVaaWithRetry(
commitment,
);
for (const unsignedTransaction of unsignedTransactions) {
unsignedTransaction.add(...(computeBudgetInstructions || []));
addComputeBudget(connection, unsignedTransaction, []);
}

const postVaaTransaction = unsignedTransactions.pop()!;
Expand Down
Loading

0 comments on commit 2bac879

Please sign in to comment.