Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Convert all errors in @solana/rpc-types to coded exceptions #2226

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
12 changes: 12 additions & 0 deletions packages/errors/src/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ export const SOLANA_ERROR__CODECS_FIXED_NULLABLE_WITH_VARIABLE_SIZE_PREFIX = 44
export const SOLANA_ERROR__CODECS_CODEC_REQUIRES_FIXED_SIZE = 45 as const;
export const SOLANA_ERROR__CODECS_NUMBER_OUT_OF_RANGE = 46 as const;
export const SOLANA_ERROR__CODECS_INVALID_STRING_FOR_BASE = 47 as const;
export const SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE = 48 as const;
export const SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE = 49 as const;
export const SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE = 50 as const;
export const SOLANA_ERROR__MALFORMED_BIGINT_STRING = 51 as const;
export const SOLANA_ERROR__MALFORMED_NUMBER_STRING = 52 as const;
export const SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE = 53 as const;
export const SOLANA_ERROR__EXPECTED_INSTRUCTION_TO_HAVE_ACCOUNTS = 70 as const;
export const SOLANA_ERROR__EXPECTED_INSTRUCTION_TO_HAVE_DATA = 71 as const;
// Reserve error codes starting with [4615000-4615999] for the Rust enum `InstructionError`
Expand Down Expand Up @@ -216,6 +222,12 @@ export type SolanaErrorCode =
| typeof SOLANA_ERROR__INVALID_SEEDS_POINT_ON_CURVE
| typeof SOLANA_ERROR__COULD_NOT_FIND_VIABLE_PDA_BUMP_SEED
| typeof SOLANA_ERROR__PROGRAM_ADDRESS_ENDS_WITH_PDA_MARKER
| typeof SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE
| typeof SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE
| typeof SOLANA_ERROR__MALFORMED_BIGINT_STRING
| typeof SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE
| typeof SOLANA_ERROR__MALFORMED_NUMBER_STRING
| typeof SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE
| typeof SOLANA_ERROR__SUBTLE_CRYPTO_MISSING
| typeof SOLANA_ERROR__SUBTLE_CRYPTO_DIGEST_MISSING
| typeof SOLANA_ERROR__SUBTLE_CRYPTO_ED25519_ALGORITHM_MISSING
Expand Down
20 changes: 20 additions & 0 deletions packages/errors/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
SOLANA_ERROR__ADDRESS_BYTE_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__ADDRESS_STRING_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED,
SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__CODECS_CANNOT_DECODE_EMPTY_BYTE_ARRAY,
SOLANA_ERROR__CODECS_CODEC_REQUIRES_FIXED_SIZE,
SOLANA_ERROR__CODECS_ENUM_DISCRIMINATOR_OUT_OF_RANGE,
Expand Down Expand Up @@ -74,6 +76,8 @@ import {
SOLANA_ERROR__INSTRUCTION_ERROR_UNSUPPORTED_PROGRAM_ID,
SOLANA_ERROR__INSTRUCTION_ERROR_UNSUPPORTED_SYSVAR,
SOLANA_ERROR__INVALID_KEYPAIR_BYTES,
SOLANA_ERROR__MALFORMED_BIGINT_STRING,
SOLANA_ERROR__MALFORMED_NUMBER_STRING,
SOLANA_ERROR__MAX_NUMBER_OF_PDA_SEEDS_EXCEEDED,
SOLANA_ERROR__MAX_PDA_SEED_LENGTH_EXCEEDED,
SOLANA_ERROR__MULTIPLE_ACCOUNTS_NOT_FOUND,
Expand All @@ -92,6 +96,7 @@ import {
SOLANA_ERROR__SIGNER_EXPECTED_TRANSACTION_PARTIAL_SIGNER,
SOLANA_ERROR__SIGNER_EXPECTED_TRANSACTION_SENDING_SIGNER,
SOLANA_ERROR__SIGNER_EXPECTED_TRANSACTION_SIGNER,
SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION_ERROR_DUPLICATE_INSTRUCTION,
SOLANA_ERROR__TRANSACTION_ERROR_INSUFFICIENT_FUNDS_FOR_RENT,
SOLANA_ERROR__TRANSACTION_ERROR_PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED,
Expand Down Expand Up @@ -187,6 +192,12 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
[SOLANA_ERROR__ADDRESS_STRING_LENGTH_OUT_OF_RANGE]: {
actualLength: number;
};
[SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE]: {
actualLength: number;
};
[SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE]: {
actualLength: number;
};
[SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED]: {
currentBlockHeight: bigint;
lastValidBlockHeight: bigint;
Expand Down Expand Up @@ -263,6 +274,9 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
[SOLANA_ERROR__INVALID_KEYPAIR_BYTES]: {
byteLength: number;
};
[SOLANA_ERROR__MALFORMED_BIGINT_STRING]: {
value: string;
};
[SOLANA_ERROR__MAX_NUMBER_OF_PDA_SEEDS_EXCEEDED]: {
actual: number;
maxSeeds: number;
Expand All @@ -288,6 +302,9 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
[SOLANA_ERROR__NOT_ALL_ACCOUNTS_DECODED]: {
addresses: string[];
};
[SOLANA_ERROR__MALFORMED_NUMBER_STRING]: {
value: string;
};
[SOLANA_ERROR__PROGRAM_DERIVED_ADDRESS_BUMP_SEED_OUT_OF_RANGE]: {
bump: number;
};
Expand Down Expand Up @@ -326,6 +343,9 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
[SOLANA_ERROR__SIGNER_EXPECTED_TRANSACTION_SIGNER]: {
address: string;
};
[SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE]: {
value: number;
};
[SOLANA_ERROR__TRANSACTION_ERROR_DUPLICATE_INSTRUCTION]: {
index: number;
};
Expand Down
14 changes: 14 additions & 0 deletions packages/errors/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
SOLANA_ERROR__ADDRESS_BYTE_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__ADDRESS_STRING_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED,
SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__CODECS_CANNOT_DECODE_EMPTY_BYTE_ARRAY,
SOLANA_ERROR__CODECS_CANNOT_REVERSE_CODEC_OF_VARIABLE_SIZE,
SOLANA_ERROR__CODECS_CODEC_REQUIRES_FIXED_SIZE,
Expand Down Expand Up @@ -82,6 +84,9 @@ import {
SOLANA_ERROR__INSTRUCTION_ERROR_UNSUPPORTED_SYSVAR,
SOLANA_ERROR__INVALID_KEYPAIR_BYTES,
SOLANA_ERROR__INVALID_SEEDS_POINT_ON_CURVE,
SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE,
SOLANA_ERROR__MALFORMED_BIGINT_STRING,
SOLANA_ERROR__MALFORMED_NUMBER_STRING,
SOLANA_ERROR__MALFORMED_PROGRAM_DERIVED_ADDRESS,
SOLANA_ERROR__MAX_NUMBER_OF_PDA_SEEDS_EXCEEDED,
SOLANA_ERROR__MAX_PDA_SEED_LENGTH_EXCEEDED,
Expand Down Expand Up @@ -112,6 +117,7 @@ import {
SOLANA_ERROR__SUBTLE_CRYPTO_MISSING,
SOLANA_ERROR__SUBTLE_CRYPTO_SIGN_FUNCTION_MISSING,
SOLANA_ERROR__SUBTLE_CRYPTO_VERIFY_FUNCTION_MISSING,
SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE,
SOLANA_ERROR__TRANSACTION_ERROR_ACCOUNT_BORROW_OUTSTANDING,
SOLANA_ERROR__TRANSACTION_ERROR_ACCOUNT_IN_USE,
SOLANA_ERROR__TRANSACTION_ERROR_ACCOUNT_LOADED_TWICE,
Expand Down Expand Up @@ -184,6 +190,10 @@ export const SolanaErrorMessages: Readonly<{
'Expected base58 encoded address to decode to a byte array of length 32. Actual length: $actualLength.',
[SOLANA_ERROR__ADDRESS_STRING_LENGTH_OUT_OF_RANGE]:
'Expected base58-encoded address string of length in the range [32, 44]. Actual length: $actualLength.',
[SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE]:
'Expected base58 encoded blockhash to decode to a byte array of length 32. Actual length: $actualLength.',
[SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE]:
'Expected base58-encoded blockash string of length in the range [32, 44]. Actual length: $actualLength.',
[SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED]:
'The network has progressed past the last block for which this transaction could have been committed.',
[SOLANA_ERROR__CODECS_CANNOT_DECODE_EMPTY_BYTE_ARRAY]: 'Codec [$codecDescription] cannot decode empty byte arrays.',
Expand Down Expand Up @@ -292,6 +302,9 @@ export const SolanaErrorMessages: Readonly<{
[SOLANA_ERROR__INSTRUCTION_ERROR_UNSUPPORTED_SYSVAR]: 'Unsupported sysvar',
[SOLANA_ERROR__INVALID_KEYPAIR_BYTES]: 'Key pair bytes must be of length 64, got $byteLength.',
[SOLANA_ERROR__INVALID_SEEDS_POINT_ON_CURVE]: 'Invalid seeds; point must fall off the Ed25519 curve.',
[SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE]: 'Lamports value must be in the range [0, 2e64-1]',
[SOLANA_ERROR__MALFORMED_BIGINT_STRING]: '`$value` cannot be parsed as a `BigInt`',
[SOLANA_ERROR__MALFORMED_NUMBER_STRING]: '`$value` cannot be parsed as a `Number`',
[SOLANA_ERROR__MALFORMED_PROGRAM_DERIVED_ADDRESS]:
'Expected given program derived address to have the following format: [Address, ProgramDerivedAddressBump].',
[SOLANA_ERROR__MAX_NUMBER_OF_PDA_SEEDS_EXCEEDED]:
Expand Down Expand Up @@ -351,6 +364,7 @@ export const SolanaErrorMessages: Readonly<{
'here: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts.',
[SOLANA_ERROR__SUBTLE_CRYPTO_SIGN_FUNCTION_MISSING]: 'No signing implementation could be found.',
[SOLANA_ERROR__SUBTLE_CRYPTO_VERIFY_FUNCTION_MISSING]: 'No key export implementation could be found.',
[SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE]: 'Timestamp value must be in the range [-8.64e15, 8.64e15]. `$value` given',
[SOLANA_ERROR__TRANSACTION_ERROR_ACCOUNT_BORROW_OUTSTANDING]:
'Transaction processing left an account with an outstanding borrowed reference',
[SOLANA_ERROR__TRANSACTION_ERROR_ACCOUNT_IN_USE]: 'Account in use',
Expand Down
3 changes: 2 additions & 1 deletion packages/rpc-types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
],
"dependencies": {
"@solana/addresses": "workspace:*",
"@solana/codecs-strings": "workspace:*"
"@solana/codecs-strings": "workspace:*",
"@solana/errors": "workspace:*"
},
"devDependencies": {
"@solana/build-scripts": "workspace:*",
Expand Down
43 changes: 35 additions & 8 deletions packages/rpc-types/src/__tests__/blockhash-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { VariableSizeEncoder } from '@solana/codecs-core';
import { getBase58Encoder } from '@solana/codecs-strings';
import {
SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE,
SOLANA_ERROR__CODECS_INVALID_STRING_FOR_BASE,
SolanaError,
} from '@solana/errors';

jest.mock('@solana/codecs-strings', () => ({
...jest.requireActual('@solana/codecs-strings'),
Expand Down Expand Up @@ -30,17 +36,38 @@ describe('assertIsBlockhash()', () => {
});

it('throws when supplied a non-base58 string', () => {
const badBlockhash = 'not-a-base-58-encoded-string-but-nice-try';
expect(() => {
assertIsBlockhash('not-a-base-58-encoded-string');
}).toThrow();
assertIsBlockhash(badBlockhash);
}).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_INVALID_STRING_FOR_BASE, {
alphabet: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
base: 58,
value: badBlockhash,
}),
);
});
it('throws when the decoded byte array has a length other than 32 bytes', () => {
it.each([31, 45])('throws when the encoded string is of length %s', actualLength => {
const badBlockhash = '1'.repeat(actualLength);
expect(() => {
assertIsBlockhash(
// 31 bytes [128, ..., 128]
'2xea9jWJ9eca3dFiefTeSPP85c6qXqunCqL2h2JNffM',
);
}).toThrow();
assertIsBlockhash(badBlockhash);
}).toThrow(
new SolanaError(SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE, {
actualLength,
}),
);
});
it.each([
[31, 'tVojvhToWjQ8Xvo4UPx2Xz9eRy7auyYMmZBjc2XfN'],
[33, 'JJEfe6DcPM2ziB2vfUWDV6aHVerXRGkv3TcyvJUNGHZz'],
])('throws when the decoded byte array has a length of %s bytes', (actualLength, badBlockhash) => {
expect(() => {
assertIsBlockhash(badBlockhash);
}).toThrow(
new SolanaError(SOLANA_ERROR__BLOCKHASH_BYTE_LENGTH_OUT_OF_RANGE, {
actualLength,
}),
);
});
it('does not throw when supplied a base-58 encoded hash', () => {
expect(() => {
Expand Down
30 changes: 25 additions & 5 deletions packages/rpc-types/src/__tests__/coercions-test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import {
SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE,
SOLANA_ERROR__MALFORMED_BIGINT_STRING,
SOLANA_ERROR__MALFORMED_NUMBER_STRING,
SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE,
SolanaError,
} from '@solana/errors';

import { lamports, LamportsUnsafeBeyond2Pow53Minus1 } from '../lamports';
import { StringifiedBigInt, stringifiedBigInt } from '../stringified-bigint';
import { StringifiedNumber, stringifiedNumber } from '../stringified-number';
Expand All @@ -12,7 +20,7 @@ describe('coercions', () => {
});
it('throws on invalid `LamportsUnsafeBeyond2Pow53Minus1`', () => {
const thisThrows = () => lamports(-5n);
expect(thisThrows).toThrow('Input for 64-bit unsigned integer cannot be negative');
expect(thisThrows).toThrow(new SolanaError(SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE));
});
});
describe('stringifiedBigInt', () => {
Expand All @@ -23,7 +31,11 @@ describe('coercions', () => {
});
it('throws on invalid `StringifiedBigInt`', () => {
const thisThrows = () => stringifiedBigInt('test');
expect(thisThrows).toThrow('`test` cannot be parsed as a BigInt');
expect(thisThrows).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_BIGINT_STRING, {
value: 'test',
}),
);
});
});
describe('stringifiedNumber', () => {
Expand All @@ -34,7 +46,11 @@ describe('coercions', () => {
});
it('throws on invalid `StringifiedNumber`', () => {
const thisThrows = () => stringifiedNumber('test');
expect(thisThrows).toThrow('`test` cannot be parsed as a Number');
expect(thisThrows).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_NUMBER_STRING, {
value: 'test',
}),
);
});
});
describe('unixTimestamp', () => {
Expand All @@ -43,9 +59,13 @@ describe('coercions', () => {
const coerced = unixTimestamp(1234);
expect(coerced).toBe(raw);
});
it('throws on invalid `UnixTimestamp`', () => {
it('throws on an out-of-range `UnixTimestamp`', () => {
const thisThrows = () => unixTimestamp(8.75e15);
expect(thisThrows).toThrow('`8750000000000000` is not a timestamp');
expect(thisThrows).toThrow(
new SolanaError(SOLANA_ERROR__TIMESTAMP_OUT_OF_RANGE, {
value: 8.75e15,
}),
);
});
});
});
8 changes: 5 additions & 3 deletions packages/rpc-types/src/__tests__/lamports-test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE, SolanaError } from '@solana/errors';

import { assertIsLamports } from '../lamports';

describe('assertIsLamports()', () => {
it('throws when supplied a negative number', () => {
expect(() => {
assertIsLamports(-1n);
}).toThrow();
}).toThrow(new SolanaError(SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE));
expect(() => {
assertIsLamports(-1000n);
}).toThrow();
}).toThrow(new SolanaError(SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE));
});
it('throws when supplied a too large number', () => {
expect(() => {
assertIsLamports(2n ** 64n);
}).toThrow();
}).toThrow(new SolanaError(SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE));
});
it('does not throw when supplied zero lamports', () => {
expect(() => {
Expand Down
26 changes: 22 additions & 4 deletions packages/rpc-types/src/__tests__/stringified-bigint-test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import { SOLANA_ERROR__MALFORMED_BIGINT_STRING, SolanaError } from '@solana/errors';

import { assertIsStringifiedBigInt } from '../stringified-bigint';

describe('assertIsStringifiedBigInt()', () => {
it("throws when supplied a string that can't parse as a number", () => {
expect(() => {
assertIsStringifiedBigInt('abc');
}).toThrow();
}).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_BIGINT_STRING, {
value: 'abc',
}),
);
expect(() => {
assertIsStringifiedBigInt('123a');
}).toThrow();
}).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_BIGINT_STRING, {
value: '123a',
}),
);
});
it("throws when supplied a string that can't parse as an integer", () => {
expect(() => {
assertIsStringifiedBigInt('123.0');
}).toThrow();
}).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_BIGINT_STRING, {
value: '123.0',
}),
);
expect(() => {
assertIsStringifiedBigInt('123.5');
}).toThrow();
}).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_BIGINT_STRING, {
value: '123.5',
}),
);
});
it('does not throw when supplied a string that parses as an integer', () => {
expect(() => {
Expand Down
14 changes: 12 additions & 2 deletions packages/rpc-types/src/__tests__/stringified-number-test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { SOLANA_ERROR__MALFORMED_NUMBER_STRING, SolanaError } from '@solana/errors';

import { assertIsStringifiedNumber } from '../stringified-number';

describe('assertIsStringifiedNumber()', () => {
it("throws when supplied a string that can't parse as a number", () => {
expect(() => {
assertIsStringifiedNumber('abc');
}).toThrow();
}).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_NUMBER_STRING, {
value: 'abc',
}),
);
expect(() => {
assertIsStringifiedNumber('123a');
}).toThrow();
}).toThrow(
new SolanaError(SOLANA_ERROR__MALFORMED_NUMBER_STRING, {
value: '123a',
}),
);
});
it('does not throw when supplied a string that parses as a float', () => {
expect(() => {
Expand Down
Loading
Loading