Incorrect Inheritance Order in Smart Contracts
Incorrect Inheritance Order in Smart Contracts
What is Multiple Inheritance in Solidity?
Solidity allows for multiple inheritances, including polymorphism.
A function call, whether internal or external, will always execute the function with the same name (and set of parameter types) in the most derived contract in the tree of inheritance. Each function in the hierarchy must be explicitly enabled using the virtual and override keywords.
By explicitly defining the contract using ContractName.functionName() or by using super.functionName() if you want to call the function one level higher up in the flattened inheritance hierarchy, it is possible to call functions further up in the inheritance hierarchy internally.
When a contract inherits from another contract, the code from all the base contracts is compiled into the newly formed contract, which is the only contract that is created on the blockchain. As a result, any internal calls to base contract functions will likewise only employ internal function calls (super.f(..) will use JUMP rather than a message call).
Here’s an example of how contracts inherit each other:
pragma solidity ^0.8.0;
contract X {}
contract Y is X {}
contract C is Z, X {}
Incorrect Inheritance Order:
Diamond Problem in Inheritance:
Multiple inheritance is supported by Solidity, allowing a contract to inherit multiple contracts. Multiple inheritance poses an ambiguity known as the Diamond Problem: which base contract should be called in the child contract if two or more define an identical function?
Solidity resolves this dilemma by employing reverse C3 Linearization, which establishes a hierarchy between base contracts and is primarily used to determine the order in which methods should be inherited in the presence of multiple inheritance.
This means that in the contracts shown below, the `Child` contract will inherit from the other contracts in the order Child -> Part2 -> Part1.
contract Part1 {
constructor() public {}
}
contract Part2 {
constructor() public {}
}
contract Child is Part1, Part2 {
constructor() public {}
}
Case Scenario:
In the example shown above, there are two role checker contracts. One of them checks if the caller has an admin role, and the other one checks if they have a guest role. Both of these functions return true if the statement is true.
These checkers are inherited by the contract “ownersCanKill”. The inheritance order matters here because it’ll decide which “roleCheck()” function to call.
Ideally, guests should not be able to call the “kill()” function but according to the C3 Linearization, the function call will follow the following path:
ownersCanKill.kill() -> guestChecker.roleCheck() -> adminChecker.roleCheck()
This will cause unexpected behavior and allow guests to `selfdestruct` the contract and transfer all the funds into their accounts.
This, however, won’t work from Solidity 0.6.0+, and the compiler will throw an error on ambiguous functions.
Best practices for inheritance in Solidity:
C3 Linearization:
When multiple inheritance is present, the C3 superclass linearization algorithm is largely utilized to determine the order in which methods should be inherited.
The developers should keep in mind the order while specifying the contract names when inheriting them. This will be a deciding factor on where the defined functions come from.
The parent function is invoked:
If the developer wants to specifically call a function defined in the parent contract, they can use the `super` keyword along with the function call: `super.func()`.
This will instruct the EVM to use the `func()` defined in the parent contract, completely ignoring any definition in the child.
One function, multiple forms:
Similar to how contracts can have numerous forms, functions can likewise have numerous forms, which is what polymorphism entails. The ability to have numerous functions with the same name as long as their signatures differ is one of the best things about polymorphism. If they use different inputs, i.e, In actuality, this is equivalent to having only one function, but this one function has multiple manifestations.
Conclusion:
Developers can inherit functions, state variables, and function modifiers using Solidity. Polymorphism is also supported by solidity via function overriding. Ambiguity can arise when multiple inherited contracts define functions with the same names and parameter types. When implementing function overriding in your apps, don’t forget to use the words virtual and override.
You can rely on SolidityScan, a cloud-based smart contract vulnerability scanner, to ensure that the proper steps are taken to achieve the highest level of smart contract security. Signup for a free trial at https://solidityscan.com/signup
