Skip to content

fix(abi): encode zero-width static aggregates as empty bytes#4777

Open
spokodev wants to merge 1 commit into
wevm:mainfrom
spokodev:fix/abi-encode-zero-width-static-aggregate
Open

fix(abi): encode zero-width static aggregates as empty bytes#4777
spokodev wants to merge 1 commit into
wevm:mainfrom
spokodev:fix/abi-encode-zero-width-static-aggregate

Conversation

@spokodev

Copy link
Copy Markdown

Summary

An empty fixed array (T[0]) or empty tuple (()) is a zero-width static type. In encodeAbiParameters.ts, the static branches of encodeArray and encodeTuple build their encoding with concat(preparedParams.map(p => p.encoded)) over an empty array.

concat([]) (in utils/data/concat.ts) checks typeof values[0] === 'string'. For an empty input values[0] is undefined, so it routes to concatBytes([]) and returns an empty Uint8Array rather than the hex string '0x'. That Uint8Array then leaks back into encodeParams, poisoning the string-vs-bytes detection and producing either wrong bytes or an uncaught TypeError.

The function's documented return type is Hex, never Uint8Array.

Reproduction

encodeAbiParameters([{ type: 'uint256[0]' }, { type: 'uint256' }], [[], 3n])
// before: Uint8Array(66)            (33 leading zero bytes + the uint256)
// after:  the 32-byte uint256 0x..0003

encodeAbiParameters([{ type: 'uint256' }, { type: 'tuple', components: [] }], [5n, []])
// before: throws TypeError: x.replace is not a function   (concatHex on a Uint8Array)
// after:  the 32-byte uint256 0x..0005

Oracle

Per the ABI spec, enc(T[0]) and enc(()) are zero-length: an empty fixed array or empty tuple contributes no bytes. ethers agrees:

AbiCoder.defaultAbiCoder().encode(['uint256[0]', 'uint256'], [[], 3n])
// '0x0000000000000000000000000000000000000000000000000000000000000003'
AbiCoder.defaultAbiCoder().encode(['uint256', 'tuple()'], [5n, []])
// '0x0000000000000000000000000000000000000000000000000000000000000005'

Fix

Emit '0x' directly when preparedParams is empty in the static branch of encodeArray and encodeTuple, instead of relying on concat([]). The change is local to the two aggregate encoders; concat's behaviour is untouched (it has other callers).

Added tests cover both repros against the ethers/ABI-spec oracle.

This is a separate root cause from #921 (empty dynamic bytes[]) and from the RLP trailing-bytes fix in a sibling PR.

An empty fixed array (`T[0]`) or empty tuple (`()`) is a zero-width
static type. `encodeArray`/`encodeTuple` built the static encoding via
`concat(preparedParams.map(p => p.encoded))` over an empty array, but
`concat([])` routes to `concatBytes([])` (because `values[0]` is
`undefined`, not a string) and returns an empty `Uint8Array` instead of
the hex string `'0x'`. That `Uint8Array` then poisons the string-vs-bytes
detection in `encodeParams`, producing wrong bytes or an uncaught
`TypeError: x.replace is not a function`.

Per the ABI spec, `enc(T[0])` and `enc(())` are zero-length, so emit
`'0x'` directly when there are no prepared params.

  encodeAbiParameters([{type:'uint256[0]'},{type:'uint256'}], [[], 3n])
  // before: Uint8Array(66)  after: 0x..0003 (32 bytes)
  encodeAbiParameters([{type:'uint256'},{type:'tuple',components:[]}], [5n, []])
  // before: throws          after: 0x..0005 (32 bytes)

ethers `AbiCoder.encode` returns the 32-byte uint256 for both inputs.
@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5ae4a93

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

@spokodev is attempting to deploy a commit to the Wevm Team on Vercel.

A member of the Team first needs to authorize it.

@spokodev

Copy link
Copy Markdown
Author

Sibling PR for a separate root cause: #4778 (fromRlp trailing bytes).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant