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.
Contents
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.
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
:
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:
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
:
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.
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:
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:
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.
Multi-Level
Multi-level inheritance generates more than one parent-child relationship.
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 {}
:
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.
- Easy to use with a learn-by-doing approach
- Offers quality content
- Gamified in-browser coding experience
- The price matches the quality
- Suitable for learners ranging from beginner to advanced
- Free certificates of completion
- Focused on data science skills
- Flexible learning timetable
- Simplistic design (no unnecessary information)
- High-quality courses (even the free ones)
- Variety of features
- Nanodegree programs
- Suitable for enterprises
- Paid Certificates of completion
- A wide range of learning programs
- University-level courses
- Easy to navigate
- Verified certificates
- Free learning track available
- University-level courses
- Suitable for enterprises
- Verified certificates of completion
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:
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:
contract Derived1 is Base(7) {
constructor() public {}
}
You can specify arguments through a modifier of the derived constructor:
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.
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:
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.