Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/afraid-tables-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solana/instruction-plans': minor
---

Add new `TransactionPlanResult` type with helpers. This type describes the execution results of transaction plans with the same structural hierarchy — capturing the execution status of each transaction message whether executed in parallel, sequentially, or as a single transaction.
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import '@solana/test-matchers/toBeFrozenObject';

import { Address } from '@solana/addresses';
import { SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE, SolanaError } from '@solana/errors';
import { pipe } from '@solana/functional';
import { BaseTransactionMessage, TransactionMessageWithFeePayer } from '@solana/transaction-messages';
import { createTransactionMessage, setTransactionMessageFeePayer } from '@solana/transaction-messages';
import { Transaction } from '@solana/transactions';

import {
canceledSingleTransactionPlanResult,
failedSingleTransactionPlanResult,
nonDivisibleSequentialTransactionPlanResult,
parallelTransactionPlanResult,
sequentialTransactionPlanResult,
successfulSingleTransactionPlanResult,
} from '../transaction-plan-result';

function createMessage<TId extends string>(
id: TId,
): BaseTransactionMessage & TransactionMessageWithFeePayer & { id: TId } {
return pipe(
createTransactionMessage({ version: 0 }),
m => setTransactionMessageFeePayer('E9Nykp3rSdza2moQutaJ3K3RSC8E5iFERX2SqLTsQfjJ' as Address, m),
m => Object.freeze({ ...m, id }),
);
}

function createTransaction<TId extends string>(id: TId): Transaction & { id: TId } {
return Object.freeze({ id }) as unknown as Transaction & { id: TId };
}

describe('successfulSingleTransactionPlanResult', () => {
it('creates SingleTransactionPlanResult objects with successful status', () => {
const messageA = createMessage('A');
const transactionA = createTransaction('A');
const result = successfulSingleTransactionPlanResult(messageA, transactionA);
expect(result).toEqual({
kind: 'single',
message: messageA,
status: { context: {}, kind: 'successful', transaction: transactionA },
});
});
it('accepts an optional context object', () => {
const messageA = createMessage('A');
const transactionA = createTransaction('A');
const context = { foo: 'bar' };
const result = successfulSingleTransactionPlanResult(messageA, transactionA, context);
expect(result).toEqual({
kind: 'single',
message: messageA,
status: { context, kind: 'successful', transaction: transactionA },
});
});
it('freezes created SingleTransactionPlanResult objects', () => {
const messageA = createMessage('A');
const transactionA = createTransaction('A');
const result = successfulSingleTransactionPlanResult(messageA, transactionA);
expect(result).toBeFrozenObject();
});
it('freezes the status object of created SingleTransactionPlanResult objects', () => {
const messageA = createMessage('A');
const transactionA = createTransaction('A');
const result = successfulSingleTransactionPlanResult(messageA, transactionA);
expect(result.status).toBeFrozenObject();
});
});

describe('failedSingleTransactionPlanResult', () => {
it('creates SingleTransactionPlanResult objects with failed status', () => {
const messageA = createMessage('A');
const error = new SolanaError(SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE);
const result = failedSingleTransactionPlanResult(messageA, error);
expect(result).toEqual({
kind: 'single',
message: messageA,
status: { error, kind: 'failed' },
});
});
it('freezes created SingleTransactionPlanResult objects', () => {
const messageA = createMessage('A');
const error = new SolanaError(SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE);
const result = failedSingleTransactionPlanResult(messageA, error);
expect(result).toBeFrozenObject();
});
it('freezes the status object of created SingleTransactionPlanResult objects', () => {
const messageA = createMessage('A');
const error = new SolanaError(SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE);
const result = failedSingleTransactionPlanResult(messageA, error);
expect(result.status).toBeFrozenObject();
});
});

describe('canceledSingleTransactionPlanResult', () => {
it('creates SingleTransactionPlanResult objects with canceled status', () => {
const messageA = createMessage('A');
const result = canceledSingleTransactionPlanResult(messageA);
expect(result).toEqual({
kind: 'single',
message: messageA,
status: { kind: 'canceled' },
});
});
it('freezes created SingleTransactionPlanResult objects', () => {
const messageA = createMessage('A');
const result = canceledSingleTransactionPlanResult(messageA);
expect(result).toBeFrozenObject();
});
it('freezes the status object of created SingleTransactionPlanResult objects', () => {
const messageA = createMessage('A');
const result = canceledSingleTransactionPlanResult(messageA);
expect(result.status).toBeFrozenObject();
});
});

describe('parallelTransactionPlanResult', () => {
it('creates ParallelTransactionPlanResult objects from other results', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const result = parallelTransactionPlanResult([planA, planB]);
expect(result).toEqual({
kind: 'parallel',
plans: [planA, planB],
});
});
it('can nest other result types', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const planC = canceledSingleTransactionPlanResult(createMessage('C'));
const result = parallelTransactionPlanResult([planA, parallelTransactionPlanResult([planB, planC])]);
expect(result).toEqual({
kind: 'parallel',
plans: [planA, { kind: 'parallel', plans: [planB, planC] }],
});
});
it('freezes created ParallelTransactionPlanResult objects', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const result = parallelTransactionPlanResult([planA, planB]);
expect(result).toBeFrozenObject();
});
});

describe('sequentialTransactionPlanResult', () => {
it('creates divisible SequentialTransactionPlanResult objects from other results', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const result = sequentialTransactionPlanResult([planA, planB]);
expect(result).toEqual({
divisible: true,
kind: 'sequential',
plans: [planA, planB],
});
});
it('can nest other result types', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const planC = canceledSingleTransactionPlanResult(createMessage('C'));
const result = sequentialTransactionPlanResult([planA, sequentialTransactionPlanResult([planB, planC])]);
expect(result).toEqual({
divisible: true,
kind: 'sequential',
plans: [planA, { divisible: true, kind: 'sequential', plans: [planB, planC] }],
});
});
it('freezes created SequentialTransactionPlanResult objects', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const result = sequentialTransactionPlanResult([planA, planB]);
expect(result).toBeFrozenObject();
});
});

describe('nonDivisibleSequentialTransactionPlanResult', () => {
it('creates non-divisible SequentialTransactionPlanResult objects from other results', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const result = nonDivisibleSequentialTransactionPlanResult([planA, planB]);
expect(result).toEqual({
divisible: false,
kind: 'sequential',
plans: [planA, planB],
});
});
it('can nest other result types', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const planC = canceledSingleTransactionPlanResult(createMessage('C'));
const result = nonDivisibleSequentialTransactionPlanResult([
planA,
nonDivisibleSequentialTransactionPlanResult([planB, planC]),
]);
expect(result).toEqual({
divisible: false,
kind: 'sequential',
plans: [planA, { divisible: false, kind: 'sequential', plans: [planB, planC] }],
});
});
it('freezes created SequentialTransactionPlanResult objects', () => {
const planA = canceledSingleTransactionPlanResult(createMessage('A'));
const planB = canceledSingleTransactionPlanResult(createMessage('B'));
const result = nonDivisibleSequentialTransactionPlanResult([planA, planB]);
expect(result).toBeFrozenObject();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import type { SolanaError } from '@solana/errors';
import type { BaseTransactionMessage, TransactionMessageWithFeePayer } from '@solana/transaction-messages';
import type { Transaction } from '@solana/transactions';

import {
canceledSingleTransactionPlanResult,
failedSingleTransactionPlanResult,
nonDivisibleSequentialTransactionPlanResult,
ParallelTransactionPlanResult,
parallelTransactionPlanResult,
SequentialTransactionPlanResult,
sequentialTransactionPlanResult,
SingleTransactionPlanResult,
successfulSingleTransactionPlanResult,
TransactionPlanResult,
TransactionPlanResultContext,
} from '../transaction-plan-result';

const messageA = null as unknown as BaseTransactionMessage & TransactionMessageWithFeePayer & { id: 'A' };
const messageB = null as unknown as BaseTransactionMessage & TransactionMessageWithFeePayer & { id: 'B' };
const messageC = null as unknown as BaseTransactionMessage & TransactionMessageWithFeePayer & { id: 'C' };
const transactionA = null as unknown as Transaction;
const transactionB = null as unknown as Transaction;
const error = null as unknown as SolanaError;

type CustomContext = { customData: string };

// [DESCRIBE] parallelTransactionPlanResult
{
// It satisfies ParallelTransactionPlanResult.
{
const result = parallelTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA),
successfulSingleTransactionPlanResult(messageB, transactionB),
]);
result satisfies ParallelTransactionPlanResult;
result satisfies TransactionPlanResult;
}

// It can work with custom context.
{
const result = parallelTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA, { customData: 'A' }),
successfulSingleTransactionPlanResult(messageB, transactionB, { customData: 'B' }),
]);
result satisfies ParallelTransactionPlanResult<CustomContext>;
result satisfies TransactionPlanResult;
}

// It can nest other result plans.
{
const result = parallelTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA),
parallelTransactionPlanResult([
successfulSingleTransactionPlanResult(messageB, transactionB),
canceledSingleTransactionPlanResult(messageC),
]),
]);
result satisfies ParallelTransactionPlanResult;
result satisfies TransactionPlanResult;
}
}

// [DESCRIBE] sequentialTransactionPlanResult
{
// It satisfies a divisible SequentialTransactionPlanResult.
{
const result = sequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA),
successfulSingleTransactionPlanResult(messageB, transactionB),
]);
result satisfies SequentialTransactionPlanResult & { divisible: true };
result satisfies TransactionPlanResult;
}

// It can work with custom context.
{
const result = sequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA, { customData: 'A' }),
successfulSingleTransactionPlanResult(messageB, transactionB, { customData: 'B' }),
]);
result satisfies SequentialTransactionPlanResult<CustomContext> & { divisible: true };
result satisfies TransactionPlanResult;
}

// It can nest other result plans.
{
const result = sequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA),
sequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageB, transactionB),
canceledSingleTransactionPlanResult(messageC),
]),
]);
result satisfies SequentialTransactionPlanResult & { divisible: true };
result satisfies TransactionPlanResult;
}
}

// [DESCRIBE] nonDivisibleSequentialTransactionPlanResult
{
// It satisfies a non-divisible SequentialTransactionPlanResult.
{
const result = nonDivisibleSequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA),
successfulSingleTransactionPlanResult(messageB, transactionB),
]);
result satisfies SequentialTransactionPlanResult & { divisible: false };
result satisfies TransactionPlanResult;
}

// It can work with custom context.
{
const result = nonDivisibleSequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA, { customData: 'A' }),
successfulSingleTransactionPlanResult(messageB, transactionB, { customData: 'B' }),
]);
result satisfies SequentialTransactionPlanResult<CustomContext> & { divisible: false };
result satisfies TransactionPlanResult;
}

// It can nest other result plans.
{
const result = nonDivisibleSequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageA, transactionA),
nonDivisibleSequentialTransactionPlanResult([
successfulSingleTransactionPlanResult(messageB, transactionB),
canceledSingleTransactionPlanResult(messageC),
]),
]);
result satisfies SequentialTransactionPlanResult & { divisible: false };
result satisfies TransactionPlanResult;
}
}

// [DESCRIBE] successfulSingleTransactionPlanResult
{
// It satisfies SingleTransactionPlanResult with a successful status.
{
const result = successfulSingleTransactionPlanResult(messageA, transactionA);
result satisfies SingleTransactionPlanResult<TransactionPlanResultContext, typeof messageA>;
result satisfies TransactionPlanResult;
}

// It can include a custom context.
{
const result = successfulSingleTransactionPlanResult(messageA, transactionA, { customData: 'test' });
result satisfies SingleTransactionPlanResult<CustomContext, typeof messageA>;
result satisfies TransactionPlanResult;
}
}

// [DESCRIBE] failedSingleTransactionPlanResult
{
// It satisfies SingleTransactionPlanResult with a failed status.
{
const result = failedSingleTransactionPlanResult(messageA, error);
result satisfies SingleTransactionPlanResult<TransactionPlanResultContext, typeof messageA>;
result satisfies TransactionPlanResult;
}
}

// [DESCRIBE] canceledSingleTransactionPlanResult
{
// It satisfies SingleTransactionPlanResult with a canceled status.
{
const result = canceledSingleTransactionPlanResult(messageA);
result satisfies SingleTransactionPlanResult<TransactionPlanResultContext, typeof messageA>;
result satisfies TransactionPlanResult;
}
}
Loading
Loading