Skip to content

Commit

Permalink
Convert all errors in @solana/rpc-types to coded exceptions (#2226)
Browse files Browse the repository at this point in the history
Addresses #2118.
  • Loading branch information
steveluscher committed Mar 1, 2024
1 parent 613053d commit 94fee67
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 61 deletions.
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

0 comments on commit 94fee67

Please sign in to comment.