feat: support additional compute budget ixs (#25104)
This commit is contained in:
parent
f6da78a741
commit
a7e6f4deef
|
@ -1,4 +1,5 @@
|
||||||
import * as BufferLayout from '@solana/buffer-layout';
|
import * as BufferLayout from '@solana/buffer-layout';
|
||||||
|
import {u64} from '@solana/buffer-layout-utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
encodeData,
|
encodeData,
|
||||||
|
@ -76,6 +77,34 @@ export class ComputeBudgetInstruction {
|
||||||
return {bytes};
|
return {bytes};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode set compute unit limit compute budget instruction and retrieve the instruction params.
|
||||||
|
*/
|
||||||
|
static decodeSetComputeUnitLimit(
|
||||||
|
instruction: TransactionInstruction,
|
||||||
|
): SetComputeUnitLimitParams {
|
||||||
|
this.checkProgramId(instruction.programId);
|
||||||
|
const {units} = decodeData(
|
||||||
|
COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitLimit,
|
||||||
|
instruction.data,
|
||||||
|
);
|
||||||
|
return {units};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode set compute unit price compute budget instruction and retrieve the instruction params.
|
||||||
|
*/
|
||||||
|
static decodeSetComputeUnitPrice(
|
||||||
|
instruction: TransactionInstruction,
|
||||||
|
): SetComputeUnitPriceParams {
|
||||||
|
this.checkProgramId(instruction.programId);
|
||||||
|
const {microLamports} = decodeData(
|
||||||
|
COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice,
|
||||||
|
instruction.data,
|
||||||
|
);
|
||||||
|
return {microLamports};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -96,11 +125,18 @@ export type ComputeBudgetInstructionType =
|
||||||
// It would be preferable for this type to be `keyof ComputeBudgetInstructionInputData`
|
// It would be preferable for this type to be `keyof ComputeBudgetInstructionInputData`
|
||||||
// but Typedoc does not transpile `keyof` expressions.
|
// but Typedoc does not transpile `keyof` expressions.
|
||||||
// See https://github.com/TypeStrong/typedoc/issues/1894
|
// See https://github.com/TypeStrong/typedoc/issues/1894
|
||||||
'RequestUnits' | 'RequestHeapFrame';
|
| 'RequestUnits'
|
||||||
|
| 'RequestHeapFrame'
|
||||||
|
| 'SetComputeUnitLimit'
|
||||||
|
| 'SetComputeUnitPrice';
|
||||||
|
|
||||||
type ComputeBudgetInstructionInputData = {
|
type ComputeBudgetInstructionInputData = {
|
||||||
RequestUnits: IInstructionInputData & Readonly<RequestUnitsParams>;
|
RequestUnits: IInstructionInputData & Readonly<RequestUnitsParams>;
|
||||||
RequestHeapFrame: IInstructionInputData & Readonly<RequestHeapFrameParams>;
|
RequestHeapFrame: IInstructionInputData & Readonly<RequestHeapFrameParams>;
|
||||||
|
SetComputeUnitLimit: IInstructionInputData &
|
||||||
|
Readonly<SetComputeUnitLimitParams>;
|
||||||
|
SetComputeUnitPrice: IInstructionInputData &
|
||||||
|
Readonly<SetComputeUnitPriceParams>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,8 +145,7 @@ type ComputeBudgetInstructionInputData = {
|
||||||
export interface RequestUnitsParams {
|
export interface RequestUnitsParams {
|
||||||
/** Units to request for transaction-wide compute */
|
/** Units to request for transaction-wide compute */
|
||||||
units: number;
|
units: number;
|
||||||
|
/** Prioritization fee lamports */
|
||||||
/** Additional fee to pay */
|
|
||||||
additionalFee: number;
|
additionalFee: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +157,22 @@ export type RequestHeapFrameParams = {
|
||||||
bytes: number;
|
bytes: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set compute unit limit instruction params
|
||||||
|
*/
|
||||||
|
export interface SetComputeUnitLimitParams {
|
||||||
|
/** Transaction-wide compute unit limit */
|
||||||
|
units: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set compute unit price instruction params
|
||||||
|
*/
|
||||||
|
export interface SetComputeUnitPriceParams {
|
||||||
|
/** Transaction compute unit price used for prioritization fees */
|
||||||
|
microLamports: number | bigint;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enumeration of valid ComputeBudget InstructionType's
|
* An enumeration of valid ComputeBudget InstructionType's
|
||||||
* @internal
|
* @internal
|
||||||
|
@ -147,6 +198,18 @@ export const COMPUTE_BUDGET_INSTRUCTION_LAYOUTS = Object.freeze<{
|
||||||
ComputeBudgetInstructionInputData['RequestHeapFrame']
|
ComputeBudgetInstructionInputData['RequestHeapFrame']
|
||||||
>([BufferLayout.u8('instruction'), BufferLayout.u32('bytes')]),
|
>([BufferLayout.u8('instruction'), BufferLayout.u32('bytes')]),
|
||||||
},
|
},
|
||||||
|
SetComputeUnitLimit: {
|
||||||
|
index: 2,
|
||||||
|
layout: BufferLayout.struct<
|
||||||
|
ComputeBudgetInstructionInputData['SetComputeUnitLimit']
|
||||||
|
>([BufferLayout.u8('instruction'), BufferLayout.u32('units')]),
|
||||||
|
},
|
||||||
|
SetComputeUnitPrice: {
|
||||||
|
index: 3,
|
||||||
|
layout: BufferLayout.struct<
|
||||||
|
ComputeBudgetInstructionInputData['SetComputeUnitPrice']
|
||||||
|
>([BufferLayout.u8('instruction'), u64('microLamports')]),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,4 +249,30 @@ export class ComputeBudgetProgram {
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setComputeUnitLimit(
|
||||||
|
params: SetComputeUnitLimitParams,
|
||||||
|
): TransactionInstruction {
|
||||||
|
const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitLimit;
|
||||||
|
const data = encodeData(type, params);
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys: [],
|
||||||
|
programId: this.programId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static setComputeUnitPrice(
|
||||||
|
params: SetComputeUnitPriceParams,
|
||||||
|
): TransactionInstruction {
|
||||||
|
const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice;
|
||||||
|
const data = encodeData(type, {
|
||||||
|
microLamports: BigInt(params.microLamports),
|
||||||
|
});
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys: [],
|
||||||
|
programId: this.programId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ import {
|
||||||
Transaction,
|
Transaction,
|
||||||
ComputeBudgetProgram,
|
ComputeBudgetProgram,
|
||||||
ComputeBudgetInstruction,
|
ComputeBudgetInstruction,
|
||||||
PublicKey,
|
|
||||||
SystemProgram,
|
|
||||||
sendAndConfirmTransaction,
|
sendAndConfirmTransaction,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import {helpers} from './mocks/rpc-http';
|
import {helpers} from './mocks/rpc-http';
|
||||||
|
@ -21,15 +19,13 @@ describe('ComputeBudgetProgram', () => {
|
||||||
it('requestUnits', () => {
|
it('requestUnits', () => {
|
||||||
const params = {
|
const params = {
|
||||||
units: 150000,
|
units: 150000,
|
||||||
additionalFee: 0,
|
additionalFee: LAMPORTS_PER_SOL,
|
||||||
};
|
};
|
||||||
const transaction = new Transaction().add(
|
const ix = ComputeBudgetProgram.requestUnits(params);
|
||||||
ComputeBudgetProgram.requestUnits(params),
|
const decodedParams = ComputeBudgetInstruction.decodeRequestUnits(ix);
|
||||||
);
|
expect(params).to.eql(decodedParams);
|
||||||
expect(transaction.instructions).to.have.length(1);
|
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
|
||||||
const [computeBudgetInstruction] = transaction.instructions;
|
'RequestUnits',
|
||||||
expect(params).to.eql(
|
|
||||||
ComputeBudgetInstruction.decodeRequestUnits(computeBudgetInstruction),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,48 +33,49 @@ describe('ComputeBudgetProgram', () => {
|
||||||
const params = {
|
const params = {
|
||||||
bytes: 33 * 1024,
|
bytes: 33 * 1024,
|
||||||
};
|
};
|
||||||
const transaction = new Transaction().add(
|
const ix = ComputeBudgetProgram.requestHeapFrame(params);
|
||||||
ComputeBudgetProgram.requestHeapFrame(params),
|
const decodedParams = ComputeBudgetInstruction.decodeRequestHeapFrame(ix);
|
||||||
);
|
expect(decodedParams).to.eql(params);
|
||||||
expect(transaction.instructions).to.have.length(1);
|
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
|
||||||
const [computeBudgetInstruction] = transaction.instructions;
|
'RequestHeapFrame',
|
||||||
expect(params).to.eql(
|
|
||||||
ComputeBudgetInstruction.decodeRequestHeapFrame(computeBudgetInstruction),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ComputeBudgetInstruction', () => {
|
it('setComputeUnitLimit', () => {
|
||||||
const requestUnits = ComputeBudgetProgram.requestUnits({
|
const params = {
|
||||||
units: 150000,
|
units: 50_000,
|
||||||
additionalFee: 0,
|
};
|
||||||
});
|
const ix = ComputeBudgetProgram.setComputeUnitLimit(params);
|
||||||
const requestHeapFrame = ComputeBudgetProgram.requestHeapFrame({
|
const decodedParams =
|
||||||
bytes: 33 * 1024,
|
ComputeBudgetInstruction.decodeSetComputeUnitLimit(ix);
|
||||||
});
|
expect(decodedParams).to.eql(params);
|
||||||
|
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
|
||||||
|
'SetComputeUnitLimit',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const requestUnitsTransaction = new Transaction().add(requestUnits);
|
it('setComputeUnitPrice', () => {
|
||||||
expect(requestUnitsTransaction.instructions).to.have.length(1);
|
const params = {
|
||||||
const requestUnitsTransactionType =
|
microLamports: 100_000,
|
||||||
ComputeBudgetInstruction.decodeInstructionType(
|
};
|
||||||
requestUnitsTransaction.instructions[0],
|
const ix = ComputeBudgetProgram.setComputeUnitPrice(params);
|
||||||
);
|
const expectedParams = {
|
||||||
expect(requestUnitsTransactionType).to.eq('RequestUnits');
|
...params,
|
||||||
|
microLamports: BigInt(params.microLamports),
|
||||||
const requestHeapFrameTransaction = new Transaction().add(requestHeapFrame);
|
};
|
||||||
expect(requestHeapFrameTransaction.instructions).to.have.length(1);
|
const decodedParams =
|
||||||
const requestHeapFrameTransactionType =
|
ComputeBudgetInstruction.decodeSetComputeUnitPrice(ix);
|
||||||
ComputeBudgetInstruction.decodeInstructionType(
|
expect(decodedParams).to.eql(expectedParams);
|
||||||
requestHeapFrameTransaction.instructions[0],
|
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
|
||||||
);
|
'SetComputeUnitPrice',
|
||||||
expect(requestHeapFrameTransactionType).to.eq('RequestHeapFrame');
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.TEST_LIVE) {
|
if (process.env.TEST_LIVE) {
|
||||||
const STARTING_AMOUNT = 2 * LAMPORTS_PER_SOL;
|
it('send live request units ix', async () => {
|
||||||
const FEE_AMOUNT = LAMPORTS_PER_SOL;
|
|
||||||
it('live compute budget actions', async () => {
|
|
||||||
const connection = new Connection(url, 'confirmed');
|
const connection = new Connection(url, 'confirmed');
|
||||||
|
const FEE_AMOUNT = LAMPORTS_PER_SOL;
|
||||||
|
const STARTING_AMOUNT = 2 * LAMPORTS_PER_SOL;
|
||||||
const baseAccount = Keypair.generate();
|
const baseAccount = Keypair.generate();
|
||||||
const basePubkey = baseAccount.publicKey;
|
const basePubkey = baseAccount.publicKey;
|
||||||
await helpers.airdrop({
|
await helpers.airdrop({
|
||||||
|
@ -87,69 +84,49 @@ describe('ComputeBudgetProgram', () => {
|
||||||
amount: STARTING_AMOUNT,
|
amount: STARTING_AMOUNT,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await connection.getBalance(baseAccount.publicKey)).to.eq(
|
const additionalFeeTooHighTransaction = new Transaction().add(
|
||||||
STARTING_AMOUNT,
|
|
||||||
);
|
|
||||||
|
|
||||||
const seed = 'hi there';
|
|
||||||
const programId = Keypair.generate().publicKey;
|
|
||||||
const createAccountWithSeedAddress = await PublicKey.createWithSeed(
|
|
||||||
basePubkey,
|
|
||||||
seed,
|
|
||||||
programId,
|
|
||||||
);
|
|
||||||
const space = 0;
|
|
||||||
|
|
||||||
let minimumAmount = await connection.getMinimumBalanceForRentExemption(
|
|
||||||
space,
|
|
||||||
);
|
|
||||||
|
|
||||||
const createAccountWithSeedParams = {
|
|
||||||
fromPubkey: basePubkey,
|
|
||||||
newAccountPubkey: createAccountWithSeedAddress,
|
|
||||||
basePubkey,
|
|
||||||
seed,
|
|
||||||
lamports: minimumAmount,
|
|
||||||
space,
|
|
||||||
programId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const createAccountFeeTooHighTransaction = new Transaction().add(
|
|
||||||
ComputeBudgetProgram.requestUnits({
|
ComputeBudgetProgram.requestUnits({
|
||||||
units: 2,
|
units: 150_000,
|
||||||
additionalFee: 2 * FEE_AMOUNT,
|
additionalFee: STARTING_AMOUNT,
|
||||||
}),
|
}),
|
||||||
SystemProgram.createAccountWithSeed(createAccountWithSeedParams),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sendAndConfirmTransaction(
|
sendAndConfirmTransaction(
|
||||||
connection,
|
connection,
|
||||||
createAccountFeeTooHighTransaction,
|
additionalFeeTooHighTransaction,
|
||||||
[baseAccount],
|
[baseAccount],
|
||||||
{preflightCommitment: 'confirmed'},
|
{preflightCommitment: 'confirmed'},
|
||||||
),
|
),
|
||||||
).to.be.rejected;
|
).to.be.rejected;
|
||||||
|
|
||||||
expect(await connection.getBalance(baseAccount.publicKey)).to.eq(
|
const validAdditionalFeeTransaction = new Transaction().add(
|
||||||
STARTING_AMOUNT,
|
|
||||||
);
|
|
||||||
|
|
||||||
const createAccountFeeTransaction = new Transaction().add(
|
|
||||||
ComputeBudgetProgram.requestUnits({
|
ComputeBudgetProgram.requestUnits({
|
||||||
units: 2,
|
units: 150_000,
|
||||||
additionalFee: FEE_AMOUNT,
|
additionalFee: FEE_AMOUNT,
|
||||||
}),
|
}),
|
||||||
SystemProgram.createAccountWithSeed(createAccountWithSeedParams),
|
|
||||||
);
|
);
|
||||||
await sendAndConfirmTransaction(
|
await sendAndConfirmTransaction(
|
||||||
connection,
|
connection,
|
||||||
createAccountFeeTransaction,
|
validAdditionalFeeTransaction,
|
||||||
[baseAccount],
|
[baseAccount],
|
||||||
{preflightCommitment: 'confirmed'},
|
{preflightCommitment: 'confirmed'},
|
||||||
);
|
);
|
||||||
expect(await connection.getBalance(baseAccount.publicKey)).to.be.at.most(
|
expect(await connection.getBalance(baseAccount.publicKey)).to.be.at.most(
|
||||||
STARTING_AMOUNT - FEE_AMOUNT - minimumAmount,
|
STARTING_AMOUNT - FEE_AMOUNT,
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('send live request heap ix', async () => {
|
||||||
|
const connection = new Connection(url, 'confirmed');
|
||||||
|
const STARTING_AMOUNT = 2 * LAMPORTS_PER_SOL;
|
||||||
|
const baseAccount = Keypair.generate();
|
||||||
|
const basePubkey = baseAccount.publicKey;
|
||||||
|
await helpers.airdrop({
|
||||||
|
connection,
|
||||||
|
address: basePubkey,
|
||||||
|
amount: STARTING_AMOUNT,
|
||||||
|
});
|
||||||
|
|
||||||
async function expectRequestHeapFailure(bytes: number) {
|
async function expectRequestHeapFailure(bytes: number) {
|
||||||
const requestHeapFrameTransaction = new Transaction().add(
|
const requestHeapFrameTransaction = new Transaction().add(
|
||||||
|
@ -181,6 +158,63 @@ describe('ComputeBudgetProgram', () => {
|
||||||
[baseAccount],
|
[baseAccount],
|
||||||
{preflightCommitment: 'confirmed'},
|
{preflightCommitment: 'confirmed'},
|
||||||
);
|
);
|
||||||
}).timeout(10 * 1000);
|
});
|
||||||
|
|
||||||
|
it('send live compute unit ixs', async () => {
|
||||||
|
const connection = new Connection(url, 'confirmed');
|
||||||
|
const FEE_AMOUNT = LAMPORTS_PER_SOL;
|
||||||
|
const STARTING_AMOUNT = 2 * LAMPORTS_PER_SOL;
|
||||||
|
const baseAccount = Keypair.generate();
|
||||||
|
const basePubkey = baseAccount.publicKey;
|
||||||
|
await helpers.airdrop({
|
||||||
|
connection,
|
||||||
|
address: basePubkey,
|
||||||
|
amount: STARTING_AMOUNT,
|
||||||
|
});
|
||||||
|
|
||||||
|
// lamport fee = 2B * 1M / 1M = 2 SOL
|
||||||
|
const prioritizationFeeTooHighTransaction = new Transaction()
|
||||||
|
.add(
|
||||||
|
ComputeBudgetProgram.setComputeUnitPrice({
|
||||||
|
microLamports: 2_000_000_000,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
ComputeBudgetProgram.setComputeUnitLimit({
|
||||||
|
units: 1_000_000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
sendAndConfirmTransaction(
|
||||||
|
connection,
|
||||||
|
prioritizationFeeTooHighTransaction,
|
||||||
|
[baseAccount],
|
||||||
|
{preflightCommitment: 'confirmed'},
|
||||||
|
),
|
||||||
|
).to.be.rejected;
|
||||||
|
|
||||||
|
// lamport fee = 1B * 1M / 1M = 1 SOL
|
||||||
|
const validPrioritizationFeeTransaction = new Transaction()
|
||||||
|
.add(
|
||||||
|
ComputeBudgetProgram.setComputeUnitPrice({
|
||||||
|
microLamports: 1_000_000_000,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
ComputeBudgetProgram.setComputeUnitLimit({
|
||||||
|
units: 1_000_000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await sendAndConfirmTransaction(
|
||||||
|
connection,
|
||||||
|
validPrioritizationFeeTransaction,
|
||||||
|
[baseAccount],
|
||||||
|
{preflightCommitment: 'confirmed'},
|
||||||
|
);
|
||||||
|
expect(await connection.getBalance(baseAccount.publicKey)).to.be.at.most(
|
||||||
|
STARTING_AMOUNT - FEE_AMOUNT,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue