Skip to content
Closed
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
6 changes: 6 additions & 0 deletions .changeset/solid-sites-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@solana/rpc-types': patch
'@solana/errors': patch
---

Updated the `InstructionError` type based on the fact that the new validator produces errors with the address of the responsible program and its inner instruction index if applicable
85 changes: 84 additions & 1 deletion packages/errors/src/__tests__/instruction-error-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,56 @@ const EXPECTED_ERROR_CODES = [
describe('getSolanaErrorFromInstructionError', () => {
it.each(EXPECTED_ERROR_CODES)(
'produces the correct `SolanaError` for a `%s` error',
(transactionError, expectedCode) => {
const error = getSolanaErrorFromInstructionError(123, transactionError, '1111', 42);
expect(error).toEqual(
new SolanaError(expectedCode as SolanaErrorCode, {
index: 123,
innerIndex: 42,
responsibleProgramAddress: '1111',
}),
);
},
);
it.each(EXPECTED_ERROR_CODES)(
'produces the correct `SolanaError` for a pre-solana-transaction-error 3.0.0 `%s` error',
(transactionError, expectedCode) => {
const error = getSolanaErrorFromInstructionError(123, transactionError);
expect(error).toEqual(new SolanaError(expectedCode as SolanaErrorCode, { index: 123 }));
},
);
it.each(EXPECTED_ERROR_CODES)(
'produces the correct `SolanaError` for a `%s` error with a bigint index',
'produces the correct `SolanaError` for a `%s` error with a bigint instruction indices',
(transactionError, expectedCode) => {
const error = getSolanaErrorFromInstructionError(123n, transactionError, '1111', 42n);
expect(error).toEqual(
new SolanaError(expectedCode as SolanaErrorCode, {
index: 123,
innerIndex: 42,
responsibleProgramAddress: '1111',
}),
);
},
);
it.each(EXPECTED_ERROR_CODES)(
'produces the correct `SolanaError` for a pre-solana-transaction-error 3.0.0 `%s` error with a bigint index',
(transactionError, expectedCode) => {
const error = getSolanaErrorFromInstructionError(123n, transactionError);
expect(error).toEqual(new SolanaError(expectedCode as SolanaErrorCode, { index: 123 }));
},
);
it('produces the correct `SolanaError` for a `Custom` error', () => {
const error = getSolanaErrorFromInstructionError(123, { Custom: 789 }, '1111', 42n);
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM, {
code: 789,
index: 123,
innerIndex: 42,
responsibleProgramAddress: '1111',
}),
);
});
it('produces the correct `SolanaError` for a a pre-solana-transaction-error 3.0.0 `Custom` error', () => {
const error = getSolanaErrorFromInstructionError(123, { Custom: 789 });
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM, {
Expand All @@ -87,6 +124,17 @@ describe('getSolanaErrorFromInstructionError', () => {
);
});
it('produces the correct `SolanaError` for a `Custom` error with a bigint code', () => {
const error = getSolanaErrorFromInstructionError(123, { Custom: 789n }, '1111', 42);
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM, {
code: 789,
index: 123,
innerIndex: 42,
responsibleProgramAddress: '1111',
}),
);
});
it('produces the correct `SolanaError` for a pre-solana-transaction-error 3.0.0 `Custom` error with a bigint code', () => {
const error = getSolanaErrorFromInstructionError(123, { Custom: 789n });
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM, {
Expand All @@ -96,6 +144,17 @@ describe('getSolanaErrorFromInstructionError', () => {
);
});
it('produces the correct `SolanaError` for a `BorshIoError` error', () => {
const error = getSolanaErrorFromInstructionError(123, { BorshIoError: 'abc' }, '1111', 42);
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__BORSH_IO_ERROR, {
encodedData: 'abc',
index: 123,
innerIndex: 42,
responsibleProgramAddress: '1111',
}),
);
});
it('produces the correct `SolanaError` for a pre-solana-transaction-error 3.0.0 `BorshIoError` error', () => {
const error = getSolanaErrorFromInstructionError(123, { BorshIoError: 'abc' });
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__BORSH_IO_ERROR, {
Expand All @@ -105,6 +164,17 @@ describe('getSolanaErrorFromInstructionError', () => {
);
});
it("returns the unknown error when encountering an enum name that's missing from the map", () => {
const error = getSolanaErrorFromInstructionError(123, 'ThisDoesNotExist', '1111', 42);
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN, {
errorName: 'ThisDoesNotExist',
index: 123,
innerIndex: 42,
responsibleProgramAddress: '1111',
}),
);
});
it("returns the unknown pre-solana-transaction-error 3.0.0 error when encountering an enum name that's missing from the map", () => {
const error = getSolanaErrorFromInstructionError(123, 'ThisDoesNotExist');
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN, {
Expand All @@ -124,4 +194,17 @@ describe('getSolanaErrorFromInstructionError', () => {
}),
);
});
it("returns the unknown pre-solana-transaction-error 3.0.0 error when encountering an enum struct that's missing from the map", () => {
const expectedContext = {} as const;
const error = getSolanaErrorFromInstructionError(123, { ThisDoesNotExist: expectedContext }, '1111', 42);
expect(error).toEqual(
new SolanaError(SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN, {
errorName: 'ThisDoesNotExist',
index: 123,
innerIndex: 42,
instructionErrorContext: expectedContext,
responsibleProgramAddress: '1111',
}),
);
});
});
63 changes: 49 additions & 14 deletions packages/errors/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,17 @@ import {
} from './codes';
import { RpcSimulateTransactionResult } from './json-rpc-error';

type BasicInstructionErrorContext<T extends SolanaErrorCode> = Readonly<{ [P in T]: { index: number } }>;
type BasicInstructionErrorContext<T extends SolanaErrorCode> = Readonly<{
[P in T]:
| {
index: number;
innerIndex?: number;
responsibleProgramAddress: string;
}
| {
index: number;
};
}>;

type DefaultUnspecifiedErrorContextToUndefined<T> = {
[P in SolanaErrorCode]: P extends keyof T ? T[P] : undefined;
Expand Down Expand Up @@ -381,19 +391,44 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
minRange: number;
variant: number;
};
[SOLANA_ERROR__INSTRUCTION_ERROR__BORSH_IO_ERROR]: {
encodedData: string;
index: number;
};
[SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM]: {
code: number;
index: number;
};
[SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN]: {
errorName: string;
index: number;
instructionErrorContext?: unknown;
};
[SOLANA_ERROR__INSTRUCTION_ERROR__BORSH_IO_ERROR]:
| {
encodedData: string;
index: number;
innerIndex?: number;
responsibleProgramAddress: string;
}
// Pre `solana-transaction-error` 3.0.0
| {
encodedData: string;
index: number;
};
[SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM]:
| {
code: number;
index: number;
innerIndex?: number;
responsibleProgramAddress: string;
}
// Pre `solana-transaction-error` 3.0.0
| {
code: number;
index: number;
};
[SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN]:
| {
errorName: string;
index: number;
innerIndex?: number;
instructionErrorContext?: unknown;
responsibleProgramAddress: string;
}
// Pre `solana-transaction-error` 3.0.0
| {
errorName: string;
index: number;
instructionErrorContext?: unknown;
};
[SOLANA_ERROR__INSTRUCTION__EXPECTED_TO_HAVE_ACCOUNTS]: {
data?: ReadonlyUint8Array;
programAddress: string;
Expand Down
41 changes: 32 additions & 9 deletions packages/errors/src/instruction-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,35 +67,58 @@ const ORDERED_ERROR_NAMES = [
];

export function getSolanaErrorFromInstructionError(
/**
* The index of the instruction inside the transaction.
*/
index: bigint | number,
outerInstructionIndex: bigint | number,
instructionError: string | { [key: string]: unknown },
responsibleProgramAddress: string,
innerInstructionIndex?: bigint | number,
): SolanaError;
// Pre `solana-transaction-error` 3.0.0
export function getSolanaErrorFromInstructionError(
outerInstructionIndex: bigint | number,
instructionError: string | { [key: string]: unknown },
): SolanaError;
export function getSolanaErrorFromInstructionError(
outerInstructionIndex: bigint | number,
instructionError: string | { [key: string]: unknown },
responsibleProgramAddress?: string,
innerInstructionIndex?: bigint | number,
): SolanaError {
const numberIndex = Number(index);
const numberOuterInstructionIndex = Number(outerInstructionIndex);
const numberInnerInstructionIndex = innerInstructionIndex ? Number(innerInstructionIndex) : innerInstructionIndex;
return getSolanaErrorFromRpcError(
{
errorCodeBaseOffset: 4615001,
getErrorContext(errorCode, rpcErrorName, rpcErrorContext) {
const innerIndexProp = responsibleProgramAddress ? { innerIndex: numberInnerInstructionIndex } : null;
const responsibleProgramAddressProp = responsibleProgramAddress ? { responsibleProgramAddress } : null;
if (errorCode === SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN) {
return {
errorName: rpcErrorName,
index: numberIndex,
index: numberOuterInstructionIndex,
...innerIndexProp,
...(rpcErrorContext !== undefined ? { instructionErrorContext: rpcErrorContext } : null),
...responsibleProgramAddressProp,
};
} else if (errorCode === SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM) {
return {
code: Number(rpcErrorContext as bigint | number),
index: numberIndex,
index: numberOuterInstructionIndex,
...innerIndexProp,
...responsibleProgramAddressProp,
};
} else if (errorCode === SOLANA_ERROR__INSTRUCTION_ERROR__BORSH_IO_ERROR) {
return {
encodedData: rpcErrorContext as string,
index: numberIndex,
index: numberOuterInstructionIndex,
...innerIndexProp,
...responsibleProgramAddressProp,
};
}
return { index: numberIndex };
return {
index: numberOuterInstructionIndex,
...innerIndexProp,
...responsibleProgramAddressProp,
};
},
orderedErrorNames: ORDERED_ERROR_NAMES,
rpcEnumError: instructionError,
Expand Down
14 changes: 11 additions & 3 deletions packages/rpc-types/src/transaction-error.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Address } from '@solana/addresses';

type CustomProgramError = number;

// Keep synced with RPC source: https://github.com/anza-xyz/agave/blob/master/sdk/program/src/instruction.rs
Expand Down Expand Up @@ -57,8 +59,10 @@ type InstructionError =
| { BorshIoError: string }
| { Custom: CustomProgramError };

type InstructionIndex = number;
type AccountIndex = number;
type InnerInstructionIndex = Address;
type OuterInstructionIndex = number;
type ResponsibleProgramAddress = number;

// Keep synced with RPC source: https://github.com/anza-xyz/agave/blob/master/sdk/src/transaction/error.rs
export type TransactionError =
Expand Down Expand Up @@ -95,7 +99,11 @@ export type TransactionError =
| 'WouldExceedMaxAccountCostLimit'
| 'WouldExceedMaxBlockCostLimit'
| 'WouldExceedMaxVoteCostLimit'
| { DuplicateInstruction: InstructionIndex }
| { InstructionError: [InstructionIndex, InstructionError] }
| {
InstructionError:
| [OuterInstructionIndex, InstructionError, null, null] // Pre `solana-transaction-error` 3.0.0
| [OuterInstructionIndex, InstructionError, ResponsibleProgramAddress, InnerInstructionIndex | null];
}
| { DuplicateInstruction: OuterInstructionIndex }
| { InsufficientFundsForRent: { account_index: AccountIndex } }
| { ProgramExecutionTemporarilyRestricted: { account_index: AccountIndex } };