Solidity inheritance
What is inheritance?
What comes to mind when you hear the word "inheritance"?
According to the Oxford Dictionary, "inheritance" is defined as "a thing that is inherited." It can also refer to the act of receiving property or other things.
How can we easily understand what inheritance is in solidity? Now take a look at this scenario:
Consider constructing a home with a variety of rooms and features. Each area has a distinct role and use, but they all work together to form a whole and useful living space.
Solidity inheritance works in a similar way. Inheritance allows you to create smart contracts that inherit properties and functionality from other contracts, just like rooms in a house inherit certain characteristics from the overall house design.
Let's say you have a base contract called House
. This House
contract defines some fundamental features that every house should have, such as a foundation, walls, and a roof. It serves as the blueprint for all the houses you will build.
Now, you want to create a specialized contract called SmartHouse
that inherits from the House
contract. The SmartHouse
contract adds additional features like automated lighting, smart locks, and energy monitoring.
By using inheritance, you can easily create the SmartHouse
contract by extending the House
contract. It's like adding extra rooms and advanced technologies to your existing house design.
In Solidity, you can achieve this by using the is
keyword to specify that one contract inherits from another.
Understanding how multiple inheritance works
Solidity supports multiple inheritance including polymorphism.
How do we simply break "multiple inheritance" for better understanding? See the example below;
For example, you're a junior developer with the ability to acquire multiple skills from different senior developers. Each senior developer specializes in a specific skill set, and you can learn from them to gain a wide range of skills.
Solidity, just like your journey as a developer, supports multiple inheritances and polymorphism. Multiple inheritances allow a contract to inherit properties and functionality from multiple parent contracts, just like you can learn from multiple senior developers to acquire various skill sets. If you have three senior developers: SeniorDevA, SeniorDevB, and SeniorDevC. SeniorDevA teaches you solidity, SeniorDevB teaches you smart contract testing, and SeniorDevC teaches you yul opcodes
In Solidity, you can create a contract called Skills
that inherits from all three senior developers, combining their skill sets into a single entity. Here's an example:
It is possible to call functions further up in the inheritance hierarchy internally by explicitly specifying the contract using ContractName.functionName() or using super.functionName() if you want to call the function one level higher up in the flattened inheritance hierarchy
Using the "virtual" keyword
When a contract inherits from other contracts, only a single contract is created on the blockchain, and the code from all the base contracts is compiled into the newly created contract. Derived contracts can access all non-private members, including internal functions and state variables.
virtual
is a keyword in Solidity that is used to indicate that a function can be overridden by a function with the same name in a derived contract.
When a function is declared as virtual
, it means that it can be overridden by a function in a derived contract using the override keyword.
The keyword “
virtual
” means that the function in the base contract can change its behaviour in the new contract using overriding (this will be explained soon).
Note: The above code will report a warning due to deprecated
selfdestruct
.
Overriding functions
Functions can be overridden by another function with the same name and the same number/types of inputs. If the overriding function has different types of output parameters, that causes an error. If you want the function to override, you need to use the
override
keyword. You need to specify thevirtual
keyword again if you want this function to be overridden again.
Base functions can be overridden by inheriting contracts to change their behavior if they are marked as virtual
. The overriding function must then use the override
keyword in the function header. The overriding function may only change the visibility of the overridden function from external to public
. The mutability may be changed to a more strict one following the order: nonpayable
can be overridden by view
and pure
. view
can be overridden by pure
. payable
is an exception and cannot be changed to any other mutability. The following example demonstrates changing mutability and visibility:
Handling constructors when inheriting a contract
If a constructor takes an argument, it needs to be provided in the header or modifier-invocation-style at the constructor of the derived contract.
The Named
contract has a constructor. below is how we can implement it in the derived contract.
Now, take a close look at this destroy
function below;
Looking closely, we only specify override
and not virtual
. This means that contracts deriving from the connnnntract cannot change the behaviour of destroy
anymore.
A quick note on abstract contracts:
The abstract contracts are only provided to make the interface known to the compiler. If a contract does not implement all functions it can only be used as an interface.
Explicitly specifying base contracts
For multiple inheritance, the most derived base contracts that define the same function must be specified explicitly after the override keyword. In other words, you have to specify all base contracts that define the same function and have not yet been overridden by another base contract (on some path through the inheritance graph). Additionally, if a contract inherits the same function from multiple (unrelated) bases, it has to explicitly override it
Imagine you're attending a gathering with multiple groups of friends. Each group has its own set of activities they like to do together. However, there's a rule: if two or more groups want to do the same activity, you need to explicitly specify which group will lead that activity.
In Solidity, when you have multiple inheritance and two or more base contracts define the same function, you need to explicitly specify which base contract should be used for that function in the derived contract. This is necessary to avoid conflicts and ambiguity.
Let's say you have two base contracts: ContractA and ContractB. Both contracts have a function called doActivity(), but they have different implementations.
To create a derived contract that inherits from both ContractA and ContractB, you need to explicitly specify which base contract's implementation of doActivity() should be used in the derived contract.
In this example, the DerivedContract
inherits from both ContractA
and ContractB
. To specify which implementation of doActivity()
should be used, we use the override keyword followed by the names of the base contracts: override(ContractA, ContractB)
.
Inside the derived contract, we explicitly call the doActivity()
function from ContractA
by using ContractA.doActivity()
.
This explicit specification ensures that there is no ambiguity and allows us to decide which implementation of the function should be used in the derived contract when there are multiple base contracts defining the same function.
Note: Functions with the private visibility cannot be virtual.
Functions without implementation
Functions without implementation have to be marked virtual outside of interfaces. In interfaces, all functions are automatically considered virtual. To explain this further, see this assumption below.
Assume you have a group of friends planning a fun outing. Each friend suggests different activities they want to do together. Some friends have specific plans and know how to organize the activities, while others have ideas but aren't sure how to execute them.
In Solidity, functions without implementation are like those friends with ideas but no clear plan of action. To indicate that a function is just an idea and doesn't have a specific implementation yet, you mark it as "virtual" outside of interfaces. This lets other contracts know that they need to provide their own implementation of that function.
On the other hand, interfaces in Solidity are like a group discussion where everyone shares their ideas. In interfaces, all functions are automatically considered "virtual." It's as if every friend in the discussion acknowledges that their suggested activity is just an idea, and they are open to others providing their own implementation of it.
So, outside of interfaces, when you have a function that is not implemented yet but serves as a placeholder for an idea, you mark it as "virtual" to let others know it needs an implementation. But inside interfaces, all functions are assumed to be ideas waiting for implementation, and marking them as "virtual" is not necessary. In summary of the above assumption, it's like friends planning activities. Some friends have specific plans and mark their ideas as "virtual" to show they need someone else to implement them. In group discussions, all ideas are considered virtual by default, as everyone knows they need to be implemented by someone else.
Note that a function without implementation is different from a Function Type even though their syntax looks very similar. Example of function without implementation (a function declaration) and example of a declaration of a variable whose type is a function type:
Modifier overriding
Function modifiers can override each other. This works in the same way as function overriding (except that there is no overloading for modifiers). The virtual keyword must be used on the overridden modifier and the override keyword must be used in the overriding modifier.
Order of inheritance
Take note, to inherit from multiple base contracts, you must start from most base-like to most derived. See an example from this code below.
Note: Starting from Solidity 0.8.8, the override keyword is not required when overriding an interface function, except for the case where the function is defined in multiple bases.
In summary;
Solidity supports multiple inheritance.
Contracts can inherit other contracts by using the
is
keyword.A function that is going to be overridden by a child contract must be declared virtual.
A function that is going to override a parent function must use the keyword override.
The order of inheritance is important.
You have to list the parent contracts in the order from “most base-like” to “most derived”.
In case you missed solidity data types, click here.
Read on Solidity fallback function and function overloading here.
Also, click here to learn more about variables and control structures in solidity and here for solidity functions.
Click here to see the Github repo for this 100 days of solidity challenge.
Click here for guidelines for becoming a Solidity developer.
To learn more about blockchain, click here.
To learn more about Web3, click here.
Follow me for more knowledge on Solidity, Ethereum, and blockchain.
Subscribe to my newsletter
Read articles from Favour Ajaye directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Favour Ajaye
Favour Ajaye
smart contract developer