9

I'm attempting to understand how solidity resolves multiple inheritance. As explained in the docs, Solidity uses C3 linearization. The relation between my classes is displayed on the image below: Inheritance graph

I want to understand in which order super is working when called from SampleCrowdsale. I ran the C3 linearization algorithm manually as well as using this tool. The results I get:

1) SampleCrowdsale
2) CappedCrowdsale
3) RefundableCrowdsale
4) FinalizableCrowdsale
5) TimedCrowdsale
6) MintedCrowdsale
7) IndividuallyCappedCrowdsale
8) TieredCrowdsale
9) Crowdsale
10) Ownable

Then I tried to confirm my findings empirically. I added a method log to every contract in the inheritance chain, the method fires an event with the contract name. My findings using this method differ from the analytical approach:

1) SampleCrowdsale
2) TieredCrowdsale
3) IndividuallyCappedCrowdsale
4) MintedCrowdsale
5) RefundableCrowdsale
6) FinalizableCrowdsale
7) TimedCrowdsale
8) CappedCrowdsale
9) Crowdsale
10) Ownable

Can anyone explain to me how to solve this?

7
+25

I did a few test and seems like the order you use to extend the classes it matter. Given this example:

pragma solidity 0.4.21;

contract Ownable {
    event OwnableE(uint);
    function Ownable() public {
        emit OwnableE(1);
    }
}
contract TimedCrowdsale {
    event TimedCrowdsaleE(uint);
    function TimedCrowdsale() public {
        emit TimedCrowdsaleE(1);
    }
}
contract Crowdsale {
    event CrowdsaleE(uint);
    function Crowdsale() public {
        emit CrowdsaleE(1);
    }
}

contract FinalizableCrowdsale is Ownable, TimedCrowdsale  {
    event FinalizableCrowdsaleE(uint);
    function FinalizableCrowdsale() public {
        emit FinalizableCrowdsaleE(1);
    }
}
contract CappedCrowdsale is Crowdsale {
    event CappedCrowdsaleE(uint);
    function CappedCrowdsale() public {
        emit CappedCrowdsaleE(1);
    }
} 
contract RefundableCrowdsale is FinalizableCrowdsale {
    event RefundableCrowdsaleE(uint);
    function RefundableCrowdsale() public {
        emit RefundableCrowdsaleE(1);
    }
}
contract MintedCrowdsale is Crowdsale { 
    event MintedCrowdsaleE(uint);
    function MintedCrowdsale() public {
        emit MintedCrowdsaleE(1);
    }
}
contract IndividuallyCappedCrowdsale is Ownable, Crowdsale { 
    event IndividuallyCappedCrowdsaleE(uint);
    function IndividuallyCappedCrowdsale() public {
        emit IndividuallyCappedCrowdsaleE(1);
    }
}
contract TieredCrowdsale is Ownable, Crowdsale {
    event TieredCrowdsaleE(uint);
    function TieredCrowdsale() public {
        emit TieredCrowdsaleE(1);
    }
}

contract SampleCrowdsale is CappedCrowdsale, RefundableCrowdsale, MintedCrowdsale, IndividuallyCappedCrowdsale, TieredCrowdsale { 
    event SampleCrowdsaleE(uint);
    function SampleCrowdsale() public {
        emit SampleCrowdsaleE(1);
    }
} 

the call chain would be:

[
    {
        "topic": "bdac561e97cd2f10f81e3ccfa6953563ea5921aacbbed1a01338388b58b7035b",
        "event": "OwnableE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "0745b765421933707f4bdaebed6f1478ed78c6f1fa656035d672a1eb04a914ce",
        "event": "CrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "7e78dc6ab4bed05d0901d10704f58ce946a5eb50d3a279d26887df13fe4b19d6",
        "event": "CappedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "896ac1cf1e4f0dea962c7533989a352036679bd5222c53e344f88a80eb70febb",
        "event": "TimedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "07b3016f470d5b16960d54a55cb7e9bd74c20746a19c7cb0ba634a52e83934ce",
        "event": "FinalizableCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "2a1ec31b0b7f73e6938c981b1f806a9d76ec0c1904a53de98197f2b7054301d7",
        "event": "RefundableCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "39a8041bef698db3de246b679ad52b01b9f8ba609de0573804701617faac2803",
        "event": "MintedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "9dde5892f2ad9eb0f156963773cf38cad6b804f8061d765fbbbc10f9d3d4048f",
        "event": "IndividuallyCappedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "3171f924f0e542de51df919c69da5e54fb7c51a9467384a87261ac7f6f945f48",
        "event": "TieredCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "615ecfd72872a19eeed214c27309877c89602943fa4b789f87d67fc0cce30a11",
        "event": "SampleCrowdsaleE",
        "args": [
            "1"
        ]
    }
]

from the bottom to the top this would be the order

SampleCrowdsale
TieredCrowdsale
IndividuallyCappedCrowdsale
MintedCrowdsale
RefundableCrowdsale
FinalizableCrowdsale
TimedCrowdsale
CappedCrowdsale
Crowdsale
Ownable

same as the one you found on your empirical approach.

But if I change the order of the class that I'm going to extend in the SampleCrowdsale the output would be different

contract SampleCrowdsale is TieredCrowdsale, IndividuallyCappedCrowdsale, MintedCrowdsale, RefundableCrowdsale, CappedCrowdsale { 
    event SampleCrowdsaleE(uint);
    function SampleCrowdsale() public {
        emit SampleCrowdsaleE(1);
    }
} 

this will give you this output

[
    {
        "topic": "bdac561e97cd2f10f81e3ccfa6953563ea5921aacbbed1a01338388b58b7035b",
        "event": "OwnableE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "0745b765421933707f4bdaebed6f1478ed78c6f1fa656035d672a1eb04a914ce",
        "event": "CrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "3171f924f0e542de51df919c69da5e54fb7c51a9467384a87261ac7f6f945f48",
        "event": "TieredCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "9dde5892f2ad9eb0f156963773cf38cad6b804f8061d765fbbbc10f9d3d4048f",
        "event": "IndividuallyCappedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "39a8041bef698db3de246b679ad52b01b9f8ba609de0573804701617faac2803",
        "event": "MintedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "896ac1cf1e4f0dea962c7533989a352036679bd5222c53e344f88a80eb70febb",
        "event": "TimedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "07b3016f470d5b16960d54a55cb7e9bd74c20746a19c7cb0ba634a52e83934ce",
        "event": "FinalizableCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "2a1ec31b0b7f73e6938c981b1f806a9d76ec0c1904a53de98197f2b7054301d7",
        "event": "RefundableCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "7e78dc6ab4bed05d0901d10704f58ce946a5eb50d3a279d26887df13fe4b19d6",
        "event": "CappedCrowdsaleE",
        "args": [
            "1"
        ]
    },
    {
        "topic": "615ecfd72872a19eeed214c27309877c89602943fa4b789f87d67fc0cce30a11",
        "event": "SampleCrowdsaleE",
        "args": [
            "1"
        ]
    }
]

and the order is the one you expect (again from the bottom to the top on the logs)

SampleCrowdsale
CappedCrowdsale
RefundableCrowdsale
FinalizableCrowdsale
TimedCrowdsale
MintedCrowdsale
IndividuallyCappedCrowdsale
TieredCrowdsale
Crowdsale
Ownable

The linearization in Solidity is from the right to the left instead of left to right as per wiki (used in Python). As the documentation states:

Solidity is similar to Python in that it uses “C3 Linearization” to force a specific order in the DAG of base classes. Especially, the order in which the base classes are given in the is directive is important: You have to list the direct base contracts in the order from “most base-like” to “most derived”. Note that this order is different from the one used in Python. In the following code, Solidity will give the error “Linearization of inheritance graph impossible”.

My overall consideration is to make it as simple as possible to avoid that the multiple inheritance will be a pain and you encounter the yo-yo problem.

  • Very detailed answer, thank you. I also opened an issue on github and am waiting for the developers feedback – user3223162 Apr 13 '18 at 9:25
  • The issue has been resolved on the provided github issue. Could you update your answer so I can mark it as accepted? – user3223162 May 4 '18 at 9:58
  • The issue is not resolved (provided is an issue anyway, I think is more the design of the language design), they just updated the documentation. As I wrote in my post, the linearization in Solidity is from the right to the left instead of left to right as per wiki. How should I updated the answer? I can't see any improvement to my answer in the github link. – mirg May 4 '18 at 10:04
  • The wiki has been updated and now is stating that linearization is being done from right to left. The way your conclusions are presented is that you state that you are unsure, however you were right. Could you update the conclusion with these details? – user3223162 May 4 '18 at 10:13
  • oh, got it. Now is more clear :) – mirg May 4 '18 at 10:15
0

This may not be as formal as you would like, but it may be helpful.

Your diagram confuses me and I don't think it's what you want. It's more likely that you want Ownable to be inherited by SampleCrowdsale and then various branches of increasing specialization.

contract Ownable {
    address public owner = 0xca35b7d915458ef540ade6068dfe2f44e8fa733c; 

    function getOwner() public view returns(address theOwner) {
        return owner;
    }
}

contract SampleCrowdsale is Ownable {

    address public owner = 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c; 

    /**
     * super does not work in this context unless SampleCrowdsale is Ownable
     */

    function getOwner2() public view returns(address theOwner) {
        return super.getOwner();
    }
}

contract CappedCrowdsale is SampleCrowdsale {

    address public owner = 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db;

    /**
     * In this context, it comes from SampleCrowdsale
     */

    function getOwner3() public view returns(address anotherOwner) {
        return super.getOwner2(); // <== this would not work unless "super" referred to the (first, left to right) contract mentioned after "is".
    }

    /**
     * It's worth noting that inherited functions are now part of CappedCrowdsale
     * You can inspect (this) getOwner2() as well as (this) getOwner() because they
     * have become part of the CappedCrowdsale contract.
     * 
     * The following works because the function became part of SampleCrowdsale.
     */

    function telling() public view returns(address chained) {
        return super.getOwner();
    }
}
contract RefundableCrowdsale is SampleCrowdsale {}
contract MintedCrowdsale is SampleCrowdsale {}
contract TieredCrowdsale is SampleCrowdsale {}

contract FinalizedCrowdsale is RefundableCrowdsale {}
contract TimedCrowdsale is FinalizedCrowdsale {}

The following is not exactly like your diagram. I think it's more aligned with what you're trying to achieve. I can't easily conceive of a Crowdsale that wouldn't be Ownable, and the rest seem to be specializations, so I'd probably get that concern out of the way first.

contract Ownable {}
contract Crowdsale is Ownable {}
contract SampleCrowdsale is Crowdsale {}
contract CappedCrowdsale is SampleCrowdsale {}
contract RefundableCrowdsale is SampleCrowdsale {}
contract MintedCrowdsale is SampleCrowdsale {}
contract TieredCrowdsale is SampleCrowdsale {}
contract FinalizedCrowdsale is RefundableCrowdsale {}
contract TimedCrowdsale is FinalizedCrowdsale {}

Everything is Ownable.

Since everything inherits the functions of Ownable, you can probably get by without using the super idea in most cases.

Or, if I take even more liberty, another interpretation is you want to create classes based on a basic Crowdsale and then present an Instance of one those classes as an example, called SampleCrowdsale. It might look something like this:

Define the re-usable classes

contract Ownable {}
contract Crowdsale is Ownable {}
contract CappedCrowdsale is Crowdsale {}
contract RefundableCrowdsale is Crowdsale {}
contract MintedCrowdsale is Crowdsale {}
contract TieredCrowdsale is Crowdsale {}
contract FinalizedCrowdsale is RefundableCrowdsale {}
contract TimedCrowdsale is RefundableCrowdsale {}

Then use one of them

contract SampleCrowdsale is TimedCrowdsale {}

Hope it helps.

  • If my answer gives you any ideas that lead to rethinking the original diagram, it might help others with the same quandary if you appended the revised version to the original question. It might really help others if they can see the evolution of your thinking. – Rob Hitchens - B9lab Apr 13 '18 at 2:46
  • but in his example CappedCrowdsale, MintedCrowdsale, Crowdsale and TimedCrowdsale are not ownable – mirg Apr 13 '18 at 3:16
  • I couldn't make it work as diagramed. – Rob Hitchens - B9lab Apr 13 '18 at 3:28
  • Thanks for your effort but pragmatics aside, in the scope of this question I'm only interested in inheritance resolution. I think the pragmatics can be a separate question and would like to keep this focused. – user3223162 Apr 13 '18 at 8:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.