Skip to content

Commit

Permalink
refactor(experimental): errors: compat package (#2202)
Browse files Browse the repository at this point in the history
Adds custom `SolanaError` throws to the `@solana/compat` package.
  • Loading branch information
buffalojoec authored Feb 29, 2024
1 parent 91a360d commit a71a2db
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/compat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"node": ">=17.4"
},
"dependencies": {
"@solana/errors": "workspace:*",
"@solana/functional": "workspace:*",
"@solana/instructions": "workspace:*",
"@solana/keys": "workspace:*",
Expand Down
21 changes: 17 additions & 4 deletions packages/compat/src/__tests__/transaction-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Buffer } from 'node:buffer';

import { Address } from '@solana/addresses';
import {
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS,
SolanaError,
} from '@solana/errors';
import { AccountRole, IInstruction } from '@solana/instructions';
import { SignatureBytes } from '@solana/keys';
import { ITransactionWithSignatures, Nonce } from '@solana/transactions';
Expand Down Expand Up @@ -639,7 +644,7 @@ describe('fromVersionedTransactionWithDurableNonce', () => {

expect(() => {
fromVersionedTransactionWithDurableNonce(oldTransaction);
}).toThrow('transaction with no instructions cannot be durable nonce transaction');
}).toThrow(new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS));
});

it('throws for a transaction with one instruction which is not advance nonce', () => {
Expand All @@ -660,7 +665,11 @@ describe('fromVersionedTransactionWithDurableNonce', () => {

expect(() => {
fromVersionedTransactionWithDurableNonce(oldTransaction);
}).toThrow('transaction first instruction is not advance nonce account instruction');
}).toThrow(
new SolanaError(
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
),
);
});

it('converts a transaction with one instruction which is advance nonce', () => {
Expand Down Expand Up @@ -871,7 +880,7 @@ describe('fromVersionedTransactionWithDurableNonce', () => {

expect(() => {
fromVersionedTransactionWithDurableNonce(oldTransaction);
}).toThrow('transaction with no instructions cannot be durable nonce transaction');
}).toThrow(new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS));
});

it('throws for a transaction with one instruction which is not advance nonce', () => {
Expand All @@ -892,7 +901,11 @@ describe('fromVersionedTransactionWithDurableNonce', () => {

expect(() => {
fromVersionedTransactionWithDurableNonce(oldTransaction);
}).toThrow('transaction first instruction is not advance nonce account instruction');
}).toThrow(
new SolanaError(
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
),
);
});

it('converts a transaction with one instruction which is advance nonce', () => {
Expand Down
33 changes: 21 additions & 12 deletions packages/compat/src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { type Address, assertIsAddress } from '@solana/addresses';
import {
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS,
SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS,
SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER,
SolanaError,
} from '@solana/errors';
import { pipe } from '@solana/functional';
import type { IAccountMeta, IInstruction } from '@solana/instructions';
import { AccountRole } from '@solana/instructions';
Expand Down Expand Up @@ -33,8 +40,9 @@ function convertAccount(
): IAccountMeta {
const accountPublicKey = accountKeys.get(accountIndex);
if (!accountPublicKey) {
// TODO coded error
throw new Error(`Could not find account address at index ${accountIndex}`);
throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS, {
index: accountIndex,
});
}
const isSigner = message.isAccountSigner(accountIndex);
const isWritable = message.isAccountWritable(accountIndex);
Expand All @@ -60,8 +68,9 @@ function convertInstruction(
): IInstruction {
const programAddressPublicKey = accountKeys.get(instruction.programIdIndex);
if (!programAddressPublicKey) {
// TODO coded error
throw new Error(`Could not find program address at index ${instruction.programIdIndex}`);
throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS, {
index: instruction.programIdIndex,
});
}

const accounts = instruction.accountKeyIndexes.map(accountIndex =>
Expand Down Expand Up @@ -99,15 +108,16 @@ export function fromVersionedTransactionWithBlockhash(
// - will need to convert account instructions to `IAccountLookupMeta` when appropriate
if (transaction.message.addressTableLookups.length > 0) {
// TODO coded error
// This should probably not be a `SolanaError`, since we need to add
// this functionality.
throw new Error('Cannot convert transaction with addressTableLookups');
}

const accountKeys = transaction.message.getAccountKeys();

// Fee payer is first account
const feePayer = accountKeys.staticAccountKeys[0];
// TODO: coded error
if (!feePayer) throw new Error('No fee payer set in VersionedTransaction');
if (!feePayer) throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER);

const blockhashLifetime = {
blockhash: transaction.message.recentBlockhash as Blockhash,
Expand Down Expand Up @@ -140,29 +150,28 @@ export function fromVersionedTransactionWithDurableNonce(
// - will need to convert account instructions to `IAccountLookupMeta` when appropriate
if (transaction.message.addressTableLookups.length > 0) {
// TODO coded error
// This should probably not be a `SolanaError`, since we need to add
// this functionality.
throw new Error('Cannot convert transaction with addressTableLookups');
}

const accountKeys = transaction.message.getAccountKeys();

// Fee payer is first account
const feePayer = accountKeys.staticAccountKeys[0];
// TODO: coded error
if (!feePayer) throw new Error('No fee payer set in VersionedTransaction');
if (!feePayer) throw new SolanaError(SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER);

const instructions = transaction.message.compiledInstructions.map(instruction =>
convertInstruction(transaction.message, accountKeys, instruction),
);

// Check first instruction is durable nonce + extract params
if (instructions.length === 0) {
// TODO: coded error
throw new Error('transaction with no instructions cannot be durable nonce transaction');
throw new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS);
}

if (!isAdvanceNonceAccountInstruction(instructions[0])) {
// TODO: coded error
throw new Error('transaction first instruction is not advance nonce account instruction');
throw new SolanaError(SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE);
}

// We know these accounts are defined because we checked `isAdvanceNonceAccountInstruction`
Expand Down
16 changes: 12 additions & 4 deletions packages/errors/src/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ export const SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_A
export const SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING = 5663009 as const;
export const SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES = 5663010 as const;
export const SOLANA_ERROR__TRANSACTION_SIGNATURE_NOT_COMPUTABLE = 5663011 as const;
export const SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS = 5663012 as const;
export const SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER = 5663013 as const;
export const SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS = 5663014 as const;
export const SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE = 5663015 as const;
// Reserve error codes starting with [7050000-7050999] for the Rust enum `TransactionError`
export const SOLANA_ERROR__TRANSACTION_ERROR_UNKNOWN = 7050000 as const;
export const SOLANA_ERROR__TRANSACTION_ERROR_ACCOUNT_IN_USE = 7050001 as const;
Expand Down Expand Up @@ -314,12 +318,16 @@ export type SolanaErrorCode =
| typeof SOLANA_ERROR__TRANSACTION_ERROR_RESANITIZATION_NEEDED
| typeof SOLANA_ERROR__TRANSACTION_ERROR_PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED
| typeof SOLANA_ERROR__TRANSACTION_ERROR_UNBALANCED_TRANSACTION
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE
| typeof SOLANA_ERROR__TRANSACTION_EXPECTED_BLOCKHASH_LIFETIME
| typeof SOLANA_ERROR__TRANSACTION_EXPECTED_NONCE_LIFETIME
| typeof SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_CONTENTS_MISSING
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND
| typeof SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING;
| typeof SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE
| typeof SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES
| typeof SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE
| typeof SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS
| typeof SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER
| typeof SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE;
4 changes: 4 additions & 0 deletions packages/errors/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import {
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES,
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE,
SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS,
SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES,
SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE,
SolanaErrorCode,
Expand Down Expand Up @@ -317,6 +318,9 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
[SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE]: {
programAddress: string;
};
[SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS]: {
index: number;
};
[SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES]: {
addresses: string[];
};
Expand Down
10 changes: 10 additions & 0 deletions packages/errors/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,12 @@ import {
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_ADDRESS_LOOKUP_TABLE_INDEX_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING,
SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND,
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE,
SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS,
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES,
SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE,
SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS,
SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER,
SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES,
SOLANA_ERROR__TRANSACTION_SIGNATURE_NOT_COMPUTABLE,
SOLANA_ERROR__TRANSACTION_VERSION_NUMBER_OUT_OF_RANGE,
Expand Down Expand Up @@ -377,12 +381,18 @@ export const SolanaErrorMessages: Readonly<{
[SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_FEE_PAYER_MISSING]: 'No fee payer set in CompiledTransaction',
[SOLANA_ERROR__TRANSACTION_FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND]:
'Could not find program address at index $index',
[SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_FIRST_INSTRUCTION_NOT_ADVANCE_NONCE]:
'Transaction first instruction is not advance nonce account instruction.',
[SOLANA_ERROR__TRANSACTION_INVALID_NONCE_TRANSACTION_NO_INSTRUCTIONS]:
'Transaction with no instructions cannot be durable nonce transaction.',
[SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_CANNOT_PAY_FEES]:
'This transaction includes an address (`$programAddress`) which is both ' +
'invoked and set as the fee payer. Program addresses may not pay fees',
[SOLANA_ERROR__TRANSACTION_INVOKED_PROGRAMS_MUST_NOT_BE_WRITABLE]:
'This transaction includes an address (`$programAddress`) which is both invoked and ' +
'marked writable. Program addresses may not be writable',
[SOLANA_ERROR__TRANSACTION_MISSING_ADDRESS]: 'Transaction is missing an address at index: $index.',
[SOLANA_ERROR__TRANSACTION_MISSING_FEE_PAYER]: 'Transaction is missing a fee payer.',
[SOLANA_ERROR__TRANSACTION_MISSING_SIGNATURES]: 'Transaction is missing signatures for addresses: $addresses.',
[SOLANA_ERROR__TRANSACTION_SIGNATURE_NOT_COMPUTABLE]:
"Could not determine this transaction's signature. Make sure that the transaction has " +
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a71a2db

Please sign in to comment.