Solidity Diamond Inheritance

Not related to Diamond (Upgrade) Standard .

Diamonds Are a Girl's Best Friend, but that might not be the case for Solidity developers. Solidity allows for multiple inheritance and uses C3 linearization to resolve it.

Let’s take a quick dive into C3 linearization and how it works. Its first major appearance was when it was chosen as the default algorithm for method resolution in Python 2.3, followed by Raku, Parrot, Solidity, and PGF/TikZ's Object-Oriented Programming module.

Python's former "Benevolent dictator for life" (BDFL) Guido van Rossum summarizes C3 superclass linearization thusly:

“Basically, the idea behind C3 is that if you write down all of the ordering rules imposed by inheritance relationships in a complex class hierarchy, the algorithm will determine a monotonic ordering of the classes that satisfies all of them. If such an ordering can not be determined, the algorithm will fail.”

In other words, the C3 linearization algorithm takes a class hierarchy, which may be nonlinear, and converts it to a linear hierarchy or fails. To clarify by example:

class O

class A extends O

class B extends O

class C extends O

class D extends O

class E extends O

class K1 extends A, B, C

class K2 extends D, B, E

class K3 extends D, A

class Z extends K1, K2, K3

After linearization, it will result in [Z, K1, K2, K3, D, A, B, C, E, O]. Pay attention to how O is the last class in the list, even if it is a direct parent for class E.

Now, let’s jump back to Solidity. Given an example:

contract Mommy {

    constructor() public {}

}

contract Daddy {

    constructor() public {}

}

contract Kiddo is Mommy, Daddy {

    constructor() public {}

}

The above code will produce the following constructor call order:

  • Mommy
  • Daddy
  • Kiddo

Now, Solidity has a super keyword which allows it to call a method on the next parent in an inheritance hierarchy. What is not expected by many is the outcome of the example below:

contract Granny {
    event Say(string, string);

    constructor() public {
        emit Say('I am Granny and I am cool!', 'no one');
    }

   function getName() public returns (string memory) {
        return 'Granny';
    }
}

contract Mommy is Granny {
    constructor() public {
        emit Say('I am Mommy and I call ', super.getName());
    }

    function getName() public returns (string memory) {
        return 'Mommy';
    }
}

contract Daddy is Granny {
    constructor() public {
        emit Say('I am Daddy and I call ', super.getName());
    }

    function getName() public returns (string memory) {
        return 'Daddy';
    }
}

contract Kiddo is Mommy, Daddy {
    constructor() public {
        emit Say('I am Kiddo and I call ', super.getName());
    }
}

What would the order and the contents be of emitted logs? Stop right here and think for a second.

Think.
Think.
Think.
Ready?

Logs will be emitted in the following order:

  • I am Granny and I am cool
  • I am Mommy and I call Granny
  • I am Daddy and I call Mommy
  • I am Kiddo and I call Daddy

The first and second ones are obvious. What may catch you by surprise is that emit Say('I am Daddy and I call ', super.getName()); results in I am Daddy and I call Mommy when contract Daddy has only Granny on its inheritance list!

This happens due to how C3 linearization works. However, think for a moment that code super.foo() may call a method on a class which is not in an inheritance list of a calling class. It is mind blowing!

We just showed that Solidity allows for multiple inheritance and resolves it using C3 linearization. One of the tricky outcomes of such a design is the super keyword. Under displayed circumstances, it will result in a call to a method not present on the inheritance list of a contract. When using a super keyword, always keep in mind that a call to a parent can be substituted by a sibling's call.


Update 23 April 2020: It was brought to our attention that this topic was also presented in this article by Sheraz Arshad. Their article also presents an interesting view of the diamond inheritance problem, and we encourage you to check it out as well if you are interested in the topic.

3 Likes

Thanks @ylv-io,

Nice explanation of Diamond Inheritance in Solidity.
I am Daddy and I call Mommy was definitely not expected.


As an aside on Diamond rings: Why Engagement Rings Are a Scam - Adam Ruins Everything

3 Likes

I have updated the code example for Solidity 0.8.23:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.23;

contract Granny {
    event Say(string, string);

    constructor() {
        emit Say('I am Granny and I am cool!', 'no one');
    }

   function getName() public virtual returns (string memory) {
        return 'Granny';
    }
}

contract Mommy is Granny {
    constructor() {
        emit Say('I am Mommy and I call ', super.getName());
    }

    function getName() public virtual override returns (string memory) {
        return 'Mommy';
    }
}

contract Daddy is Granny {
    constructor() {
        emit Say('I am Daddy and I call ', super.getName());
    }

    function getName() public virtual override returns (string memory) {
        return 'Daddy';
    }
}

contract Kiddo is Mommy, Daddy {
    constructor() {
        emit Say('I am Kiddo and I call ', super.getName());
    }

    function getName() public pure override(Mommy,Daddy) returns (string memory) {
        return 'Kiddo';
    }
}

Since Solidity 0.6.0 virtual and override keywords have been added and now for multiple inheritance " the most derived base contracts that define the same function must be specified explicitly after the override keyword." (source: https://ethereum.stackexchange.com/a/78573/24685)