Contract inheritance in Solidity | How to derive smart contracts from other contracts

In this guide, we are going to learn how contract inheritance works in Solidity and how to derive smart contracts from other smart contracts in Solidity.

In this guide, we are going to learn how contract inheritance works in Solidity and how to derive smart contracts from other smart contracts in Solidity.

Solidity supports multi-level inheritance which allows you to derive smart contracts from other smart contracts.

The utility of that is to create smart contract based on other smart contracts so you don't have to implement all the functions and states in child smart contracts because you can access the implementation of the parent.

The most common examples are ERC-20 and ERC-721 smart contracts which respectively allow you to create your own custom token or NFT.

These standards are implemented by organisations like OpenZeppelin so all you have to do to create a token or an NFT smart contract is to inherit from their implementation of the standard and override the things you want to change and build on top of the base features.

How to inherit from another smart contract

To inherit from another smart contract, you first need to import it if it's defined in another file:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

contract A {
    function test() public pure virtual returns (string memory) {
        return "A";
    }
}
contracts/A.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

import "./A.sol";

contract B is A {
}
contracts/B.sol

In the code above, the contract B inherits from the contract A. To inherit from another smart contract, we use the is keyword when defining the contract so we say contract B is A { ... }.

So you can call the test function on the smart contract B and it will actually call the implementation that is inside the smart contract A.

If the contract A was defined in the same file as B, you wouldn't need to import it and you could just inherit from it using the is keyword.

If you want your contract to inherit from multiple smart contracts, it is totally possible:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

contract A {
    function test() public pure virtual returns (string memory) {
        return "A";
    }
}

contract B {
    function secondTest() public pure virtual returns (string memory) {
        return "B";
    }
}

contract C is A, B {
}

That way, C inherits from both A and B so it will have both test and secondTest functions. The order in which you put the smart contracts after the is keyword matters. You will understand why later.

How to override functions from the parent smart contract

To override a function from a parent smart contract means re-writing what that function does when called on the child smart contract.

To allow child smart contracts to override a function, the function in the parent smart contract needs to be marked as virtual.

To override a function from the parent smart contract, the name of the function in the child smart contract needs to be the same as the name in the parent and use the override keyword.

Here is an example:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

contract A {
    function test() public pure virtual returns (string memory) {
        return "A";
    }
}

contract B is A {
    function test() public pure override returns (string memory) {
        return "B";
    }
}

In the function above, if you call the test function on the smart contract A it will return "A" and if you call it from the smart contract B it will return "B".

It might not seem useful at the first glance but it actually is. It allows you to have a default implementation of a function that child smart contracts can change and build on top of.

How to build on top of parent smart contract functions

If you don't want to change the implementation of the parent smart contract but just add things to that implementation, you can use the super keyword to call a function from the parent smart contract.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

contract A {
    function test() public pure virtual returns (uint256) {
        return 1;
    }
}

contract B is A {
    function test() public pure override returns (uint256) {
        uint256 a = super.test();
        return a + 1;
    }
}

In the code above, super.test() will call the test function from the smart contract A. We assign that to a variable and return the result of that call plus one.

If you want to call the constructor of the parent smart contract in the constructor of a child smart contract, you don't need to use super:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

contract A {
    uint256 testVar;
    
    constructor(uint256 _testVar) {
        testVar = _testVar;
    }
}

contract B is A {
    constructor() A(1) {
    }
}

In the code above, when the constructor of B is called, it will call the constructor of A and pass in 1 as a parameter to the constructor and then run the code that is in the constructor of B.

If you inherit from multiple smart contracts, and override a function that these contracts implement, you'll have override it differently:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

contract A {
    function test() public pure virtual returns (uint256) {
        return 1;
    }
}

contract B is A {
    function test() public pure override returns (uint256) {
        uint256 a = super.test();
        return a + 1;
    }
}

contract C is A, B {
    function test() public pure override(A, B) returns (uint256) {
        uint256 b = super.test();
        return b + 1;
    }
}

In the code above, when C overrides the test function, you need to pass (A, B) to the override keyword, in the same order as it is in the contract definition. Otherwise it will throw a compiler error.

Now, when you call test from C, super.test() will call the test function that is in B and not A because B is the right-most contract. It's the one that C inherits from last.

If you define the contract C that way: contract C is B, A and then override the function that way:

contract C is B, A {
    function test() public pure override(B, A) returns (uint256) {
        uint256 a = super.test();
        return a + 1;
    }
}

Then super.test() will call the implementation of test that's in A.

That's why the order of inheritance matters.

And that's it 🎉

Thank you for reading this article, if you want to go further and get better at blockchain development, leave your email below and you'll get:

  • access to the private Discord of a community of Web3 builders
  • access to free guides that will teach you Blockchain Development