Proxy Contracts Inheritance Hierarchy

Hi everyone,

I trust everyone is well.

I’ve been studying the proxy contracts to get a better understanding of how they work. I’ve come across a few things that I don’t understand along the way. I’ve listed some questions below.

  • The BaseAdminUpgradeabilityProxy contract inherits the BaseUpgradeabilityProxy contract, but only imports the UpgradeabilityProxy.sol file. Why does this work? Does this work because the UpgradeabilityProxy contract imports and inherits the BaseUpgradeabilityProxy? Why wouldn’t you just import the BaseUpgradeabilityProxy contract into the BaseAdminUpgradeabilityProxy contract?

BaseAdminUpgradeabilityProxy Inheritance Issue

  • Similarly, the AdminUpgradeabilityProxy contract inherits the UpgradeabilityProxy contract and BaseAdminUpgradeabilityProxy contract, but only imports the BaseAdminUpgradeabilityProxy.sol file. Does it have access to UpgradeabilityProxy.sol through the BaseAdminUpgradeabilityProxy contract?

  • There is a _willFallback() function in the BaseAdminUpgradeabilityProxy contract. In that function, it invokes super._willFallback(). I thought that super only accessed functions from the immediate parent contract level, but the _willFallback() function is found in the Proxy contract which is two levels above. If you use super, does it just keep going up levels until it finds the referenced function? Is there a limit to how my inheritance levels it will go up?

The rectangles mean inherited contracts and the ovals mean imported, but not inherited.

I would appreciate any advice or comments. Cheers.

Sincerely,
Craig

2 Likes

Hi @cjd9s,

You may want to look at the proxy contracts that were migrated to OpenZeppelin Contracts 3.x which could be simpler to follow the hierarchy:

Currently the OpenZeppelin Upgrades Plugins use contracts in Upgrades Core (though this will change to use the proxy contracts in OpenZeppelin Contracts). So recommend studying these versions as this is what you would use when deploying an upgradeable project today with the Upgrades Plugins.

The proxy contracts used in the OpenZeppelin CLI, as you found, have a more complex hierarchy.
Please see: Building for interoperability: why we’re focusing on Upgrades Plugins as we are focusing our upgradeability efforts on the Upgrades Plugins exclusively.
https://github.com/OpenZeppelin/openzeppelin-sdk/tree/v2.8.2/packages/lib/contracts/upgradeability


I assume this works because an import of a parent allows inheriting from a grand parent.
It would be simpler if the imports matched the inheritance.

I assume the same applies.

I assume that super calls up the inheritance tree: https://solidity.readthedocs.io/en/latest/contracts.html#inheritance

I suggest trying it out with some simple contracts to see it for yourself.

Hi @cjd9s,

Let me know if you need more information.

Hi Andrew @abcoathup,

If you ever stop hearing from me, it’s because I’m dead. I will never not need more information.

I prefer to download and inherit the proxy contracts directly. I have gone through and mostly understand what is going on. It has been interesting to learn Yul. I do have a few questions, however.

  1. My previous post had a question about super. I noted that the _willFallback() function in BaseAdminUpgradeabilityProxy contract invokes super._willFallback(). However, there is no _willFallback() in the immediate parent contract. It’s actually two levels up in the Proxy contract.

As you suggested, I created some simple contracts in Remix. And, lo, even though there was no function in the parent contract, there was one in the grandparent contract and it worked.

However, you copied a link to Solidity docs about inheritance. I noted the following:
“It is possible to call functions further up in the inheritance hierarchy internally by … using super.functionName() if you want to call the function one level higher up in the flattened inheritance hierarchy (see below).”

So, if I’m reading this right, it says that super can be used to call a function one level higher up in the inheritance hierarchy. However, it seems to work at least two levels up. Am I reading this wrong, do I not understand the specific order in the directed acyclic graph (DAG) of base classes, or is Solidity Docs wrong?

  1. In the ProxyFactory contract there is a deployMinimal() function.

// store next available memory address in clone

let clone := mload(0x40)

// Set the bytes of memory from the next available memory address (clone) to clone+32
// equal to
// 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
// which is the constructor of the clone contract

mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)

// Set the bytes of memory from clone+20 to clone+52 equal to targetBytes
// which is the address of the implementation (logic) contract

mstore(add(clone, 0x14), targetBytes)

// Set the bytes of memory from clone+40 to clone+72 is set to equal
// 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000

mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)

So, am I correct that the first store operation is the clone constructor? What is the signifiance of the final (third) store operation?

  1. If I want to use the Proxy Factory contract, do I need to inherit the InitializableAdminUpgradeabilityProxy into my own contract and then change the import in the Proxy Contract from InitializableAdminUpgradeabilityProxy to my contract? And then replace the InitializableAdminUpgradeabilityProxy reference to my contract in the constructor function?

As always, any insight you could provide would be great. Cheers.

Sincerely,
Craig

1 Like

Hi @cjd9s,

If you are deploying from Truffle or Buidler I recommend using the proxy contracts via OpenZeppelin Upgrades Plugins as the plugins will check upgrade safety when deploying and upgrading.

I assume the documentation needs updating. I suggest creating an issue and if the Solidity team agree, a PR to update the docs: https://github.com/ethereum/solidity/blob/develop/docs/contracts/inheritance.rst

For information on minimal proxies, I recommend reading:

Yes, that sounds right. You could modify the ProxyFactory to meet your use case.

There is an open issue and a PR in OpenZeppelin Contracts to create a factory to deploy upgradeable contracts

Hi Andrew @abcoathup,

I appreciate your help.

I submitted an issue in Github referencing the Solidity docs explanation of super. We’ll see what happens. I suspect that it’s probably a misunderstanding on my part in some way.

I appreciate the link to the Minimal Proxy contract blog post. Very helpful. And I learned some things about how the EVM stack works.

I have some more questions, but I’m going to create a new post. Thanks again for your help. Cheers.

Sincerely,
Craig

1 Like

Hi @cjd9s,

Thanks for creating an issue for the docs, as it would be great to get that cleared up.
Can you share the link to the Solidity docs issue?

Keep the questions coming. Appreciate you creating a new topic for each type of question. :pray:

Hi Andrew @abcoathup,

The link for the issue that I created is https://github.com/ethereum/solidity/issues/10131. Cheers.

Sincerely,
Craig

1 Like

Hi @cjd9s,

In case you didn’t know, in GitHub you can use Markdown to improve the readability of your post.

It may be worth adding this to your contract examples to make your Issue easier to read

```solidity
Code goes here
```

You can also add a quote for the line from the documentation.
> Quote some text

Hi Andrew,

I didn’t know that. I have used your suggestions to clean up the issue. Cheers.

Sincerely,
Craig

1 Like

Hi Andrew @abcoathup,

I received a reply on the Solidity docs inheritance issue that I created. As the person points out, because superParent is inheriting from superGrandparent, the setNumber() function is also defined in that the superParent contract. So using super.setNumber() in the superChild contract is accessing the setNumber() function in the superParent contract which is one level above in the inheritance hierarchy. It seems obvious now, but I guess this is how you learn.

The person goes on to note that the keyword super is mainly useful when multiple base classes implement the same function type. Cheers.

Sincerely,
Craig

1 Like

It looks so much better. Thank you.

Whilst obvious in retrospect I still found it confusing, though haven’t found a succinct way to express it in the documentation.

Hi Andrew @abcoathup,

After mulling over the issue, I became confused again. However, after studying the documentation closely, I think I understand.

I believe that part of my confusion is my misunderstanding of the word ‘level’ in this context. The example given in the section on super in the Github link that you sent me has an inheritance order that goes Owned => Destructible => B1 => B2 => Final. (https://github.com/ethereum/solidity/blob/develop/docs/contracts/inheritance.rst). Owned is inherited by Destructible which is inherited by both B1 and B2 which are then both inherited by Final.

My previous understanding is that there would be four levels in this inheritance hierarchy (owned, destructible, B1 & B2, and final). However, it appears that super references the next contract in the sequence Owned => Destructible => B1 => B2 => Final. So, in a sense, this contract system has five levels.

This is important in cases of multiple inheritance where you need to ensure that the overridden function in both B1 and B2 is called. The reason in the example is that there is different clean up code that needs to be run in B1 AND B2 in advance of a selfdestruct command. If you use super.overriddenFunction() in final it calls the overriddenFunction() in B2. If you use super.overriddenFunction() in B2 it calls the overriddenFunction() in B1. If you use super.overriddenFunction() in B1 it calls the overriddenFunction() in Destructible.

So, super really doesn’t have much affect on contracts unless there is multiple inheritance involved. This is because without multiple inheritance (i.e. a straight line down of inheriting functions) the overridden function will always be in the contract just above the contract in which it has been overridden. And that’s why the example I gave was straightforward to answer.

Essentially, this comes down to understanding what is meant by a level in a ‘flattened’ inheritance hierarchy. Cheers.

Sincerely,
Craig

1 Like

Hi @cjd9s,

You could also look at: