Skip to content
Draft
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
48 changes: 47 additions & 1 deletion src/arch/svm/SpokeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
type WritableAccount,
type ReadonlyAccount,
type Commitment,
type CompilableTransactionMessage,
} from "@solana/kit";
import assert from "assert";
import winston from "winston";
Expand Down Expand Up @@ -100,6 +101,8 @@ import {
*/
export const SLOT_DURATION_MS = 400;

export const SOLANA_TX_SIZE_LIMIT = 1232;

type ProtoFill = Omit<RelayData, "recipient" | "outputToken"> & {
destinationChainId: number;
recipient: SvmAddress;
Expand Down Expand Up @@ -693,6 +696,8 @@ export async function getIPFillRelayTx(
getEventAuthority(program),
]);

const recipientAtaEncodedAccount = await fetchEncodedAccount(solanaClient, recipientAta);

// Add remaining accounts if the relayData has a non-empty message.
// @dev ! since in the context of creating a `fillRelayTx`, `relayData` must be defined.
const remainingAccounts: (WritableAccount | ReadonlyAccount)[] = [];
Expand Down Expand Up @@ -736,7 +741,7 @@ export async function getIPFillRelayTx(
fillInput,
{ outputAmount: relayData.outputAmount.toBigInt(), recipient: toAddress(relayData.recipient) },
mintInfo.data.decimals,
true,
!recipientAtaEncodedAccount.exists,
remainingAccounts
);
}
Expand Down Expand Up @@ -1413,6 +1418,47 @@ export async function getCCTPDepositAccounts(
};
}

/**
* Returns true if the input deposit's corresponding relay data would result in a transaction size
* that is larger than the Solana transaction size limit.
* @param fillRelayTx The compilable fill relay transaction to check.
* @returns Object containing a boolean if the input deposit requires a multipart fill, false otherwise and
* the number of bytes in the serialized transaction.
*/
export async function isSVMFillTooLarge(fillRelayTx: CompilableTransactionMessage): Promise<{
tooLarge: boolean;
sizeBytes: number;
}> {
const sizeBytes = await calculateFillSizeBytes(fillRelayTx);
return {
tooLarge: sizeBytes > SOLANA_TX_SIZE_LIMIT,
sizeBytes,
};
}

/**
* Returns the byte size of a base64 transaction.
* @param base64TxString base64 serialized Solana transaction.
* @returns The number of bytes in the transaction.
*/
export function base64StrToByteSize(base64TxString: string): number {
// base64 string has 6 bits per character, so every 4 symbols represent 3 bytes
// However, we also need to account for padding: https://en.wikipedia.org/wiki/Base64#Padding
const paddingLen = base64TxString.endsWith("==") ? 2 : base64TxString.endsWith("=") ? 1 : 0;
return (base64TxString.length * 3) / 4 - paddingLen;
}

/**
* Returns the size of the fill relay transaction using the input relayData.
* @param fillTx The compilable fill relay transaction.
* @returns The number of bytes in the serialized fillRelay transaction.
*/
export async function calculateFillSizeBytes(fillTx: CompilableTransactionMessage): Promise<number> {
const signedTransaction = await signTransactionMessageWithSigners(fillTx);
const serializedTx = getBase64EncodedWireTransaction(signedTransaction);
return base64StrToByteSize(serializedTx);
}

/**
* Returns the account metas for a deposit message.
* @param message The CCTP message.
Expand Down
4 changes: 4 additions & 0 deletions src/arch/svm/encoders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export function getHandlerMessageEncoder(): Encoder<Array<CompiledIx>> {
return getArrayEncoder(getCompiledIxEncoder());
}

export function getHandlerMessageDecoder(): Decoder<Array<CompiledIx>> {
return getArrayDecoder(getCompiledIxDecoder());
}

export function getCompiledIxEncoder(): Encoder<CompiledIx> {
return getStructEncoder([
["program_id_index", getU8Encoder()],
Expand Down
22 changes: 20 additions & 2 deletions src/relayFeeCalculator/chain-queries/svmQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@ import {
TransactionSigner,
fetchEncodedAccount,
isSome,
type CompilableTransactionMessage,
} from "@solana/kit";
import { SVMProvider, SolanaVoidSigner, getFillRelayTx, toAddress, getAssociatedTokenAddress } from "../../arch/svm";
import {
SVMProvider,
SolanaVoidSigner,
getFillRelayTx,
toAddress,
getAssociatedTokenAddress,
isSVMFillTooLarge,
} from "../../arch/svm";
import { Coingecko } from "../../coingecko";
import { CHAIN_IDs } from "../../constants";
import { getGasPriceEstimate } from "../../gasPriceOracle";
Expand Down Expand Up @@ -85,7 +93,7 @@ export class SvmQuery implements QueryInterface {
);

const [computeUnitsConsumed, gasPriceEstimate, tokenAccountInfo] = await Promise.all([
toBN(await this.computeUnitEstimator(fillRelayTx)),
this.estimateComputeUnits(fillRelayTx),
getGasPriceEstimate(this.provider, {
unsignedTx: fillRelayTx,
baseFeeMultiplier: options.baseFeeMultiplier,
Expand Down Expand Up @@ -211,4 +219,14 @@ export class SvmQuery implements QueryInterface {
if (!this.symbolMapping[tokenSymbol]) throw new Error(`${tokenSymbol} does not exist in mapping`);
return this.symbolMapping[tokenSymbol].decimals;
}

async estimateComputeUnits(fillRelayTx: CompilableTransactionMessage): Promise<BigNumber> {
const fillTooLarge = await isSVMFillTooLarge(fillRelayTx);
if (fillTooLarge.tooLarge) {
return toBN(await this.computeUnitEstimator(fillRelayTx));
}
const totalComputeUnitAmount = 0;
// The fill is too large; we need to simulate the transaction in a bundle.
return toBN(totalComputeUnitAmount);
}
}
Loading