From 1c31ad432d9dce120b7de16a79eadd8f5699f717 Mon Sep 17 00:00:00 2001 From: itzKLAUS <124288796+itzKLAUS@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:31:08 +0530 Subject: [PATCH] Validate fixed array lengths before packed encoding --- src/abi.test.ts | 19 +++++++++++++++++++ src/parsers/array.test.ts | 26 ++++++++++++++++++++++++++ src/parsers/array.ts | 16 +++++++++------- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/abi.test.ts b/src/abi.test.ts index 3ad15f2..c880c5d 100644 --- a/src/abi.test.ts +++ b/src/abi.test.ts @@ -48,6 +48,25 @@ describe('encodeSingle', () => { }); describe('encodePacked', () => { + it.each([false, true])( + 'validates fixed arrays with dynamic elements with tight=%s', + (tight) => { + expect(() => encodePacked(['string[2]'], [['foo']], tight)).toThrow( + 'Unable to encode value: Array length does not match type length. Expected a length of 2, but received 1.', + ); + expect(() => encodePacked(['bytes[2]'], [['0x12']], tight)).toThrow( + 'Unable to encode value: Array length does not match type length. Expected a length of 2, but received 1.', + ); + + expect( + bytesToHex(encodePacked(['string[2]'], [['foo', 'bar']], tight)), + ).toBe('0x666f6f626172'); + expect( + bytesToHex(encodePacked(['bytes[2]'], [['0x12', '0x34']], tight)), + ).toBe('0x1234'); + }, + ); + it.each(PACKED_ABI_TEST_VECTORS)( 'encodes values in packed mode', ({ types, values, result }) => { diff --git a/src/parsers/array.test.ts b/src/parsers/array.test.ts index dbd10da..b37c0c4 100644 --- a/src/parsers/array.test.ts +++ b/src/parsers/array.test.ts @@ -52,6 +52,32 @@ describe('array', () => { }); describe('encode', () => { + describe.each([false, true])( + 'packed fixed arrays with tight=%s', + (tight) => { + it.each([ + ['string[2]', []], + ['string[2]', ['foo']], + ['string[2]', ['foo', 'bar', 'baz']], + ['bytes[2]', []], + ['bytes[2]', ['0x12']], + ['bytes[2]', ['0x12', '0x34', '0x56']], + ])('rejects a mismatched length for %s with %j', (type, value) => { + expect(() => + array.encode({ + type, + value, + buffer: new Uint8Array(), + packed: true, + tight, + }), + ).toThrow( + `Array length does not match type length. Expected a length of 2, but received ${value.length}.`, + ); + }); + }, + ); + it('encodes an array', () => { expect( bytesToHex( diff --git a/src/parsers/array.ts b/src/parsers/array.ts index 936c1e3..35aaf68 100644 --- a/src/parsers/array.ts +++ b/src/parsers/array.ts @@ -121,6 +121,15 @@ export const array: Parser = { new ParserError(`Cannot pack nested arrays.`), ); + if (fixedLength !== undefined) { + assert( + fixedLength === value.length, + new ParserError( + `Array length does not match type length. Expected a length of ${fixedLength}, but received ${value.length}.`, + ), + ); + } + // Tightly pack `T[]` where `T` is a dynamic type. This is not supported in // Solidity, but is commonly used in the Ethereum ecosystem. if (packed && isDynamicParser(getParser(arrayType), arrayType)) { @@ -135,13 +144,6 @@ export const array: Parser = { } if (fixedLength) { - assert( - fixedLength === value.length, - new ParserError( - `Array length does not match type length. Expected a length of ${fixedLength}, but received ${value.length}.`, - ), - ); - // `T[k]` for any `T` and `k` is encoded as `(T[0], ..., T[k - 1])`. return tuple.encode({ type: getTupleType(arrayType, fixedLength),