From d9c019d1bbfa00cb166b191607a1feead19f0b35 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Wed, 3 Apr 2024 00:53:01 +0100 Subject: [PATCH] refactor(experimental): add addCodecSentinel to @solana/codecs-core --- .changeset/gorgeous-gorillas-sniff.md | 16 ++ packages/codecs-core/README.md | 29 ++++ .../src/__tests__/add-codec-sentinel-test.ts | 78 ++++++++++ .../add-codec-sentinel-typetest.ts | 35 +++++ .../codecs-core/src/add-codec-sentinel.ts | 143 ++++++++++++++++++ packages/codecs-core/src/index.ts | 1 + .../src/__tests__/struct-test.ts | 13 +- .../src/__tests__/tuple-test.ts | 13 +- packages/codecs/README.md | 1 + packages/errors/src/codes.ts | 4 + packages/errors/src/context.ts | 14 ++ packages/errors/src/messages.ts | 6 + 12 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 .changeset/gorgeous-gorillas-sniff.md create mode 100644 packages/codecs-core/src/__tests__/add-codec-sentinel-test.ts create mode 100644 packages/codecs-core/src/__typetests__/add-codec-sentinel-typetest.ts create mode 100644 packages/codecs-core/src/add-codec-sentinel.ts diff --git a/.changeset/gorgeous-gorillas-sniff.md b/.changeset/gorgeous-gorillas-sniff.md new file mode 100644 index 000000000000..66113e79a0dc --- /dev/null +++ b/.changeset/gorgeous-gorillas-sniff.md @@ -0,0 +1,16 @@ +--- +'@solana/codecs-core': patch +'@solana/errors': patch +--- + +Added new `addCodecSentinel` primitive + +The `addCodecSentinel` function provides a new way of delimiting the size of a codec. It allows us to add a sentinel to the end of the encoded data and to read until that sentinel is found when decoding. It accepts any codec and a `Uint8Array` sentinel responsible for delimiting the encoded data. + +```ts +const codec = addCodecSentinel(getUtf8Codec(), new Uint8Array([255, 255])); +codec.encode('hello'); +// 0x68656c6c6fffff +// | └-- Our sentinel. +// └-- Our encoded string. +``` \ No newline at end of file diff --git a/packages/codecs-core/README.md b/packages/codecs-core/README.md index 07803d09495f..41c0ce8c6f54 100644 --- a/packages/codecs-core/README.md +++ b/packages/codecs-core/README.md @@ -385,6 +385,35 @@ const getU32Base58Decoder = () => addDecoderSizePrefix(getBase58Decoder(), getU3 const getU32Base58Codec = () => combineCodec(getU32Base58Encoder(), getU32Base58Decoder()); ``` +## Adding sentinels to codecs + +Another way of delimiting the size of a codec is to use sentinels. The `addCodecSentinel` function allows us to add a sentinel to the end of the encoded data and to read until that sentinel is found when decoding. It accepts any codec and a `Uint8Array` sentinel responsible for delimiting the encoded data. + +```ts +const codec = addCodecSentinel(getUtf8Codec(), new Uint8Array([255, 255])); +codec.encode('hello'); +// 0x68656c6c6fffff +// | └-- Our sentinel. +// └-- Our encoded string. +``` + +Note that the sentinel _must not_ be present in the encoded data and _must_ be present in the decoded data for this to work. If this is not the case, dedicated errors will be thrown. + +```ts +const sentinel = new Uint8Array([108, 108]); // 'll' +const codec = addCodecSentinel(getUtf8Codec(), sentinel); + +codec.encode('hello'); // Throws: sentinel is in encoded data. +codec.decode(new Uint8Array([1, 2, 3])); // Throws: sentinel missing in decoded data. +``` + +Separate `addEncoderSentinel` and `addDecoderSentinel` functions are also available. + +```ts +const bytes = addEncoderSentinel(getUtf8Encoder(), sentinel).encode('hello'); +const value = addDecoderSentinel(getUtf8Decoder(), sentinel).decode(bytes); +``` + ## Adjusting the size of codecs The `resizeCodec` helper re-defines the size of a given codec by accepting a function that takes the current size of the codec and returns a new size. This works for both fixed-size and variable-size codecs. diff --git a/packages/codecs-core/src/__tests__/add-codec-sentinel-test.ts b/packages/codecs-core/src/__tests__/add-codec-sentinel-test.ts new file mode 100644 index 000000000000..84c3d22b1065 --- /dev/null +++ b/packages/codecs-core/src/__tests__/add-codec-sentinel-test.ts @@ -0,0 +1,78 @@ +import { + SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL, + SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES, + SolanaError, +} from '@solana/errors'; + +import { addCodecSentinel } from '../add-codec-sentinel'; +import { b, getMockCodec } from './__setup__'; + +describe('addCodecSentinel', () => { + it('encodes the sentinel after the main content', () => { + const mockCodec = getMockCodec(); + mockCodec.getSizeFromValue.mockReturnValue(10); + mockCodec.write.mockImplementation((_, bytes, offset) => { + bytes.set(b('68656c6c6f776f726c64'), offset); + return offset + 10; + }); + const codec = addCodecSentinel(mockCodec, b('ff')); + + expect(codec.encode('helloworld')).toStrictEqual(b('68656c6c6f776f726c64ff')); + expect(mockCodec.write).toHaveBeenCalledWith('helloworld', expect.any(Uint8Array), 0); + }); + + it('decodes until the first occurence of the sentinel is found', () => { + const mockCodec = getMockCodec(); + mockCodec.read.mockReturnValue(['helloworld', 10]); + const codec = addCodecSentinel(mockCodec, b('ff')); + + expect(codec.decode(b('68656c6c6f776f726c64ff0000'))).toBe('helloworld'); + expect(mockCodec.read).toHaveBeenCalledWith(b('68656c6c6f776f726c64'), 0); + }); + + it('fails if the encoded bytes contain the sentinel', () => { + const mockCodec = getMockCodec(); + mockCodec.getSizeFromValue.mockReturnValue(10); + mockCodec.write.mockImplementation((_, bytes, offset) => { + bytes.set(b('68656c6c6f776f726cff'), offset); + return offset + 10; + }); + const codec = addCodecSentinel(mockCodec, b('ff')); + + expect(() => codec.encode('helloworld')).toThrow( + new SolanaError(SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL, { + encodedBytes: b('68656c6c6f776f726cff'), + hexEncodedBytes: '68656c6c6f776f726cff', + hexSentinel: 'ff', + sentinel: b('ff'), + }), + ); + }); + + it('fails if the decoded bytes do not contain the sentinel', () => { + const mockCodec = getMockCodec(); + const codec = addCodecSentinel(mockCodec, b('ff')); + + expect(() => codec.decode(b('68656c6c6f776f726c64000000'))).toThrow( + new SolanaError(SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES, { + decodedBytes: b('68656c6c6f776f726c64000000'), + hexDecodedBytes: '68656c6c6f776f726c64000000', + hexSentinel: 'ff', + sentinel: b('ff'), + }), + ); + }); + + it('returns the correct fixed size', () => { + const mockCodec = getMockCodec({ size: 10 }); + const codec = addCodecSentinel(mockCodec, b('ffff')); + expect(codec.fixedSize).toBe(12); + }); + + it('returns the correct variable size', () => { + const mockCodec = getMockCodec(); + mockCodec.getSizeFromValue.mockReturnValueOnce(10); + const codec = addCodecSentinel(mockCodec, b('ffff')); + expect(codec.getSizeFromValue('helloworld')).toBe(12); + }); +}); diff --git a/packages/codecs-core/src/__typetests__/add-codec-sentinel-typetest.ts b/packages/codecs-core/src/__typetests__/add-codec-sentinel-typetest.ts new file mode 100644 index 000000000000..743572b003d8 --- /dev/null +++ b/packages/codecs-core/src/__typetests__/add-codec-sentinel-typetest.ts @@ -0,0 +1,35 @@ +import { addCodecSentinel, addDecoderSentinel, addEncoderSentinel } from '../add-codec-sentinel'; +import { + Codec, + Decoder, + Encoder, + FixedSizeCodec, + FixedSizeDecoder, + FixedSizeEncoder, + VariableSizeCodec, + VariableSizeDecoder, + VariableSizeEncoder, +} from '../codec'; + +const sentinel = {} as Uint8Array; + +{ + // [addEncoderSentinel]: It knows if the encoder is fixed size or variable size. + addEncoderSentinel({} as FixedSizeEncoder, sentinel) satisfies FixedSizeEncoder; + addEncoderSentinel({} as VariableSizeEncoder, sentinel) satisfies VariableSizeEncoder; + addEncoderSentinel({} as Encoder, sentinel) satisfies VariableSizeEncoder; +} + +{ + // [addDecoderSentinel]: It knows if the decoder is fixed size or variable size. + addDecoderSentinel({} as FixedSizeDecoder, sentinel) satisfies FixedSizeDecoder; + addDecoderSentinel({} as VariableSizeDecoder, sentinel) satisfies VariableSizeDecoder; + addDecoderSentinel({} as Decoder, sentinel) satisfies VariableSizeDecoder; +} + +{ + // [addCodecSentinel]: It knows if the codec is fixed size or variable size. + addCodecSentinel({} as FixedSizeCodec, sentinel) satisfies FixedSizeCodec; + addCodecSentinel({} as VariableSizeCodec, sentinel) satisfies VariableSizeCodec; + addCodecSentinel({} as Codec, sentinel) satisfies VariableSizeCodec; +} diff --git a/packages/codecs-core/src/add-codec-sentinel.ts b/packages/codecs-core/src/add-codec-sentinel.ts new file mode 100644 index 000000000000..44e318aac726 --- /dev/null +++ b/packages/codecs-core/src/add-codec-sentinel.ts @@ -0,0 +1,143 @@ +import { + SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL, + SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES, + SolanaError, +} from '@solana/errors'; + +import { containsBytes } from './bytes'; +import { + Codec, + createDecoder, + createEncoder, + Decoder, + Encoder, + FixedSizeCodec, + FixedSizeDecoder, + FixedSizeEncoder, + isFixedSize, + VariableSizeCodec, + VariableSizeDecoder, + VariableSizeEncoder, +} from './codec'; +import { combineCodec } from './combine-codec'; +import { ReadonlyUint8Array } from './readonly-uint8array'; + +/** + * Creates an encoder that writes a `Uint8Array` sentinel after the encoded value. + * This is useful to delimit the encoded value when being read by a decoder. + * + * Note that, if the sentinel is found in the encoded value, an error is thrown. + */ +export function addEncoderSentinel( + encoder: FixedSizeEncoder, + sentinel: ReadonlyUint8Array, +): FixedSizeEncoder; +export function addEncoderSentinel( + encoder: Encoder, + sentinel: ReadonlyUint8Array, +): VariableSizeEncoder; +export function addEncoderSentinel(encoder: Encoder, sentinel: ReadonlyUint8Array): Encoder { + const write = ((value, bytes, offset) => { + // Here we exceptionally use the `encode` function instead of the `write` + // function to contain the content of the encoder within its own bounds + // and to avoid writing the sentinel as part of the encoded value. + const encoderBytes = encoder.encode(value); + if (findSentinelIndex(encoderBytes, sentinel) >= 0) { + throw new SolanaError(SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL, { + encodedBytes: encoderBytes, + hexEncodedBytes: hexBytes(encoderBytes), + hexSentinel: hexBytes(sentinel), + sentinel, + }); + } + bytes.set(encoderBytes, offset); + offset += encoderBytes.length; + bytes.set(sentinel, offset); + offset += sentinel.length; + return offset; + }) as Encoder['write']; + + if (isFixedSize(encoder)) { + return createEncoder({ ...encoder, fixedSize: encoder.fixedSize + sentinel.length, write }); + } + + return createEncoder({ + ...encoder, + ...(encoder.maxSize != null ? { maxSize: encoder.maxSize + sentinel.length } : {}), + getSizeFromValue: value => encoder.getSizeFromValue(value) + sentinel.length, + write, + }); +} + +/** + * Creates a decoder that continues reading until a `Uint8Array` sentinel is found. + * + * If the sentinel is not found in the byte array to decode, an error is thrown. + */ +export function addDecoderSentinel( + decoder: FixedSizeDecoder, + sentinel: ReadonlyUint8Array, +): FixedSizeDecoder; +export function addDecoderSentinel(decoder: Decoder, sentinel: ReadonlyUint8Array): VariableSizeDecoder; +export function addDecoderSentinel(decoder: Decoder, sentinel: ReadonlyUint8Array): Decoder { + const read = ((bytes, offset) => { + const candidateBytes = offset === 0 ? bytes : bytes.slice(offset); + const sentinelIndex = findSentinelIndex(candidateBytes, sentinel); + if (sentinelIndex === -1) { + throw new SolanaError(SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES, { + decodedBytes: candidateBytes, + hexDecodedBytes: hexBytes(candidateBytes), + hexSentinel: hexBytes(sentinel), + sentinel, + }); + } + const preSentinelBytes = candidateBytes.slice(0, sentinelIndex); + // Here we exceptionally use the `decode` function instead of the `read` + // function to contain the content of the decoder within its own bounds + // and ensure that the sentinel is not part of the decoded value. + return [decoder.decode(preSentinelBytes), offset + preSentinelBytes.length + sentinel.length]; + }) as Decoder['read']; + + if (isFixedSize(decoder)) { + return createDecoder({ ...decoder, fixedSize: decoder.fixedSize + sentinel.length, read }); + } + + return createDecoder({ + ...decoder, + ...(decoder.maxSize != null ? { maxSize: decoder.maxSize + sentinel.length } : {}), + read, + }); +} + +/** + * Creates a Codec that writes a `Uint8Array` sentinel after the encoded + * value and, when decoding, continues reading until the sentinel is found. + * + * Note that, if the sentinel is found in the encoded value + * or not found in the byte array to decode, an error is thrown. + */ +export function addCodecSentinel( + codec: FixedSizeCodec, + sentinel: ReadonlyUint8Array, +): FixedSizeCodec; +export function addCodecSentinel( + codec: Codec, + sentinel: ReadonlyUint8Array, +): VariableSizeCodec; +export function addCodecSentinel( + codec: Codec, + sentinel: ReadonlyUint8Array, +): Codec { + return combineCodec(addEncoderSentinel(codec, sentinel), addDecoderSentinel(codec, sentinel)); +} + +function findSentinelIndex(bytes: ReadonlyUint8Array, sentinel: ReadonlyUint8Array) { + return bytes.findIndex((byte, index, arr) => { + if (sentinel.length === 1) return byte === sentinel[0]; + return containsBytes(arr, sentinel, index); + }); +} + +function hexBytes(bytes: ReadonlyUint8Array): string { + return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); +} diff --git a/packages/codecs-core/src/index.ts b/packages/codecs-core/src/index.ts index b4a69601786f..70bf83baefef 100644 --- a/packages/codecs-core/src/index.ts +++ b/packages/codecs-core/src/index.ts @@ -1,4 +1,5 @@ export * from './add-codec-size-prefix'; +export * from './add-codec-sentinel'; export * from './assertions'; export * from './bytes'; export * from './codec'; diff --git a/packages/codecs-data-structures/src/__tests__/struct-test.ts b/packages/codecs-data-structures/src/__tests__/struct-test.ts index 214cbab42024..5b71753e20e4 100644 --- a/packages/codecs-data-structures/src/__tests__/struct-test.ts +++ b/packages/codecs-data-structures/src/__tests__/struct-test.ts @@ -1,4 +1,4 @@ -import { addCodecSizePrefix, fixCodecSize, offsetCodec, resizeCodec } from '@solana/codecs-core'; +import { addCodecSentinel, addCodecSizePrefix, fixCodecSize, offsetCodec, resizeCodec } from '@solana/codecs-core'; import { getU8Codec, getU32Codec, getU64Codec } from '@solana/codecs-numbers'; import { getUtf8Codec } from '@solana/codecs-strings'; @@ -77,4 +77,15 @@ describe('getStructCodec', () => { expect(person.read(b('416c6963650000000000000020000000'), 0)).toStrictEqual([alice, 16]); expect(person.read(b('ff416c6963650000000000000020000000'), 1)).toStrictEqual([alice, 17]); }); + + it('can chain sentinel codecs', () => { + const person = struct([ + ['firstname', addCodecSentinel(getUtf8Codec(), b('ff'))], + ['lastname', addCodecSentinel(getUtf8Codec(), b('ff'))], + ['age', u8()], + ]); + const john = { age: 42, firstname: 'John', lastname: 'Doe' }; + expect(person.encode(john)).toStrictEqual(b('4a6f686eff446f65ff2a')); + expect(person.decode(b('4a6f686eff446f65ff2a'))).toStrictEqual(john); + }); }); diff --git a/packages/codecs-data-structures/src/__tests__/tuple-test.ts b/packages/codecs-data-structures/src/__tests__/tuple-test.ts index fe521a79d82b..2e63c8f5dd79 100644 --- a/packages/codecs-data-structures/src/__tests__/tuple-test.ts +++ b/packages/codecs-data-structures/src/__tests__/tuple-test.ts @@ -1,4 +1,4 @@ -import { addCodecSizePrefix, fixCodecSize, offsetCodec } from '@solana/codecs-core'; +import { addCodecSentinel, addCodecSizePrefix, fixCodecSize, offsetCodec } from '@solana/codecs-core'; import { getI16Codec, getU8Codec, getU32Codec, getU64Codec } from '@solana/codecs-numbers'; import { getUtf8Codec } from '@solana/codecs-strings'; import { SOLANA_ERROR__CODECS__INVALID_NUMBER_OF_ITEMS, SolanaError } from '@solana/errors'; @@ -72,4 +72,15 @@ describe('getTupleCodec', () => { expect(person.read(b('2000000000000000416c696365000000'), 0)).toStrictEqual([['Alice', 32n], 16]); expect(person.read(b('ff2000000000000000416c696365000000'), 1)).toStrictEqual([['Alice', 32n], 17]); }); + + it('can chain sentinel codecs', () => { + const person = tuple([ + addCodecSentinel(getUtf8Codec(), b('ff')), + addCodecSentinel(getUtf8Codec(), b('ff')), + u8(), + ]); + const john = ['John', 'Doe', 42] as const; + expect(person.encode(john)).toStrictEqual(b('4a6f686eff446f65ff2a')); + expect(person.decode(b('4a6f686eff446f65ff2a'))).toStrictEqual(john); + }); }); diff --git a/packages/codecs/README.md b/packages/codecs/README.md index f2e8a2ae4699..6b37f79eadc1 100644 --- a/packages/codecs/README.md +++ b/packages/codecs/README.md @@ -58,6 +58,7 @@ The `@solana/codecs` package is composed of several smaller packages, each with - [Mapping codecs](https://github.com/solana-labs/solana-web3.js/tree/master/packages/codecs-core#mapping-codecs). - [Fixing the size of codecs](https://github.com/solana-labs/solana-web3.js/tree/master/packages/codecs-core#fixing-the-size-of-codecs). - [Prefixing codecs with their size](https://github.com/solana-labs/solana-web3.js/tree/master/packages/codecs-core#prefixing-codecs-with-their-size). + - [Adding sentinels to codecs](https://github.com/solana-labs/solana-web3.js/tree/master/packages/codecs-core#adding-sentinels-to-codecs). - [Adjusting the size of codecs](https://github.com/solana-labs/solana-web3.js/tree/master/packages/codecs-core#adjusting-the-size-of-codecs). - [Offsetting codecs](https://github.com/solana-labs/solana-web3.js/tree/master/packages/codecs-core#offsetting-codecs). - [Padding codecs](https://github.com/solana-labs/solana-web3.js/tree/master/packages/codecs-core#padding-codecs). diff --git a/packages/errors/src/codes.ts b/packages/errors/src/codes.ts index eb6291d2de85..4e754f0ca9b2 100644 --- a/packages/errors/src/codes.ts +++ b/packages/errors/src/codes.ts @@ -265,6 +265,8 @@ export const SOLANA_ERROR__CODECS__LITERAL_UNION_DISCRIMINATOR_OUT_OF_RANGE = 80 export const SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE = 8078017 as const; export const SOLANA_ERROR__CODECS__INVALID_CONSTANT = 8078018 as const; export const SOLANA_ERROR__CODECS__EXPECTED_ZERO_VALUE_TO_MATCH_ITEM_FIXED_SIZE = 8078019 as const; +export const SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL = 8078020 as const; +export const SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES = 8078021 as const; // RPC-related errors. // Reserve error codes in the range [8100000-8100999]. @@ -325,6 +327,7 @@ export type SolanaErrorCode = | typeof SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED | typeof SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE | typeof SOLANA_ERROR__CODECS__CANNOT_DECODE_EMPTY_BYTE_ARRAY + | typeof SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL | typeof SOLANA_ERROR__CODECS__ENCODER_DECODER_FIXED_SIZE_MISMATCH | typeof SOLANA_ERROR__CODECS__ENCODER_DECODER_MAX_SIZE_MISMATCH | typeof SOLANA_ERROR__CODECS__ENCODER_DECODER_SIZE_COMPATIBILITY_MISMATCH @@ -343,6 +346,7 @@ export type SolanaErrorCode = | typeof SOLANA_ERROR__CODECS__LITERAL_UNION_DISCRIMINATOR_OUT_OF_RANGE | typeof SOLANA_ERROR__CODECS__NUMBER_OUT_OF_RANGE | typeof SOLANA_ERROR__CODECS__OFFSET_OUT_OF_RANGE + | typeof SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES | typeof SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE | typeof SOLANA_ERROR__CRYPTO__RANDOM_VALUES_FUNCTION_UNIMPLEMENTED | typeof SOLANA_ERROR__INSTRUCTION__EXPECTED_TO_HAVE_ACCOUNTS diff --git a/packages/errors/src/context.ts b/packages/errors/src/context.ts index c605e69cd043..c453c404ec83 100644 --- a/packages/errors/src/context.ts +++ b/packages/errors/src/context.ts @@ -13,6 +13,7 @@ import { SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE, SOLANA_ERROR__CODECS__CANNOT_DECODE_EMPTY_BYTE_ARRAY, + SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL, SOLANA_ERROR__CODECS__ENCODER_DECODER_FIXED_SIZE_MISMATCH, SOLANA_ERROR__CODECS__ENCODER_DECODER_MAX_SIZE_MISMATCH, SOLANA_ERROR__CODECS__ENUM_DISCRIMINATOR_OUT_OF_RANGE, @@ -28,6 +29,7 @@ import { SOLANA_ERROR__CODECS__LITERAL_UNION_DISCRIMINATOR_OUT_OF_RANGE, SOLANA_ERROR__CODECS__NUMBER_OUT_OF_RANGE, SOLANA_ERROR__CODECS__OFFSET_OUT_OF_RANGE, + SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES, SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE, SOLANA_ERROR__INSTRUCTION__EXPECTED_TO_HAVE_ACCOUNTS, SOLANA_ERROR__INSTRUCTION__EXPECTED_TO_HAVE_DATA, @@ -269,6 +271,12 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined< [SOLANA_ERROR__CODECS__CANNOT_DECODE_EMPTY_BYTE_ARRAY]: { codecDescription: string; }; + [SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL]: { + encodedBytes: ReadonlyUint8Array; + hexEncodedBytes: string; + hexSentinel: string; + sentinel: ReadonlyUint8Array; + }; [SOLANA_ERROR__CODECS__ENCODER_DECODER_FIXED_SIZE_MISMATCH]: { decoderFixedSize: number; encoderFixedSize: number; @@ -344,6 +352,12 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined< codecDescription: string; offset: number; }; + [SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES]: { + decodedBytes: ReadonlyUint8Array; + hexDecodedBytes: string; + hexSentinel: string; + sentinel: ReadonlyUint8Array; + }; [SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE]: { maxRange: number; minRange: number; diff --git a/packages/errors/src/messages.ts b/packages/errors/src/messages.ts index 971b5ebbd982..7edc721f1193 100644 --- a/packages/errors/src/messages.ts +++ b/packages/errors/src/messages.ts @@ -18,6 +18,7 @@ import { SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED, SOLANA_ERROR__BLOCKHASH_STRING_LENGTH_OUT_OF_RANGE, SOLANA_ERROR__CODECS__CANNOT_DECODE_EMPTY_BYTE_ARRAY, + SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL, SOLANA_ERROR__CODECS__ENCODER_DECODER_FIXED_SIZE_MISMATCH, SOLANA_ERROR__CODECS__ENCODER_DECODER_MAX_SIZE_MISMATCH, SOLANA_ERROR__CODECS__ENCODER_DECODER_SIZE_COMPATIBILITY_MISMATCH, @@ -36,6 +37,7 @@ import { SOLANA_ERROR__CODECS__LITERAL_UNION_DISCRIMINATOR_OUT_OF_RANGE, SOLANA_ERROR__CODECS__NUMBER_OUT_OF_RANGE, SOLANA_ERROR__CODECS__OFFSET_OUT_OF_RANGE, + SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES, SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE, SOLANA_ERROR__CRYPTO__RANDOM_VALUES_FUNCTION_UNIMPLEMENTED, SOLANA_ERROR__INSTRUCTION__EXPECTED_TO_HAVE_ACCOUNTS, @@ -257,6 +259,8 @@ export const SolanaErrorMessages: Readonly<{ '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.', + [SOLANA_ERROR__CODECS__ENCODED_BYTES_MUST_NOT_INCLUDE_SENTINEL]: + 'Sentinel [$hexSentinel] must not be present in encoded bytes [$hexEncodedBytes].', [SOLANA_ERROR__CODECS__ENCODER_DECODER_FIXED_SIZE_MISMATCH]: 'Encoder and decoder must have the same fixed size, got [$encoderFixedSize] and [$decoderFixedSize].', [SOLANA_ERROR__CODECS__ENCODER_DECODER_MAX_SIZE_MISMATCH]: @@ -290,6 +294,8 @@ export const SolanaErrorMessages: Readonly<{ 'Codec [$codecDescription] expected number to be in the range [$min, $max], got $value.', [SOLANA_ERROR__CODECS__OFFSET_OUT_OF_RANGE]: 'Codec [$codecDescription] expected offset to be in the range [0, $bytesLength], got $offset.', + [SOLANA_ERROR__CODECS__SENTINEL_MISSING_IN_DECODED_BYTES]: + 'Expected sentinel [$hexSentinel] to be present in decoded bytes [$hexDecodedBytes].', [SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE]: 'Union variant out of range. Expected an index between $minRange and $maxRange, got $variant.', [SOLANA_ERROR__CRYPTO__RANDOM_VALUES_FUNCTION_UNIMPLEMENTED]: 'No random values implementation could be found.',