The Solidity Developer Survey 2025 results are out! See the interactive report
{ skip to content }

Misordered Named Parameters in require with Custom Errors Bug

Posted by Solidity Team on May 20, 2026

Security Alerts

On February 9, 2026, a bug in the IR-based code generator was reported through the Ethereum Foundation bug bounty program. The bug causes the arguments of a custom error passed to require using named-parameter syntax to be ABI-encoded in call-site order rather than declaration order.

The bug was introduced in Solidity 0.8.26, which added support for passing custom errors as the second argument to require. Solidity 0.8.37, released on May 20, 2026, provides a fix. Due to the very low severity of the bug, described below, the fix was not prioritized for an expedited release.

We assigned the bug a severity of "very low". The affected code is the construction of revert data for a transaction that is already going to revert, so contract state is not modified by it. Triggering the bug additionally requires a fairly specific pattern - a custom error passed to require and instantiated with named arguments in a non-declaration order - and only affects the IR-based pipeline.

Which Contracts Are Affected?

A contract is affected when all of the following conditions hold:

  1. The contract is compiled with the IR-based pipeline (--via-ir on the command line or viaIR: true in Standard JSON). The evmasm pipeline is unaffected.
  2. The contract uses require(condition, ErrorName({...})) where ErrorName is a user-defined error.
  3. The named arguments are written in an order that differs from the order declared by the error definition. Positional arguments, or named arguments written in declaration order, produce the correct encoding.

Other language constructs that accept named arguments are not affected, including:

  • revert ErrorName({...}) statements
  • event emissions (emit E({...}))
  • internal, external, library, and bound function calls
  • struct constructor invocations

Until upgrading to a fixed version, the bug can be avoided by writing the named arguments in declaration order, or by using positional arguments.

Technical Details

A custom error declaration fixes the order of its parameters, and that order determines its ABI signature and the layout of its encoded payload:

error NamedArgsError(uint256 a, uint256 b);

Solidity also allows the caller to pass arguments by name, optionally in a different order from the declaration:

require(false, NamedArgsError({b: 7, a: 2}));

The compiler binds named arguments to parameters by name, so any permutation that mentions every parameter exactly once is accepted by the type checker. The type system does not require the call-site order to match the declaration. As a result, the bug can be triggered by any reordering. Most surprisingly, this includes reorderings of two arguments of the same type, where no positional type mismatch could ever surface.

In the IR-based code generator, the lowering of require with a custom error passes the arguments to the encoding helper in the order they appear at the call site, instead of first reordering them to match the parameter order of the error. With the snippet above, the contract reverts with a payload whose first word holds the value of b (7) and whose second word holds the value of a (2), so off-chain decoders read a = 7, b = 2 instead of the intended a = 2, b = 7.

Standalone revert ErrorName({...}) statements go through a different code path that reorders arguments before encoding, and so are not affected. The bug is specific to the require-with-custom-error path introduced in 0.8.26.

Misalignment With Mixed Stack Widths

The consequences extend beyond a swap of word-sized values when the arguments occupy different numbers of stack slots in the Yul IR. References to string, bytes, and arrays in calldata are represented by two stack slots (offset and length), whereas value types and references in other data locations use a single slot. When a calldata reference is reordered with respect to a value-type argument, the slots passed to the encoding helper are permuted at the level of stack slots, not at the level of named parameters, and the helper interprets its inputs against parameter positions of a different width than intended.

Consider:

error StringAndUint(string a, uint256 b);

contract C {
    function f(string calldata s) external pure {
        require(false, StringAndUint({b: 42, a: s}));
    }
}

The encoding helper for StringAndUint expects to first receive the two slots that describe a (the calldata string's offset and length), followed by the single slot that holds b. With the bug it receives b's single slot first, followed by a's two slots, and then encodes them assuming the first two slots belong to the dynamic-type parameter. The length field is therefore read from a value-type slot, and the string data is copied from an arbitrary calldata offset.

In the most severe configurations the encoding aborts with an EVM-level error, which still causes the transaction to revert - but with no decodable error data - rather than producing the intended custom-error revert. In less severe configurations the payload is well-formed at the byte level but does not match the error's ABI signature, so any consumer that selects on the error selector and decodes its fields observes values that do not correspond to the source-level arguments.

Impact

Because the bug affects only the construction of revert data for transactions that are already going to revert, it cannot be used to manipulate storage, return values, or external calls. Its primary consequence is misleading off-chain consumers - block explorers, indexers, error-decoding libraries, and test frameworks - that decode the payload as if the values had been placed correctly. In the stack-misalignment cases the failure is more visible: an encoding-level revert can replace the intended custom-error revert, so off-chain code that distinguishes specific custom errors from generic reverts may take a different branch than expected.

Named-argument syntax is uncommon in require calls in practice, and the bug remained undetected for almost two years. The narrow trigger conditions and the read-only nature of the affected code path together place this bug in the "very low" severity tier.

Previous post

Next post

Get involved

GitHub

Twitter

Mastodon

Matrix

Discover more

BlogDocumentationUse casesContributeAboutForum

2026 Solidity Team

Security Policy

Code of Conduct