Interfaces and Inheritance
B is A
As we have seen in previous sections, Solidity is an object-oriented programming language which allow us to create classes (contracts) and objects (deployments of contracts). However, object-oriented programming offers much more than this. For example, one of the features that OOP provides is inheritance; classes whose logic can be derived from other classes.
Solidity, in alignment with OOP, also allows us to utilize inheritance with respect to contracts. Consider a situation where we have two contracts A
and B
, and we want for B to inherit the state/logic of A. Below is the syntax of how we would do that in Solidity:
To demonstrate the inheriting property of smart contracts, consider the following code:
In contract A
, we have the state variable age = 21
and the function getName
, which returns the name of the contract of the contract. When we create B
, B
inherits the function getName
. Furthermore, B
also inherits the state variable age
, which is made evident by the function getAge
.
Overriding Functions
Although inheritance allows us to extract common logic to a single smart contract, there are instances where we might want to define our own logic for a function rather than using the implementation provided by the parent function.
To override a function, our first intuition might be as follows:
However, if you attempt to compile this the code above, you will get a very angry message from the compiler telling you that you need to declare getName
to be override-able. And indeed, this is what we're forgetting:
Examining the code above on a line-by-line basis:
Line 3: we mark
getName
in the parent contract asvirtual
: this tells the compiler that children contracts can override the logic ofgetName
Line 11: we mark
getName
in the child contract asoverride
: this tells the compiler that we are overriding the logic defined in the parent contract with our own
The super
Keyword
super
KeywordThere exists instances where, although we want to rely on the logic of a parent contract, we also want to extend the functionality of a function without having to rewrite code. The following example makes this dilemma clear:
Everyone who works at company A is an employee by default. Furthermore, everyone at company A receives a salary of $100,000. However, the CEO, in addition to already being an employee by default, also receives a bonus of $25,000.
In the example above, if we represent employees and the CEO as contracts, the associated code is as follows:
Notice that in line 16, we are copying the same code as in line 6 (i.e. we are duplicating code). Rather than duplicating code, which can be a source of bugs later on, we can utilize the super
keyword to reuse the logic of the parent implementation.
In line 16, we are calling the setSalary
which pertains to the parent contract (i.e. we are setting the salary of the CEO). Afterwords, in line 17, we are setting the bonus of the CEO to be equal to 25000
.
Overloading Functions
Consider the following code:
Both contract A
, B
contain the function returnArg
which returns the argument passed in. However, A
's implementation of returnArg
deals with strings while B
's implementation of returnArg
deals with uints. With our current understanding of functions and inheritance, this shouldn't compile, right?
It turns out, however, that this is completely valid Solidity code. The reason for this is that although both implementations of returnArg
are different, the returnArg
found in A
is actually not the same function as returnArg
in B
.
To understand why this is the case, we must introduce the idea of function selectors - the ID of a function. Although we will dive much deeper into this when we discuss the EVM, what you need to know is that when compiled, each function name is associated with an number. How is this number generated? The ID of a function is dependent on the type function parameters and the ordering of the parameters. As an example, the following demonstrates this idea firsthand:
As it might have become evident, there is no need to override returnArg
because returnArg
in contract A
has a different function selector than returnArg
in contract B
. In this scenario, we are overloading returnArg
with two different implementations.
Interfaces
In object-oriented programming languages, a way in which to hide implementation details from users of a class is via the use of interfaces. Likewise, in Solidity, we can also utilize interfaces to communicate to users the functionality of any inheriting smart contract. The syntax of a contract interface is as follows:
To motivate our understanding of interfaces, consider the following smart contract:
If we only wanted to understand the behavior of the Miner
contract rather than the implementation details, we could write an interface for the Miner
contract:
There's a couple of things to note from this example:
Why are all functions marked as external? Recall that external functions can only be called by other accounts. Therefore, by marking all functions in an interface as
external
, we are signaling that the functions a contract implements via an interface are meant to be called by other accounts.Although all functions are marked in an interface as
external
, contracts that implement an interface can change the visibility of said functions aspublic
. However, they cannot be changed toprivate
orinternal
.
Abstract Contracts
In our final section on inheritance, we cover the concept of abstract contracts - contracts where there is at least one function that is missing implementation details.
Consider the following scenario where we wish to implement a Mathematician contract; although all chefs are required to remember a specific equation (in this case, the average of two numbers), they are also required to remember their own specific equation. Already, it is obvious that there will be some overlapping logic (i.e. all chefs will have a function to compute the average of two numbers), there will also be mutually exclusive logic in each mathematician contract.
Although the IMathematician
interface tells us about the behavior of a mathematician, it still does not allow us to implement the overlapping logic that all mathematicians have. In this case, it would be best to use an abstract contract. The syntax for an abstract contract is as follows:
The code below shows how we can use abstract contracts to create our Mathematician
contract:
Last updated