Inheritance Order
Overview
Remediation Guide: Inheritance Order Remediation
When a Solidity contract inherits from multiple bases, C3 linearization decides which implementation wins and in what order super calls chain. Get the base order wrong and the contract dispatches to the wrong parent, skips a modifier, or shadows state — all without a compiler error in many cases. This detector (SWC-125) reconstructs inheritance evidence from the decompiled IR and reports four finding types:
- Diamond pattern — two inheritance paths converge on a common ancestor, where linearization order decides whose override runs.
- Multiple super calls — a function chains through several parent implementations, so the base order silently determines execution sequence.
- Multiple overrides — the same function signature has conflicting implementations across the hierarchy.
- Overlapping modifiers — guard logic from different parents overlaps, and ordering decides which check runs first (or whether one is skipped).
Why This Is an Issue
Solidity linearizes right-to-left, from “most base-like” to “most derived” — the reverse of what many developers expect from Python’s left-to-right MRO. A contract declared contract Token is Pausable, Ownable and one declared contract Token is Ownable, Pausable can resolve the same _beforeTokenTransfer override to different parents. When the skipped parent held the pause check or the access guard, the result is a live contract missing a control its author believed was inherited. These bugs are invisible in unit tests that exercise parents in isolation.
How to Resolve
Order bases from most general to most derived, make every ambiguous override explicit, and chain with super deliberately:
// Before: ambiguous — which _beforeTransfer runs first?
contract Vault is Pausable, AccessControlled {
function withdraw() external {
_beforeTransfer(); // resolves by linearization, not by intent
}
}
// After: explicit override declares the full chain
contract Vault is Pausable, AccessControlled {
function _beforeTransfer() internal override(Pausable, AccessControlled) {
super._beforeTransfer(); // runs AccessControlled, then Pausable
}
}
Examples
Sample Sigvex Output
{
"detector_id": "inheritance-order",
"severity": "medium",
"confidence": 0.7,
"description": "Diamond inheritance pattern: two code paths in withdraw() converge on shared ancestor logic; linearization order determines which override executes.",
"location": { "function": "withdraw()", "offset": 0 }
}
Detection Methodology
Inheritance is a source-level construct that the compiler flattens away, so the detector infers it from bytecode structure: functions whose bodies contain multiple internal-call chains consistent with super dispatch, duplicated function-body fragments that indicate conflicting overrides, and repeated guard prologues that indicate overlapping modifier chains. Each finding type carries its own confidence (0.5–0.7), reflecting that flattened bytecode retains only indirect evidence of the original hierarchy.
Limitations
- The original inheritance graph is not present in bytecode; findings are inferences from dispatch and guard structure, so intentional patterns (deliberate multi-parent chaining) can be flagged.
- Heavy optimization can merge or inline parent bodies, hiding conflicts from detection.
- Storage-slot shadowing between parents is covered by the shadowing detector rather than this one.
Related Detectors
- Shadowing — state variable shadowing across the hierarchy
- Storage Collision — slot layout conflicts in proxy setups
- Access Control — missing guards, whatever their cause