Inheritance trees are amazingly helpful for Solidity developers
As we know, the Solidity programming language supports multiple inheritance. This is very powerful yet it can also be confusing: the more complexity you introduce to your distributed applications (dApps) through multiple inheritance, the greater your application’s attack surface is. Not only that, if you have a big complicated dApp, it can be difficult to track all of the relationships between your smart contracts. It’s therefore important to thoroughly document how your dApp works and the interrelationships between its smart contracts. Inheritance trees are an effective way to do this.
What are inheritance trees
Inheritance trees when used in the context of contract-oriented programming languages like Solidity, are visual representations that depict the associations between smart contracts. They’re effective because they take something complex at an abstract level and present it in a simple and logical, physical representation.
An example inheritance tree for OpenZeppelin’s ERC20 contracts
OpenZeppelin is a “battle-tested” open source framework comprised of reusable Ethereum smart contracts. The framework helps smart contract developers reduce the risk of vulnerabilities in their dApps by using standard, tested, community-reviewed code. Today, its smart contracts power over $4.5 billion worth of digital assets. We recently shared lessons learned from contributing to the project.
The core development principles and strategies that OpenZeppelin uses are based on: security in depth, simple and modular code, clarity-driven naming conventions, comprehensive unit testing, pre-and-post-condition sanity checks, code consistency, and regular audits.
OpenZeppelin has a collection of ERC20 token contracts that can be used in conjunction with one another to implement different token functionality. Here’s a list of the different ERC20 token contracts in OpenZeppelin coupled with brief descriptions of what each one does:
- IERC20 — ERC20 token interface
- ERC20 — Implementation of the basic standard token; acts as the base contract that can be extended using any of the following contracts
- ERC20Burnable — Token that can be irreversibly burned (destroyed). Inherits from ERC20
- ERC20Detailed — Token with a custom name, symbol, and predefined number of decimal points that it will support. Inherits from IERC20
- ERC20Mintable — Contract that provides ERC20 minting logic. Inherits from ERC20
- ERC20Capped — Mintable token with a token cap. Inherits from ERC20Mintable
- ERC20Pausable — ERC20 modified in order to support pausable transfers. Inherits from ERC20.
- SafeERC20 — Wrappers around ERC20 operations that revert on failure.
- TokenTimelock — TokenTimelock is a token holder contract that will allow a beneficiary to extract the tokens after a given release time
Here’s what an inheritance tree would look like for these contracts:
------------ IERC20 --------------
--------------- ERC20 --------------- ERC20Detailed
| | |
v v v
ERC20Burnable ERC20Pausable ERC20Mintable
You’ll notice all of OpenZeppelin’s ERC20 contracts inherit from the ERC20 interface. Interfaces are useful when it comes to designing larger scale dApps prior to their comprehensive implementations. They make it easy to facilitate extensibility in your dApps without introducing added complexity. You can learn more about interfaces and how they differ from abstract contracts here.
From there we have other contracts that inherit from the
ERC20 base contract and extend its functionality. The added functionality includes the ability to burn ERC20 tokens, pause ERC20 token transfers, mint ERC20 tokens, and cap how many ERC20 tokens a particular ERC20 contract can mint.
Inheritance trees are particularly helpful when included in a dApp repo’s README. For instance, Tidbit is a library for oracles on Ethereum that was developed by Level K. Here’s how they’ve included an inheritance tree into their repo:
OracleBase --------> BasicOracle ------> SignedOracle
| | |
v v v
PushOracleBase --> BasicPushOracle --> SignedPushOracle
It’d be nice to see more open source dApp projects doing the same.
- The Solidity programming language supports multiple inheritance. This is very powerful yet it can also lead to confusion among development teams— particularly in the context of larger dApps that are comprised of many different smart contracts.
- Inheritance trees are useful tools that can be used to visually depict associations between interacting smart contracts in complex dApps. They take something conceptually abstract and present it in a logical representation.
- Inheritance trees are lightweight and can be developed quickly. They significantly reduce complexity and mitigate risk.
- My hope is this article will encourage more smart contract developers to include inheritance trees in their project’s documentation.