Black friday

Save big!

All courses under $5 - for a limited time!

Code has been added to clipboard!

Solidity Inheritance: Use of Constructors, Interfaces and Arguments

Reading time 6 min
Published Dec 11, 2017
Updated Nov 6, 2019

Solidity inheritance lets you combine multiple contracts into a single one. The base (or parent) contracts are the ones from which other contracts inherit. Contracts that inherit data are derived (or children).

This tutorial discusses when to use inheritance and the different types of it: single and multi-level. Additionally, we review Solidity constructors and how to include their arguments into child contracts. Lastly, this tutorial indicates the common issue when inheriting members of the same name and explains Solidity interfaces.

Solidity Inheritance: Main Tips

  • Solidity inheritance is a process resulting in parent-child relationships between contracts.
  • There are two types of inheritance: single and multi-level.
  • Solidity constructors are optional. If not set, contracts have a default constructor.
  • You can indicate constructor arguments in two ways.

Inheritance Explained

Inheritance marks several associated contracts with a parent-child relationship. Solidity inheritance rules highly resemble Python, but they have a few differences. Inheritance generates one contract and places it into the blockchain.

Note: the contract that inherits from another contract (the parent) is the child.

These are the cases, explaining when to use inheritance and its advantages:

  • Ability to change one contract and have those modifications reflected in others.
  • Reducement of dependency.
  • Ability to reuse existing code more.
Example
pragma solidity >=0.5.0 <0.7.0;


contract Owned {
    constructor() public { owner = msg.sender; }
    address payable owner;
}

Apply is to derive from other contracts. Derived contracts access all non-private members (state variables and internal functions as well). It is not possible to access them externally through this:

Example
contract Mortal is Owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

Abstract contracts are here to make the interface known to the compiler. Look at the function without the body. In cases when contracts do not complete all functions, they become interfaces:

Example
contract Config {
    function lookup(uint id) public returns (address adr);
}


contract NameReg {
    function register(bytes32 name) public;
    function unregister() public;
}

Multiple inheritances can occur. Remember that owned is a base class of mortal, but there is only one mention of owned:

Example
contract Named is Owned, Mortal {
    constructor(bytes32 name) public {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }

You can replace functions with other functions with the same name and number or types of inputs. In cases when functions have different types of output parameters, Solidity shows an error.

Example
function kill() public {
        if (msg.sender == owner) {
            Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
            NameReg(config.lookup(1)).unregister();
            // It is still possible to call a specific
            // overridden function.
            Mortal.kill();
        }
    }
}

When Solidity constructors take arguments, include them in the header or at the constructor of the derived contracts:

Example
contract PriceFeed is Owned, Mortal, Named("GoldFeed") {
    function updateInfo(uint newInfo) public {
        if (msg.sender == owner) info = newInfo;
    }

    function get() public view returns(uint r) { return info; }

    uint info;
}

In the example above, we invoked mortal.kill() to send the destruction request. This process causes problems:

Example
pragma solidity >=0.4.22 <0.7.0;

contract owned {
    constructor() public { owner = msg.sender; }
    address payable owner;
}

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* do cleanup 1 */ mortal.kill(); }
}

contract Base2 is mortal {
    function kill() public { /* do cleanup 2 */ mortal.kill(); }
}

contract Final is Base1, Base2 {
}

Single

Single inheritance passes functions, modifiers, events and variables from the parent to the child.

Solidity Inheritance

Multi-Level

Multi-level inheritance generates more than one parent-child relationship.

Solidity Inheritance

Constructors

Solidity constructor is not a required function, defined with the constructor keyword. This function executes after contract creation, where it is possible to run contract initialization code.

Note: constructor functions can be set to internal or public.

State variables are set to their indicated value when you perform initialization inline. If not, their value sets to zero. This process happens before constructor runs.

Once the Solidity constructor executes, the finished code of the contract moves to the blockchain. The longer the final code is, the more gas it will use.

Note: the final code has all functions from the public interface, and all functions that are reachable from there via function calls. It does not contain the constructor code or internal functions for it.

In cases when contracts do not have Solidity constructors, they take the default constructor which is the same as constructor() public {}:

Example
pragma solidity >=0.5.0 <0.7.0;

contract A {
    uint public a;

    constructor(uint _a) internal {
        a = _a;
    }
}

contract B is A(1) {
    constructor() public {}
}

Remember: internal constructors make abstract contracts.

Arguments for Base Constructors

Solidity calls constructors of the base contracts by following linearization rules. If such constructors have arguments, derived contracts have to indicate all of them. You can achieve this in two ways:

Example
pragma solidity >=0.4.22 <0.7.0;

contract Base {
    uint x;
    constructor(uint _x) public { x = _x; }
}

You can directly indicate the arguments in the inheritance list:

Example
contract Derived1 is Base(7) {
    constructor() public {}
}

You can specify arguments through a modifier of the derived constructor:

Example
contract Derived2 is Base {
    constructor(uint _y) Base(_y * _y) public {}
}
  • The first way specifies arguments in the inheritance list (is Base(7)).
  • The second way calls a modifier as a part of the constructor argument (Base(_y * _y)).

You have to apply only one option for adding arguments. If you use both, it will return an error.

Tip: choose the first method when the constructor argument is a constant and defines or describes the way contract works. Use the second when the base arguments rely on one of the derived contracts.

In cases when a derived contract does not include arguments for all the Solidity constructors of base contracts, the contract becomes abstract.

Note: abstract contracts are contracts that do not define their functions fully.

Multiple Inheritance and Linearization

Solidity resembles Python since they both follow C3 Linearization. These rules mean that the sequence of base classes in the directive is important. Direct base contracts must be presented in the order from most base-like to most derived.

After Solidity calls functions that are declared several times in separate contracts, it searches the provided bases from right to left. The search ends at the first match.

Note: Solidity ignores base contracts that have already been searched.

The code snippet below triggers an error because C requests X to replace A, but A asks to replace X. This cannot compile.

Example
pragma solidity >=0.4.0 <0.7.0;

contract X {}
contract A is X {}
contract C is A, X {}

Inheriting Members With the Same Name

If inheritance produces a contract that has a modifier and a function of the same name, Solidity will treat it as an error. The same error occurs when Solidity finds modifier and event of the same name.

Exception: a state variable getter can replace a public function.

Interfaces

Solidity interfaces have many restrictions:

  • Cannot inherit other contracts
  • Functions must be external
  • Cannot define state variables
  • Cannot define constructors

Note: Solidity might omit these rules.

Interfaces represent the same things as Contract ABI. Conversions between ABI and interfaces usually go smoothly, avoiding the loss of data.

Solidity marks interfaces with a special keyword:

Example
pragma solidity >=0.5.0 <0.7.0;

interface Token {
    enum TokenType { Fungible, NonFungible }
    struct Coin { string obverse; string reverse; }
    function transfer(address recipient, uint amount) external;
}

Note: contracts can inherit interfaces in the same way they inherit other contracts.

Solidity Inheritance: Summary

  • Solidity inheritance makes two related contracts develop parent-child relationships.
  • Inheritance can be single and multi-level.
  • When contracts do not have a constructor, Solidity assigns the default one.
  • Solidity defines constructor arguments in two ways.