
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.
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-irand--optimizehave 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
Related in Blockchain

THORChain Exploit Report #2 — GG20 root-cause disclosure

Black Thursday revisited: MakerDAO keeper exploited for $500K
.jpg)