2.22 Inheritance
Remember that Solidity
is an object-oriented programming language, so it supports various aspects of inheritance: multiple inheritance and polymorphism. If you have studied other object-oriented programming languages, a lot of these concepts must be familiar to you, and are very similar in Solidity
.
Languages that allow multiple inheritance have to solve some problems. One of them is known as the diamond problem: this is solved in Solidity
in a very similar way to how it is solved in Python
, using what is known as C3
linearization, that forces a specific order in the directed acyclic graph constructed from the base classes. At a high level, when a function is called that is defined multiple times in different contracts (in the base and derived classes) the given bases are searched in a specific order from right to left, in a depth first manner and stopping at the first match that is found. The difference between how Solidity
implements this versus Python
is Solidity
searches these classes from right to left in the specified order as opposed to left to right in Python
.
Polymorphism
Polymorphism means that a function call executes the function of the specified name and parameter types in the most derived contract in the inheritance hierarchy. When a contract inherits from multiple other contracts, only a single contract is created on the blockchain with the code from all the base contracts compiled into the created contract.
Function Overriding
Function overriding means that functions in the base classes can be overridden by those in the derived classes which can change their behavior. If they are marked as virtual using the virtual
keyword the overriding function must then use the override
keyword to specify that it's overriding the virtual function in the base classes.
Note that virtual functions are functions without implementation. It is mandatory for them to be marked as virtual
outside of interfaces. In interfaces all functions are automatically considered virtual
, so they don't need to use the virtual
keyword. However in abstract
contracts for example, if a function has to be considered as virtual
, that is without specifying an implementation, then it should specifically use the virtual
keyword to indicate as such. Functions with private
visibility can't be made virtual
.
An interesting feature is that the overriding functions may also change the visibility of the overridden function, but this can only be done from changing them from external
to public
. The mutability of these functions may also be changed, but only to a more stricter one following this order:
non-
payable
mutability can be changed to eitherview
orpure
.view
mutability may be changed topure
.payable
mutability is an exception: it can't be changed to any other mutability.
Function Modifiers Overriding
Function modifiers can also override each other. This is very similar to how function overriding works except that there is no concept of overloading for modifiers. The virtual
keyword again must be used on the overridden modifier and the override
keyword must be used in the overriding modifier. Again very similar to the concept of virtual
and override
functions.
Base Class Functions
When considering the inheritance hierarchy, there are base classes and then derived classes. It is possible to call functions further up in the inheritance hierarchy (e.g. the base classes) from the derived classes. If we specifically know the contract that has the function that we would like to call, then we could specify that as shown here
If we wanted to call the function exactly one level higher up in the flattened inheritance hierarchy, this can be done by using the super
keyword as shown here
Shadowing
It was supported in Solidity
for the state variables until version 0.6.0
. This effectively allowed state variables of the same name to be used in the derived classes as they were declared in the base classes. These shadowed variables could effectively be used for purposes other than those declared in the base classes.
This was removed from version 0.6.0
onward because it caused quite a bit of confusion and potentially could lead to serious errors from a security perspective. As of the latest versions, state variable shadowing is not allowed in Solidity
. This means that state variables in the derived classes can only be declared if there is no visible state variable with the same name in any of its base classes.
Base Constructor
When you have classes deriving from other base classes, then the base and the derived classes could have constructors. The constructors of all the base contracts will be called following the linearization rules (which we touched upon earlier in the context of Solidity
). If the base constructors have arguments, then the derived contracts need to specify those arguments. This can be done either in the inheritance list of the derived contract or it can be explicitly done, so within the derived constructor itself.
Name Collision
Name collision is always an error in Solidity
. It is an error when any one of the following pairs in a contract have the same name due to inheritance. A function and a modifier can't have the same names in the base and derived classes. A function and an event can't have the same name either. Finally, an event and a modifier also can't have a same name, if this happens, then this is a compile time error.
Contract Types
Besides the typical contracts supported by Solidity
, it also supports three other contract types that are relevant when it comes to inheritance. Those are abstract contracts, interfaces and libraries.
Abstract Contracts
Abstract contracts are contracts where at least one of the functions in the contract is not implemented. These are specified using the abstract
keyword.
Interfaces
Interfaces, in contrast to abstract contracts, can't have any of the functions implemented within them, they can't inherit from other contracts, all the declared functions must be external, they can't declare a constructor and they can't have any state variables. These are specified using the interface
keyword.
Libraries. The using for
directive.
using for
directive.Libraries are meant to be deployed only once at a specific address. The callers call the libraries using the DELEGATECALL
opcode. This means that if library functions are called, their code is executed in the context of the calling contract. Libraries are specified using the library
keyword.
Libraries in particular have several restrictions compared to typical contracts: they can't have state variables, they can't inherit from other classes or be inherited themselves, they can't receive Ether, they can't also be destroyed, they have access to state variables of the calling contract only, if they are explicitly supplied.
Library functions can only be called directly without the use of delegatecall
, if they do not modify the state, that is, if they are view
or pure
functions. This is because libraries are assumed to be stateless by default.
Additionally, Solidity
supports using for
directive, which is used for attaching library functions to specific types in the context of a contract. So for the directive using A for B;
, A
specifies the library and B
specifies a particular type.
This means that the library functions in A
will receive objects of type B
as their first parameter when they are called on such types. This directive is applicable only within the current contract, including within all its functions.
It has no effect outside of the contract in which it is used, so for example, if this directive is used as shown here saying using safeMath for uint256;
, it means that variables of type uint256
within that contract where this directive is used can be attached functions from the SafeMath
library.
State Variables
Remember that state variables in Solidity
can have different visibilities. One of them is public
. public
state variables have automatic getter functions generated by the Solidity
compiler. These getters are just functions that are generated to allow accessing the value of the public
state variable, so they return the value of those state variables. Such public
state variables can override external functions in their base classes that have the same name as the public
state variables, parameter and return types of those external
functions match the getter function of these variables. so while public state variables in Solidity
can override external
functions according to thpse, they themselves can't be overridden.
Last updated