{ skip to content }

Solidity 0.6.x features: inheritance

Posted by Elena Gesheva on June 18, 2020

Explainers

Similar to object-oriented programming in Solidity - a contract-oriented language - the inheritance and polymorphism features are as widely adopted and critical for the language evolution. There is hardly any Solidity developer who hasn't used these language features in their contracts to decouple logic and increase code reuse. With version 0.6 of the language the main improvements introduced are to make existing rules explicit in addition to introducing interface inheritance and disallowing the dangerous state variable shadowing. The compiler continues to use C3 Linearization, see Solidity documentation for inheritance.

Explicit virtual and override

Functions are no longer virtual by default. This means that a call to a non-virtual function will always execute that function regardless of any other contract in the inheritance hierarchy. This reduces ambiguity that exists in version 0.5 where all functions are implicitly virtual, allowing to be overridden further down the inheritance structure. This is especially dangerous in big inheritance graphs where this ambiguity can lead to unintended behaviour and bugs.

For example in contract C below calling setValue calls the most derived implementation which is from contract B. However this is non-obvious from the implementation.

pragma solidity ^0.5.17;
contract A {
    uint public x;
    function setValue(uint _x) public {
        x = _x;
    }
}

contract B {
    uint public y;
    function setValue(uint _y) public {
        y = _y;
    }
}

contract C is A, B {
}

With version 0.6 for the example above the compiler raises a type error: Derived contract must override function "setValue". Two or more base classes define functions with the same name and parameter types. The above is an example of multiple inheritance where the same function is inherited from multiple base classes - A and B. In this case, it has to be overridden and the bases have to be listed in the override specifier. Important to note that the order in override(A,B) does not matter - specifically it does not change the behaviour of super - this is still dictated by the C3 linearization of the inheritance graph which is determined by the order in the contract C is A, B { ... } declaration.

pragma solidity ^0.6.10;
contract A {
    uint public x;
    function setValue(uint _x) public virtual {
        x = _x;
    }
}

contract B {
    uint public y;
    function setValue(uint _y) public virtual {
        y = _y;
    }
}

contract C is A, B {
    function setValue(uint _x) public override(A,B) {
        A.setValue(_x);
    }
}

Note that you can only override functions if they are marked virtual. Additionally, any overriding function must be marked override. If that again should be overridable, it has to also be marked virtual.

interface functions are implicitly virtual so when implementing an interface you have to explicitly override its functions in the implementation. There is an ongoing discussion on this design here.

It's worth noting that the keyword super works as it did before: It calls the function one level higher up in the flattened inheritance hierarchy. Also unchanged: super is still not allowed on external functions.

Interfaces can inherit

This functionality is new to version 0.6 and allows interface inheritance. The resulting interface is a combination of all inherited interfaces' functions which the contract has to implement.

pragma solidity ^0.6.10;
interface X {
    function setValue(uint _x) external;
}

interface Y is X {
    function getValue() external returns (uint);
}

contract Z is Y {
    uint x;
    function setValue(uint _x) external override { x = _x; }
    function getValue() external override returns (uint) { return x; }
}

Note that if the contract does not implement all functions it has to be marked as abstract.

pragma solidity ^0.6.10;
abstract contract Z is Y {
    uint x;
    function setValue(uint _x) external override { x = _x; }
}

Abstract contracts

With version 0.5 a contract that doesn't implement all of its functions is implicitly made abstract by the compiler:

pragma solidity ^0.5.17;
contract X {
    function setValue(uint _x) public virtual;
}

With 0.6 this differentiation has to be explicit with the compiler generating error contract X should be made abstract otherwise

pragma solidity ^0.6.10;
abstract contract X {
    function setValue(uint _x) public virtual;
}

Public variables safer override for external functions

Although this feature existed before 0.6 it is now safer with the added checks that the getter function of the variable (generated by the compiler) matches the parameter and return type of the overridden external function. With version 0.5 it is possible these were allowed to differ as shown in the following example:

pragma solidity ^0.5.17;
interface A
{
    function f() external pure returns(uint8);
}

contract B is A
{
    uint256 public f = 257;
}

Contract A's call to underlying contract B would return 1 as the 257 value overflows when cast to uint8.

With 0.6 now the above generates TypeError: Overriding public state variable return types differ forcing us to resolve the type conflict thus avoiding the overflow:

pragma solidity ^0.6.10;
interface A
{
    function f() external pure returns(uint256);
}

contract B is A
{
    uint256 public override f = 257;
}

Note that public state variables can only override external functions and it remains disallowed for variables to override internal or public functions.

No state variables shadowing

Inheriting visible state variables with the same name in version 0.5 was allowed by the compiler and is only raised as a problem by some static analysis tools. The following example demonstrates the problem with this design:

pragma solidity ^0.5.17;
contract A {
    uint public x;

    function setValue1(uint _x) public { x = _x; }
}

contract B is A {
    uint public x;

    function setValue2(uint _x) public { x = _x; }
}

In this context functions in contract A referencing x are using its own instance of that state variable - A.x whereas for B it's B.x. Therefore the result of calling B.setValue2(100) would be that B.x will be set to 100 while calling B.setValue1(200) will set A.x to 200.

With 0.6 this is now prohibited and raises a compiler DeclarationError: Identifier already declared error.

Previous post

Next post

Get involved

GitHub

Twitter

Mastodon

Matrix

Discover more

BlogDocumentationUse casesContributeAboutForum

2023 Solidity Team

Security Policy

Code of Conduct