Solidity Inheritance – Why Does Calling Function Bar in Contract D Call C, B, and Finally A?

inheritancesolidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

/* Inheritance tree
   A
 /  \
B   C
 \ /
  D
*/

contract A {
    // This is called an event. You can emit events from your function
    // and they are logged into the transaction log.
    // In our case, this will be useful for tracing function calls.
    event Log(string message);

    function foo() public virtual {
        emit Log("A.foo called");
    }

    function bar() public virtual {
        emit Log("A.bar called");
    }
}

contract B is A {
    function foo() public virtual override {
        emit Log("B.foo called");
        A.foo();
    }

    function bar() public virtual override {
        emit Log("B.bar called");
        super.bar();
    }
}

contract C is A {
    function foo() public virtual override {
        emit Log("C.foo called");
        A.foo();
    }

    function bar() public virtual override {
        emit Log("C.bar called");
        super.bar();
    }
}

contract D is B, C {
    // Try:
    // - Call D.foo and check the transaction logs.
    //   Although D inherits A, B and C, it only called C and then A.
    // - Call D.bar and check the transaction logs
    //   D called C, then B, and finally A.
    //   Although super was called twice (by B and C) it only called A once.

    function foo() public override(B, C) {
        super.foo();
    }

    function bar() public override(B, C) {
        super.bar();
    }
}

As far as I understand, calling D.bar() first executes C.bar (Immediate parent) which will then call A.bar (Contract Cs parent). Can anyone explain why is Contract B called here?

Source

Best Answer

Please don't fall into the trick of thinking Solidity is an object-oriented language; it is not.

Hierarchy is just a way to tell the compiler where to put functions in the final smart contract. It has its own parsing logic, so if things get too weird, simply get rid of it at all.

Hierarchy capability was introduced to simplify the development. If it creates confusion, like in this case, it's better to avoid it.

When you compile the mentioned contract in fact, what happens is that all the code and functions are merged into a single contract.

While merging the code, if the compiler finds a function with the same signature (name + parameter's type), it overwrites the previous function, and only the last one is preserved.

The order is determined by the is keyword: contract D is B, C means that the functions of B are merged before the functions of C.

In this case C.foo() wins over B.foo(), and B.foo() does not actually exist in the final contract. This is why the flow is D.foo() -> C.foo() -> A.foo().

On the contrary, if you specify the super keyword, the Solidity compiler understands that you want to retain the corresponding parent's functions in the final contract, and then it put those functions in the reverse order it reads it D.bar() -> C.bar() -> B.bar() -> A.bar()

Also, please note that

Although super was called twice (by B and C), it is only called A once.

Related Topic