Skip to content

Back to the catalog

Inheritance Order Reversal On Storage End Warning Bug
soliditylang.orgadvisory

Inheritance Order Reversal On Storage End Warning Bug

The Solidity team discloses a medium-severity compiler bug present from Solidity 0.8.29 through 0.8.35 (fixed in 0.8.36): the code that emits Warning 3495 (custom storage layout too close to storage end) uses ranges-v3's actions::reverse instead of views::reverse, permanently mutating the linearizedBaseContracts AST annotation instead of only reading a reversed copy. Because that annotation drives inheritance-order decisions throughout analysis and codegen, affected contracts can get reversed constructor execution order, wrong super/virtual-function and virtual-modifier resolution, and incorrect fallback/receive dispatch — either silently miscompiling or triggering internal compiler errors. Exploitation requires a contract using both inheritance and a `layout at` clause landing within the last 2^64 storage slots, a pattern the team found no real-world evidence of via GitHub and Sourcify scans.

TL;DR Solidity compiler bug (0.8.29–0.8.35, fixed in 0.8.36) reverses C3 inheritance linearization in-place when emitting the storage-end Warning 3495, silently corrupting constructor order and virtual-function resolution for contracts combining inheritance with a near-end custom storage layout.
Document

What it is

A medium-severity Solidity compiler bug (Warning 3495 handler) that reverses the C3-linearized inheritance list in place. Present in 0.8.29 through 0.8.35, fixed in 0.8.36. The existing summary covers the root cause (actions::reverse vs views::reverse mutating linearizedBaseContracts) and the affected effects; what follows is the concrete trigger and the reproducer, which the summary omits.

How it works (concrete trigger and proof)

The warning — and therefore the corruption — fires precisely when the number of free storage slots after the static layout is at most 2^64. A hash-based layout (e.g. erc7201()) hitting this range has vanishing probability, comparable to a mapping element landing on a specific slot; the trigger is realistically only reached by an explicit high layout at value.

The page gives a minimal reproducer:

contract A { uint public x; constructor() { x = 1; } }
contract B is A { constructor() { x = x * 10; } }
contract C is B layout at 2**256 - 2**40 { constructor() { x = x + 5; } }

Deploying C should yield x == 15 (constructors run A→B→C: (1*10)+5). With the bug it yields x == 5, because the reversed linearization runs constructors in the wrong order. Removing the layout at clause (removing the warning) restores x == 15.

What to do

  • Upgrade to Solidity 0.8.36 or later.
  • If your contract uses a custom storage layout placed near the end of storage, recompile with a fixed version and verify that inheritance-dependent behavior — initialization order, super, virtual functions, virtual modifiers, fallback/receive — is as expected.

Caveats

  • Not always silent: in non-trivial hierarchies (those involving super, constructor parameters, or dependencies between storage variables), the reversed order is fairly likely to produce an internal compiler error or invalid IR rather than a wrong result — surfacing at compile time rather than reaching deployment.
  • Pure Yul is unaffected; transient storage is unaffected (custom layouts exist only for persistent storage). A contract without inheritance is unaffected regardless.
  • No pipeline dependence: --via-ir and --optimize have no bearing, since the bug is in shared analysis code.

Organisations named here

  • actions::reverse
  • C3 linearization
  • clonker
  • linearizedBaseContracts
  • ranges-v3
  • views::reverse
  • Warning 3495

Technologies named here

  • ranges-v3 library
  • Solidity compiler
  • Sourcify
  • GitHub search
  • Solidity